Compare commits

..

14 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
d6ab3a18ea Address code review feedback: fix X-Forwarded-For logic and add error logging
Co-authored-by: mserico <140243407+mserico@users.noreply.github.com>
2026-02-15 18:36:35 +00:00
copilot-swe-agent[bot]
b7fa3a6194 Format code with gofmt
Co-authored-by: mserico <140243407+mserico@users.noreply.github.com>
2026-02-15 18:34:09 +00:00
copilot-swe-agent[bot]
ac1307e576 Add comprehensive tests for reverse proxy functionality
Co-authored-by: mserico <140243407+mserico@users.noreply.github.com>
2026-02-15 18:32:57 +00:00
copilot-swe-agent[bot]
7f2c238eba Implement basic reverse proxy functionality
Co-authored-by: mserico <140243407+mserico@users.noreply.github.com>
2026-02-15 18:31:03 +00:00
copilot-swe-agent[bot]
2cf83d3b0c Initial plan 2026-02-15 18:24:23 +00:00
Yang Luo
3b8e7c9da2 fix: extend application with reverse proxy fields (#5113) 2026-02-16 02:23:47 +08:00
Yang Luo
4d5de767b0 fix: sync frontend i18n strings 2026-02-16 02:01:48 +08:00
Yang Luo
54bf8eae5c fix: improve category column UI in app list page 2026-02-16 01:46:06 +08:00
IsAurora6
1731b74fa0 fix: fix issue that dummy payments failed when there were too many items in the order (#5108) 2026-02-15 22:35:59 +08:00
Yang Luo
6e1e5dd569 feat: add scope-to-tool permission checking for Casdoor MCP server (#5104) 2026-02-15 22:31:35 +08:00
Yang Luo
b183359daf fix: rename order state PaymentFailed to Failed and improve UI (#5107) 2026-02-15 21:52:24 +08:00
Yang Luo
3cb9df3723 feat: [mcp-5] add Application.Category and Application.Type fields for agent applications (MCP, A2A) (#5102) 2026-02-15 21:28:00 +08:00
Yang Luo
9d1e5c10d0 feat: [mcp-4] implement RFC 8707 Resource Indicators for OAuth 2.0 (#5098) 2026-02-15 18:03:22 +08:00
Yang Luo
ef84c4b0b4 feat: [mcp-3] implement OAuth 2.0 Dynamic Client Registration (RFC 7591) (#5097) 2026-02-15 17:25:44 +08:00
58 changed files with 2150 additions and 252 deletions

View File

@@ -59,6 +59,7 @@ p, *, *, GET, /api/get-qrcode, *, *
p, *, *, GET, /api/get-webhook-event, *, *
p, *, *, GET, /api/get-captcha-status, *, *
p, *, *, *, /api/login/oauth, *, *
p, *, *, POST, /api/oauth/register, *, *
p, *, *, GET, /api/get-application, *, *
p, *, *, GET, /api/get-organization-applications, *, *
p, *, *, GET, /api/get-user, *, *

View File

@@ -30,6 +30,8 @@ ldapsServerPort = 636
radiusServerPort = 1812
radiusDefaultOrganization = "built-in"
radiusSecret = "secret"
proxyHttpPort =
proxyHttpsPort =
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
logConfig = {"adapter":"file", "filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
initDataNewOnly = false

View File

@@ -323,7 +323,7 @@ func (c *ApiController) Signup() {
// If OAuth parameters are present, generate OAuth code and return it
if clientId != "" && responseType == ResponseTypeCode {
code, err := object.GetOAuthCode(userId, clientId, "", "password", responseType, redirectUri, scope, state, nonce, codeChallenge, c.Ctx.Request.Host, c.GetAcceptLanguage())
code, err := object.GetOAuthCode(userId, clientId, "", "password", responseType, redirectUri, scope, state, nonce, codeChallenge, "", c.Ctx.Request.Host, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error(), nil)
return

View File

@@ -161,12 +161,13 @@ func (c *ApiController) HandleLoggedIn(application *object.Application, user *ob
nonce := c.Ctx.Input.Query("nonce")
challengeMethod := c.Ctx.Input.Query("code_challenge_method")
codeChallenge := c.Ctx.Input.Query("code_challenge")
resource := c.Ctx.Input.Query("resource")
if challengeMethod != "S256" && challengeMethod != "null" && challengeMethod != "" {
c.ResponseError(c.T("auth:Challenge method should be S256"))
return
}
code, err := object.GetOAuthCode(userId, clientId, form.Provider, form.SigninMethod, responseType, redirectUri, scope, state, nonce, codeChallenge, c.Ctx.Request.Host, c.GetAcceptLanguage())
code, err := object.GetOAuthCode(userId, clientId, form.Provider, form.SigninMethod, responseType, redirectUri, scope, state, nonce, codeChallenge, resource, c.Ctx.Request.Host, c.GetAcceptLanguage())
if err != nil {
c.ResponseError(err.Error(), nil)
return

74
controllers/oauth_dcr.go Normal file
View File

@@ -0,0 +1,74 @@
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"net/http"
"github.com/casdoor/casdoor/object"
)
// DynamicClientRegister
// @Title DynamicClientRegister
// @Tag OAuth API
// @Description Register a new OAuth 2.0 client dynamically (RFC 7591)
// @Param organization query string false "The organization name (defaults to built-in)"
// @Param body body object.DynamicClientRegistrationRequest true "Client registration request"
// @Success 201 {object} object.DynamicClientRegistrationResponse
// @Failure 400 {object} object.DcrError
// @router /api/oauth/register [post]
func (c *ApiController) DynamicClientRegister() {
var req object.DynamicClientRegistrationRequest
err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
if err != nil {
c.Ctx.Output.Status = http.StatusBadRequest
c.Data["json"] = object.DcrError{
Error: "invalid_client_metadata",
ErrorDescription: "invalid request body: " + err.Error(),
}
c.ServeJSON()
return
}
// Get organization from query parameter or default to built-in
organization := c.Ctx.Input.Query("organization")
if organization == "" {
organization = "built-in"
}
// Register the client
response, dcrErr, err := object.RegisterDynamicClient(&req, organization)
if err != nil {
c.Ctx.Output.Status = http.StatusInternalServerError
c.Data["json"] = object.DcrError{
Error: "server_error",
ErrorDescription: err.Error(),
}
c.ServeJSON()
return
}
if dcrErr != nil {
c.Ctx.Output.Status = http.StatusBadRequest
c.Data["json"] = dcrErr
c.ServeJSON()
return
}
// Return 201 Created
c.Ctx.Output.Status = http.StatusCreated
c.Data["json"] = response
c.ServeJSON()
}

View File

@@ -176,6 +176,7 @@ func (c *ApiController) GetOAuthToken() {
subjectToken := c.Ctx.Input.Query("subject_token")
subjectTokenType := c.Ctx.Input.Query("subject_token_type")
audience := c.Ctx.Input.Query("audience")
resource := c.Ctx.Input.Query("resource")
if clientId == "" && clientSecret == "" {
clientId, clientSecret, _ = c.Ctx.Request.BasicAuth()
@@ -231,6 +232,9 @@ func (c *ApiController) GetOAuthToken() {
if audience == "" {
audience = tokenRequest.Audience
}
if resource == "" {
resource = tokenRequest.Resource
}
}
}
@@ -275,7 +279,7 @@ func (c *ApiController) GetOAuthToken() {
}
host := c.Ctx.Request.Host
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, nonce, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage(), subjectToken, subjectTokenType, audience)
token, err := object.GetOAuthToken(grantType, clientId, clientSecret, code, verifier, scope, nonce, username, password, host, refreshToken, tag, avatar, c.GetAcceptLanguage(), subjectToken, subjectTokenType, audience, resource)
if err != nil {
c.ResponseError(err.Error())
return

View File

@@ -30,4 +30,5 @@ type TokenRequest struct {
SubjectToken string `json:"subject_token"`
SubjectTokenType string `json:"subject_token_type"`
Audience string `json:"audience"`
Resource string `json:"resource"` // RFC 8707 Resource Indicator
}

View File

@@ -72,6 +72,7 @@ func main() {
object.InitFromFile()
object.InitCasvisorConfig()
object.InitCleanupTokens()
object.InitApplicationMap()
util.SafeGoroutine(func() { object.RunSyncUsersJob() })
util.SafeGoroutine(func() { controllers.InitCLIDownloader() })
@@ -125,6 +126,7 @@ func main() {
go ldap.StartLdapServer()
go radius.StartRadiusServer()
go object.ClearThroughputPerSecond()
go proxy.StartProxyServer()
web.Run(fmt.Sprintf(":%v", port))
}

View File

@@ -15,6 +15,7 @@
package mcp
import (
"strings"
"time"
"github.com/casdoor/casdoor/object"
@@ -120,3 +121,58 @@ func (c *McpController) GetAcceptLanguage() string {
}
return language
}
// GetTokenFromRequest extracts the Bearer token from the Authorization header
func (c *McpController) GetTokenFromRequest() string {
authHeader := c.Ctx.Request.Header.Get("Authorization")
if authHeader == "" {
return ""
}
// Extract Bearer token
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
return ""
}
return parts[1]
}
// GetClaimsFromToken parses and validates the JWT token and returns the claims
// Returns nil if no token is present or if token is invalid
func (c *McpController) GetClaimsFromToken() *object.Claims {
tokenString := c.GetTokenFromRequest()
if tokenString == "" {
return nil
}
// Try to find the application for this token
// For MCP, we'll try to parse using the first available application's certificate
// In a production scenario, you might want to use a specific MCP application
token, err := object.GetTokenByAccessToken(tokenString)
if err != nil || token == nil {
return nil
}
application, err := object.GetApplication(token.Application)
if err != nil || application == nil {
return nil
}
claims, err := object.ParseJwtTokenByApplication(tokenString, application)
if err != nil {
return nil
}
return claims
}
// GetScopesFromClaims extracts the scopes from JWT claims and returns them as a slice
func GetScopesFromClaims(claims *object.Claims) []string {
if claims == nil || claims.Scope == "" {
return []string{}
}
// Scopes are space-separated in OAuth 2.0
return strings.Split(claims.Scope, " ")
}

View File

@@ -268,7 +268,160 @@ func (c *McpController) handlePing(req McpRequest) {
}
func (c *McpController) handleToolsList(req McpRequest) {
tools := []McpTool{
allTools := c.getAllTools()
// Get JWT claims from the request
claims := c.GetClaimsFromToken()
// If no token is present, check session authentication
if claims == nil {
username := c.GetSessionUsername()
// If user is authenticated via session, return all tools (backward compatibility)
if username != "" {
result := McpListToolsResult{
Tools: allTools,
}
c.McpResponseOk(req.ID, result)
return
}
// Unauthenticated request - return all tools for discovery
// This allows clients to see what tools are available before authenticating
result := McpListToolsResult{
Tools: allTools,
}
c.McpResponseOk(req.ID, result)
return
}
// Token-based authentication - filter tools by scopes
grantedScopes := GetScopesFromClaims(claims)
allowedTools := GetToolsForScopes(grantedScopes, BuiltinScopes)
// Filter tools based on allowed scopes
var filteredTools []McpTool
for _, tool := range allTools {
if allowedTools[tool.Name] {
filteredTools = append(filteredTools, tool)
}
}
result := McpListToolsResult{
Tools: filteredTools,
}
c.McpResponseOk(req.ID, result)
}
func (c *McpController) handleToolsCall(req McpRequest) {
var params McpCallToolParams
err := json.Unmarshal(req.Params, &params)
if err != nil {
c.sendInvalidParamsError(req.ID, err.Error())
return
}
// Check scope-tool permission
if !c.checkToolPermission(req.ID, params.Name) {
return // Error already sent by checkToolPermission
}
// Route to the appropriate tool handler
switch params.Name {
case "get_applications":
var args GetApplicationsArgs
if err := json.Unmarshal(params.Arguments, &args); err != nil {
c.sendInvalidParamsError(req.ID, err.Error())
return
}
c.handleGetApplicationsTool(req.ID, args)
case "get_application":
var args GetApplicationArgs
if err := json.Unmarshal(params.Arguments, &args); err != nil {
c.sendInvalidParamsError(req.ID, err.Error())
return
}
c.handleGetApplicationTool(req.ID, args)
case "add_application":
var args AddApplicationArgs
if err := json.Unmarshal(params.Arguments, &args); err != nil {
c.sendInvalidParamsError(req.ID, err.Error())
return
}
c.handleAddApplicationTool(req.ID, args)
case "update_application":
var args UpdateApplicationArgs
if err := json.Unmarshal(params.Arguments, &args); err != nil {
c.sendInvalidParamsError(req.ID, err.Error())
return
}
c.handleUpdateApplicationTool(req.ID, args)
case "delete_application":
var args DeleteApplicationArgs
if err := json.Unmarshal(params.Arguments, &args); err != nil {
c.sendInvalidParamsError(req.ID, err.Error())
return
}
c.handleDeleteApplicationTool(req.ID, args)
default:
c.McpResponseError(req.ID, -32602, "Invalid tool name", fmt.Sprintf("Tool '%s' not found", params.Name))
}
}
// checkToolPermission validates that the current token has the required scope for the tool
// Returns false and sends an error response if permission is denied
func (c *McpController) checkToolPermission(id interface{}, toolName string) bool {
// Get JWT claims from the request
claims := c.GetClaimsFromToken()
// If no token is present, check if the user is authenticated via session
if claims == nil {
username := c.GetSessionUsername()
// If user is authenticated via session (e.g., session cookie), allow access
// This maintains backward compatibility with existing session-based auth
if username != "" {
return true
}
// No authentication present - deny access
c.sendInsufficientScopeError(id, toolName, []string{})
return false
}
// Extract scopes from claims
grantedScopes := GetScopesFromClaims(claims)
// Get allowed tools for the granted scopes
allowedTools := GetToolsForScopes(grantedScopes, BuiltinScopes)
// Check if the requested tool is allowed
if !allowedTools[toolName] {
c.sendInsufficientScopeError(id, toolName, grantedScopes)
return false
}
return true
}
// sendInsufficientScopeError sends an error response for insufficient scope
func (c *McpController) sendInsufficientScopeError(id interface{}, toolName string, grantedScopes []string) {
// Find required scope for this tool
requiredScope := GetRequiredScopeForTool(toolName, BuiltinScopes)
errorData := map[string]interface{}{
"tool": toolName,
"granted_scopes": grantedScopes,
}
if requiredScope != "" {
errorData["required_scope"] = requiredScope
}
c.McpResponseError(id, -32001, "insufficient_scope", errorData)
}
// getAllTools returns all available MCP tools
func (c *McpController) getAllTools() []McpTool {
return []McpTool{
{
Name: "get_applications",
Description: "Get all applications for a specific owner",
@@ -344,60 +497,4 @@ func (c *McpController) handleToolsList(req McpRequest) {
},
},
}
result := McpListToolsResult{
Tools: tools,
}
c.McpResponseOk(req.ID, result)
}
func (c *McpController) handleToolsCall(req McpRequest) {
var params McpCallToolParams
err := json.Unmarshal(req.Params, &params)
if err != nil {
c.sendInvalidParamsError(req.ID, err.Error())
return
}
// Route to the appropriate tool handler
switch params.Name {
case "get_applications":
var args GetApplicationsArgs
if err := json.Unmarshal(params.Arguments, &args); err != nil {
c.sendInvalidParamsError(req.ID, err.Error())
return
}
c.handleGetApplicationsTool(req.ID, args)
case "get_application":
var args GetApplicationArgs
if err := json.Unmarshal(params.Arguments, &args); err != nil {
c.sendInvalidParamsError(req.ID, err.Error())
return
}
c.handleGetApplicationTool(req.ID, args)
case "add_application":
var args AddApplicationArgs
if err := json.Unmarshal(params.Arguments, &args); err != nil {
c.sendInvalidParamsError(req.ID, err.Error())
return
}
c.handleAddApplicationTool(req.ID, args)
case "update_application":
var args UpdateApplicationArgs
if err := json.Unmarshal(params.Arguments, &args); err != nil {
c.sendInvalidParamsError(req.ID, err.Error())
return
}
c.handleUpdateApplicationTool(req.ID, args)
case "delete_application":
var args DeleteApplicationArgs
if err := json.Unmarshal(params.Arguments, &args); err != nil {
c.sendInvalidParamsError(req.ID, err.Error())
return
}
c.handleDeleteApplicationTool(req.ID, args)
default:
c.McpResponseError(req.ID, -32602, "Invalid tool name", fmt.Sprintf("Tool '%s' not found", params.Name))
}
}

158
mcp/permission.go Normal file
View File

@@ -0,0 +1,158 @@
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package mcp
import (
"github.com/casdoor/casdoor/object"
)
// BuiltinScopes defines the default scope-to-tool mappings for Casdoor's MCP server
var BuiltinScopes = []*object.ScopeItem{
{
Name: "application:read",
DisplayName: "Read Applications",
Description: "View application list and details",
Tools: []string{"get_applications", "get_application"},
},
{
Name: "application:write",
DisplayName: "Manage Applications",
Description: "Create, update, and delete applications",
Tools: []string{"add_application", "update_application", "delete_application"},
},
{
Name: "user:read",
DisplayName: "Read Users",
Description: "View user list and details",
Tools: []string{"get_users", "get_user"},
},
{
Name: "user:write",
DisplayName: "Manage Users",
Description: "Create, update, and delete users",
Tools: []string{"add_user", "update_user", "delete_user"},
},
{
Name: "organization:read",
DisplayName: "Read Organizations",
Description: "View organization list and details",
Tools: []string{"get_organizations", "get_organization"},
},
{
Name: "organization:write",
DisplayName: "Manage Organizations",
Description: "Create, update, and delete organizations",
Tools: []string{"add_organization", "update_organization", "delete_organization"},
},
{
Name: "permission:read",
DisplayName: "Read Permissions",
Description: "View permission list and details",
Tools: []string{"get_permissions", "get_permission"},
},
{
Name: "permission:write",
DisplayName: "Manage Permissions",
Description: "Create, update, and delete permissions",
Tools: []string{"add_permission", "update_permission", "delete_permission"},
},
{
Name: "role:read",
DisplayName: "Read Roles",
Description: "View role list and details",
Tools: []string{"get_roles", "get_role"},
},
{
Name: "role:write",
DisplayName: "Manage Roles",
Description: "Create, update, and delete roles",
Tools: []string{"add_role", "update_role", "delete_role"},
},
{
Name: "provider:read",
DisplayName: "Read Providers",
Description: "View provider list and details",
Tools: []string{"get_providers", "get_provider"},
},
{
Name: "provider:write",
DisplayName: "Manage Providers",
Description: "Create, update, and delete providers",
Tools: []string{"add_provider", "update_provider", "delete_provider"},
},
{
Name: "token:read",
DisplayName: "Read Tokens",
Description: "View token list and details",
Tools: []string{"get_tokens", "get_token"},
},
{
Name: "token:write",
DisplayName: "Manage Tokens",
Description: "Delete tokens",
Tools: []string{"delete_token"},
},
}
// ConvenienceScopes defines alias scopes that expand to multiple resource scopes
var ConvenienceScopes = map[string][]string{
"read": {"application:read", "user:read", "organization:read", "permission:read", "role:read", "provider:read", "token:read"},
"write": {"application:write", "user:write", "organization:write", "permission:write", "role:write", "provider:write", "token:write"},
"admin": {"application:read", "application:write", "user:read", "user:write", "organization:read", "organization:write", "permission:read", "permission:write", "role:read", "role:write", "provider:read", "provider:write", "token:read", "token:write"},
}
// GetToolsForScopes returns a map of tools allowed by the given scopes
// The grantedScopes are the scopes present in the token
// The registry contains the scope-to-tool mappings (either BuiltinScopes or Application.Scopes)
func GetToolsForScopes(grantedScopes []string, registry []*object.ScopeItem) map[string]bool {
allowed := make(map[string]bool)
// Expand convenience scopes first
expandedScopes := make([]string, 0)
for _, scopeName := range grantedScopes {
if expansion, isConvenience := ConvenienceScopes[scopeName]; isConvenience {
expandedScopes = append(expandedScopes, expansion...)
} else {
expandedScopes = append(expandedScopes, scopeName)
}
}
// Map scopes to tools
for _, scopeName := range expandedScopes {
for _, item := range registry {
if item.Name == scopeName {
for _, tool := range item.Tools {
allowed[tool] = true
}
break
}
}
}
return allowed
}
// GetRequiredScopeForTool returns the first scope that provides access to the given tool
// Returns an empty string if no scope is found for the tool
func GetRequiredScopeForTool(toolName string, registry []*object.ScopeItem) string {
for _, scopeItem := range registry {
for _, tool := range scopeItem.Tools {
if tool == toolName {
return scopeItem.Name
}
}
}
return ""
}

View File

@@ -67,12 +67,22 @@ type JwtItem struct {
Type string `json:"type"`
}
type ScopeItem struct {
Name string `json:"name"`
DisplayName string `json:"displayName"`
Description string `json:"description"`
Tools []string `json:"tools"` // MCP tools allowed by this scope
}
type Application struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Category string `xorm:"varchar(20)" json:"category"`
Type string `xorm:"varchar(20)" json:"type"`
Scopes []*ScopeItem `xorm:"mediumtext" json:"scopes"`
Logo string `xorm:"varchar(200)" json:"logo"`
Title string `xorm:"varchar(100)" json:"title"`
Favicon string `xorm:"varchar(200)" json:"favicon"`
@@ -144,6 +154,13 @@ type Application struct {
FailedSigninLimit int `json:"failedSigninLimit"`
FailedSigninFrozenTime int `json:"failedSigninFrozenTime"`
CodeResendTimeout int `json:"codeResendTimeout"`
// Reverse proxy fields
Domain string `xorm:"varchar(100)" json:"domain"`
OtherDomains []string `xorm:"varchar(1000)" json:"otherDomains"`
UpstreamHost string `xorm:"varchar(100)" json:"upstreamHost"`
SslMode string `xorm:"varchar(100)" json:"sslMode"`
SslCert string `xorm:"varchar(100)" json:"sslCert"`
}
func GetApplicationCount(owner, field, value string) (int64, error) {
@@ -156,6 +173,16 @@ func GetOrganizationApplicationCount(owner, organization, field, value string) (
return session.Where("organization = ? or is_shared = ? ", organization, true).Count(&Application{})
}
func GetGlobalApplications() ([]*Application, error) {
applications := []*Application{}
err := ormer.Engine.Desc("created_time").Find(&applications)
if err != nil {
return applications, err
}
return applications, nil
}
func GetApplications(owner string) ([]*Application, error) {
applications := []*Application{}
err := ormer.Engine.Desc("created_time").Find(&applications, &Application{Owner: owner})
@@ -741,6 +768,12 @@ func UpdateApplication(id string, application *Application, isGlobalAdmin bool,
return false, err
}
if affected != 0 {
if err := RefreshApplicationCache(); err != nil {
fmt.Printf("Failed to refresh application cache after update: %v\n", err)
}
}
return affected != 0, nil
}
@@ -792,6 +825,12 @@ func AddApplication(application *Application) (bool, error) {
return false, nil
}
if affected != 0 {
if err := RefreshApplicationCache(); err != nil {
fmt.Printf("Failed to refresh application cache after add: %v\n", err)
}
}
return affected != 0, nil
}
@@ -801,6 +840,12 @@ func deleteApplication(application *Application) (bool, error) {
return false, err
}
if affected != 0 {
if err := RefreshApplicationCache(); err != nil {
fmt.Printf("Failed to refresh application cache after delete: %v\n", err)
}
}
return affected != 0, nil
}

View File

@@ -0,0 +1,85 @@
// 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.
package object
import (
"fmt"
"strings"
"sync"
"github.com/casdoor/casdoor/proxy"
)
var (
applicationMap = make(map[string]*Application)
applicationMapMutex sync.RWMutex
)
func InitApplicationMap() error {
// Set up the application lookup function for the proxy package
proxy.SetApplicationLookup(func(domain string) *proxy.Application {
app := GetApplicationByDomain(domain)
if app == nil {
return nil
}
return &proxy.Application{
Owner: app.Owner,
Name: app.Name,
UpstreamHost: app.UpstreamHost,
}
})
return refreshApplicationMap()
}
func refreshApplicationMap() error {
applications, err := GetGlobalApplications()
if err != nil {
return fmt.Errorf("failed to get global applications: %w", err)
}
newApplicationMap := make(map[string]*Application)
for _, app := range applications {
if app.Domain != "" {
newApplicationMap[strings.ToLower(app.Domain)] = app
}
for _, domain := range app.OtherDomains {
if domain != "" {
newApplicationMap[strings.ToLower(domain)] = app
}
}
}
applicationMapMutex.Lock()
applicationMap = newApplicationMap
applicationMapMutex.Unlock()
return nil
}
func GetApplicationByDomain(domain string) *Application {
applicationMapMutex.RLock()
defer applicationMapMutex.RUnlock()
domain = strings.ToLower(domain)
if app, ok := applicationMap[domain]; ok {
return app
}
return nil
}
func RefreshApplicationCache() error {
return refreshApplicationMap()
}

View File

@@ -132,6 +132,7 @@ func initBuiltInOrganization() bool {
IsProfilePublic: false,
UseEmailAsUsername: false,
EnableTour: true,
DcrPolicy: "open",
}
_, err = AddOrganization(organization)
if err != nil {
@@ -197,6 +198,9 @@ func initBuiltInApplication() {
Name: "app-built-in",
CreatedTime: util.GetCurrentTime(),
DisplayName: "Casdoor",
Category: "Default",
Type: "All",
Scopes: []*ScopeItem{},
Logo: fmt.Sprintf("%s/img/casdoor-logo_1185x256.png", conf.GetConfigString("staticBaseUrl")),
HomepageUrl: "https://casdoor.org",
Organization: "built-in",

193
object/oauth_dcr.go Normal file
View File

@@ -0,0 +1,193 @@
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"fmt"
"time"
"github.com/casdoor/casdoor/util"
)
// DynamicClientRegistrationRequest represents an RFC 7591 client registration request
type DynamicClientRegistrationRequest struct {
ClientName string `json:"client_name,omitempty"`
RedirectUris []string `json:"redirect_uris,omitempty"`
GrantTypes []string `json:"grant_types,omitempty"`
ResponseTypes []string `json:"response_types,omitempty"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
ApplicationType string `json:"application_type,omitempty"`
Contacts []string `json:"contacts,omitempty"`
LogoUri string `json:"logo_uri,omitempty"`
ClientUri string `json:"client_uri,omitempty"`
PolicyUri string `json:"policy_uri,omitempty"`
TosUri string `json:"tos_uri,omitempty"`
Scope string `json:"scope,omitempty"`
}
// DynamicClientRegistrationResponse represents an RFC 7591 client registration response
type DynamicClientRegistrationResponse struct {
ClientId string `json:"client_id"`
ClientSecret string `json:"client_secret,omitempty"`
ClientIdIssuedAt int64 `json:"client_id_issued_at,omitempty"`
ClientSecretExpiresAt int64 `json:"client_secret_expires_at,omitempty"`
ClientName string `json:"client_name,omitempty"`
RedirectUris []string `json:"redirect_uris,omitempty"`
GrantTypes []string `json:"grant_types,omitempty"`
ResponseTypes []string `json:"response_types,omitempty"`
TokenEndpointAuthMethod string `json:"token_endpoint_auth_method,omitempty"`
ApplicationType string `json:"application_type,omitempty"`
Contacts []string `json:"contacts,omitempty"`
LogoUri string `json:"logo_uri,omitempty"`
ClientUri string `json:"client_uri,omitempty"`
PolicyUri string `json:"policy_uri,omitempty"`
TosUri string `json:"tos_uri,omitempty"`
Scope string `json:"scope,omitempty"`
RegistrationClientUri string `json:"registration_client_uri,omitempty"`
RegistrationAccessToken string `json:"registration_access_token,omitempty"`
}
// DcrError represents an RFC 7591 error response
type DcrError struct {
Error string `json:"error"`
ErrorDescription string `json:"error_description,omitempty"`
}
// RegisterDynamicClient creates a new application based on DCR request
func RegisterDynamicClient(req *DynamicClientRegistrationRequest, organization string) (*DynamicClientRegistrationResponse, *DcrError, error) {
// Validate organization exists and has DCR enabled
org, err := GetOrganization(util.GetId("admin", organization))
if err != nil {
return nil, nil, err
}
if org == nil {
return nil, &DcrError{
Error: "invalid_client_metadata",
ErrorDescription: "organization not found",
}, nil
}
// Check if DCR is enabled for this organization
if org.DcrPolicy == "" || org.DcrPolicy == "disabled" {
return nil, &DcrError{
Error: "invalid_client_metadata",
ErrorDescription: "dynamic client registration is disabled for this organization",
}, nil
}
// Validate required fields
if len(req.RedirectUris) == 0 {
return nil, &DcrError{
Error: "invalid_redirect_uri",
ErrorDescription: "redirect_uris is required and must contain at least one URI",
}, nil
}
// Set defaults
if req.ClientName == "" {
clientIdPrefix := util.GenerateClientId()
if len(clientIdPrefix) > 8 {
clientIdPrefix = clientIdPrefix[:8]
}
req.ClientName = fmt.Sprintf("DCR Client %s", clientIdPrefix)
}
if len(req.GrantTypes) == 0 {
req.GrantTypes = []string{"authorization_code"}
}
if len(req.ResponseTypes) == 0 {
req.ResponseTypes = []string{"code"}
}
if req.TokenEndpointAuthMethod == "" {
req.TokenEndpointAuthMethod = "client_secret_basic"
}
if req.ApplicationType == "" {
req.ApplicationType = "web"
}
// Generate unique application name
randomName := util.GetRandomName()
appName := fmt.Sprintf("dcr_%s", randomName)
// Create Application object
// Note: DCR applications are created under "admin" owner by default
// This can be made configurable in future versions
clientId := util.GenerateClientId()
clientSecret := util.GenerateClientSecret()
createdTime := util.GetCurrentTime()
application := &Application{
Owner: "admin",
Name: appName,
Organization: organization,
CreatedTime: createdTime,
DisplayName: req.ClientName,
Category: "Agent",
Type: "MCP",
Scopes: []*ScopeItem{},
Logo: req.LogoUri,
HomepageUrl: req.ClientUri,
ClientId: clientId,
ClientSecret: clientSecret,
RedirectUris: req.RedirectUris,
GrantTypes: req.GrantTypes,
EnablePassword: false,
EnableSignUp: false,
DisableSignin: false,
EnableSigninSession: false,
EnableCodeSignin: true,
EnableAutoSignin: false,
TokenFormat: "JWT",
ExpireInHours: 168,
RefreshExpireInHours: 168,
CookieExpireInHours: 720,
FormOffset: 2,
Tags: []string{"dcr"},
TermsOfUse: req.TosUri,
}
// Add the application
affected, err := AddApplication(application)
if err != nil {
return nil, nil, err
}
if !affected {
return nil, &DcrError{
Error: "invalid_client_metadata",
ErrorDescription: "failed to create client application",
}, nil
}
// Build response
response := &DynamicClientRegistrationResponse{
ClientId: clientId,
ClientSecret: clientSecret,
ClientIdIssuedAt: time.Now().Unix(),
ClientSecretExpiresAt: 0, // Never expires
ClientName: req.ClientName,
RedirectUris: req.RedirectUris,
GrantTypes: req.GrantTypes,
ResponseTypes: req.ResponseTypes,
TokenEndpointAuthMethod: req.TokenEndpointAuthMethod,
ApplicationType: req.ApplicationType,
Contacts: req.Contacts,
LogoUri: req.LogoUri,
ClientUri: req.ClientUri,
PolicyUri: req.PolicyUri,
TosUri: req.TosUri,
Scope: req.Scope,
}
return response, nil, nil
}

View File

@@ -92,6 +92,8 @@ type Organization struct {
AccountMenu string `xorm:"varchar(20)" json:"accountMenu"`
AccountItems []*AccountItem `xorm:"mediumtext" json:"accountItems"`
DcrPolicy string `xorm:"varchar(100)" json:"dcrPolicy"`
OrgBalance float64 `json:"orgBalance"`
UserBalance float64 `json:"userBalance"`
BalanceCredit float64 `json:"balanceCredit"`

View File

@@ -303,7 +303,7 @@ func NotifyPayment(body []byte, owner string, paymentName string, lang string) (
order.Message = "Payment successful"
order.UpdateTime = util.GetCurrentTime()
} else if payment.State == pp.PaymentStateError {
order.State = "PaymentFailed"
order.State = "Failed"
order.Message = payment.Message
order.UpdateTime = util.GetCurrentTime()
} else if payment.State == pp.PaymentStateCanceled {

View File

@@ -43,6 +43,7 @@ type Token struct {
CodeChallenge string `xorm:"varchar(100)" json:"codeChallenge"`
CodeIsUsed bool `json:"codeIsUsed"`
CodeExpireIn int64 `json:"codeExpireIn"`
Resource string `xorm:"varchar(255)" json:"resource"` // RFC 8707 Resource Indicator
}
func GetTokenCount(owner, organization, field, value string) (int64, error) {

View File

@@ -509,7 +509,7 @@ func refineUser(user *User) *User {
return user
}
func generateJwtToken(application *Application, user *User, provider string, signinMethod string, nonce string, scope string, host string) (string, string, string, error) {
func generateJwtToken(application *Application, user *User, provider string, signinMethod string, nonce string, scope string, resource string, host string) (string, string, string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(time.Duration(application.ExpireInHours * float64(time.Hour)))
refreshExpireTime := nowTime.Add(time.Duration(application.RefreshExpireInHours * float64(time.Hour)))
@@ -553,7 +553,10 @@ func generateJwtToken(application *Application, user *User, provider string, sig
},
}
if application.IsShared {
// RFC 8707: Use resource as audience when provided
if resource != "" {
claims.Audience = []string{resource}
} else if application.IsShared {
claims.Audience = []string{application.ClientId + "-org-" + user.Owner}
}

View File

@@ -18,6 +18,7 @@ import (
"crypto/sha256"
"encoding/base64"
"fmt"
"net/url"
"strings"
"sync"
"time"
@@ -92,6 +93,26 @@ type DeviceAuthResponse struct {
Interval int `json:"interval"`
}
// validateResourceURI validates that the resource parameter is a valid absolute URI
// according to RFC 8707 Section 2
func validateResourceURI(resource string) error {
if resource == "" {
return nil // empty resource is allowed (backward compatibility)
}
parsedURL, err := url.Parse(resource)
if err != nil {
return fmt.Errorf("resource must be a valid URI")
}
// RFC 8707: The resource parameter must be an absolute URI
if !parsedURL.IsAbs() {
return fmt.Errorf("resource must be an absolute URI")
}
return nil
}
func ExpireTokenByAccessToken(accessToken string) (bool, *Application, *Token, error) {
token, err := GetTokenByAccessToken(accessToken)
if err != nil {
@@ -138,7 +159,7 @@ func CheckOAuthLogin(clientId string, responseType string, redirectUri string, s
return "", application, nil
}
func GetOAuthCode(userId string, clientId string, provider string, signinMethod string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string, host string, lang string) (*Code, error) {
func GetOAuthCode(userId string, clientId string, provider string, signinMethod string, responseType string, redirectUri string, scope string, state string, nonce string, challenge string, resource string, host string, lang string) (*Code, error) {
user, err := GetUser(userId)
if err != nil {
return nil, err
@@ -169,11 +190,19 @@ func GetOAuthCode(userId string, clientId string, provider string, signinMethod
}, nil
}
// Validate resource parameter (RFC 8707)
if err := validateResourceURI(resource); err != nil {
return &Code{
Message: err.Error(),
Code: "",
}, nil
}
err = ExtendUserWithRolesAndPermissions(user)
if err != nil {
return nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, provider, signinMethod, nonce, scope, host)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, provider, signinMethod, nonce, scope, resource, host)
if err != nil {
return nil, err
}
@@ -198,6 +227,7 @@ func GetOAuthCode(userId string, clientId string, provider string, signinMethod
CodeChallenge: challenge,
CodeIsUsed: false,
CodeExpireIn: time.Now().Add(time.Minute * 5).Unix(),
Resource: resource,
}
_, err = AddToken(token)
if err != nil {
@@ -210,7 +240,7 @@ func GetOAuthCode(userId string, clientId string, provider string, signinMethod
}, nil
}
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, nonce string, username string, password string, host string, refreshToken string, tag string, avatar string, lang string, subjectToken string, subjectTokenType string, audience string) (interface{}, error) {
func GetOAuthToken(grantType string, clientId string, clientSecret string, code string, verifier string, scope string, nonce string, username string, password string, host string, refreshToken string, tag string, avatar string, lang string, subjectToken string, subjectTokenType string, audience string, resource string) (interface{}, error) {
application, err := GetApplicationByClientId(clientId)
if err != nil {
return nil, err
@@ -236,7 +266,7 @@ func GetOAuthToken(grantType string, clientId string, clientSecret string, code
var tokenError *TokenError
switch grantType {
case "authorization_code": // Authorization Code Grant
token, tokenError, err = GetAuthorizationCodeToken(application, clientSecret, code, verifier)
token, tokenError, err = GetAuthorizationCodeToken(application, clientSecret, code, verifier, resource)
case "password": // Resource Owner Password Credentials Grant
token, tokenError, err = GetPasswordToken(application, username, password, scope, host)
case "client_credentials": // Client Credentials Grant
@@ -391,7 +421,7 @@ func RefreshToken(grantType string, refreshToken string, scope string, clientId
return nil, err
}
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", "", "", scope, host)
newAccessToken, newRefreshToken, tokenName, err := generateJwtToken(application, user, "", "", "", scope, "", host)
if err != nil {
return &TokenError{
Error: EndpointError,
@@ -545,7 +575,7 @@ func createGuestUserToken(application *Application, clientSecret string, verifie
}
// Generate JWT token
accessToken, refreshToken, tokenName, err := generateJwtToken(application, guestUser, "", "", "", "", "")
accessToken, refreshToken, tokenName, err := generateJwtToken(application, guestUser, "", "", "", "", "", "")
if err != nil {
return nil, &TokenError{
Error: EndpointError,
@@ -595,7 +625,7 @@ func generateGuestUsername() string {
// GetAuthorizationCodeToken
// Authorization code flow
func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string) (*Token, *TokenError, error) {
func GetAuthorizationCodeToken(application *Application, clientSecret string, code string, verifier string, resource string) (*Token, *TokenError, error) {
if code == "" {
return nil, &TokenError{
Error: InvalidRequest,
@@ -663,6 +693,14 @@ func GetAuthorizationCodeToken(application *Application, clientSecret string, co
}, nil
}
// RFC 8707: Validate resource parameter matches the one in the authorization request
if resource != token.Resource {
return nil, &TokenError{
Error: InvalidGrant,
ErrorDescription: fmt.Sprintf("resource parameter does not match authorization request, expected: [%s], got: [%s]", token.Resource, resource),
}, nil
}
nowUnix := time.Now().Unix()
if nowUnix > token.CodeExpireIn {
// code must be used within 5 minutes
@@ -719,7 +757,7 @@ func GetPasswordToken(application *Application, username string, password string
return nil, nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", "", scope, host)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", "", scope, "", host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,
@@ -765,7 +803,7 @@ func GetClientCredentialsToken(application *Application, clientSecret string, sc
Type: "application",
}
accessToken, _, tokenName, err := generateJwtToken(application, nullUser, "", "", "", scope, host)
accessToken, _, tokenName, err := generateJwtToken(application, nullUser, "", "", "", scope, "", host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,
@@ -829,7 +867,7 @@ func GetTokenByUser(application *Application, user *User, scope string, nonce st
return nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", nonce, scope, host)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", nonce, scope, "", host)
if err != nil {
return nil, err
}
@@ -936,7 +974,7 @@ func GetWechatMiniProgramToken(application *Application, code string, host strin
return nil, nil, err
}
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", "", "", host)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", "", "", "", host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,
@@ -1110,7 +1148,7 @@ func GetTokenExchangeToken(application *Application, clientSecret string, subjec
}
// Generate new JWT token
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", "", scope, host)
accessToken, refreshToken, tokenName, err := generateJwtToken(application, user, "", "", "", scope, "", host)
if err != nil {
return nil, &TokenError{
Error: EndpointError,

View File

@@ -32,6 +32,7 @@ type OidcDiscovery struct {
TokenEndpoint string `json:"token_endpoint"`
UserinfoEndpoint string `json:"userinfo_endpoint"`
DeviceAuthorizationEndpoint string `json:"device_authorization_endpoint"`
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
JwksUri string `json:"jwks_uri"`
IntrospectionEndpoint string `json:"introspection_endpoint"`
ResponseTypesSupported []string `json:"response_types_supported"`
@@ -124,6 +125,23 @@ func GetOidcDiscovery(host string, applicationName string) OidcDiscovery {
jwksUri = fmt.Sprintf("%s/.well-known/jwks", originBackend)
}
// Default OIDC scopes
scopes := []string{"openid", "email", "profile", "address", "phone", "offline_access"}
// Merge application-specific custom scopes if application is provided
if applicationName != "" {
applicationId := util.GetId("admin", applicationName)
application, err := GetApplication(applicationId)
if err == nil && application != nil && len(application.Scopes) > 0 {
for _, scope := range application.Scopes {
// Add custom scope names to the scopes list
if scope.Name != "" {
scopes = append(scopes, scope.Name)
}
}
}
}
// Examples:
// https://login.okta.com/.well-known/openid-configuration
// https://auth0.auth0.com/.well-known/openid-configuration
@@ -135,6 +153,7 @@ func GetOidcDiscovery(host string, applicationName string) OidcDiscovery {
TokenEndpoint: fmt.Sprintf("%s/api/login/oauth/access_token", originBackend),
UserinfoEndpoint: fmt.Sprintf("%s/api/userinfo", originBackend),
DeviceAuthorizationEndpoint: fmt.Sprintf("%s/api/device-auth", originBackend),
RegistrationEndpoint: fmt.Sprintf("%s/api/oauth/register", originBackend),
JwksUri: jwksUri,
IntrospectionEndpoint: fmt.Sprintf("%s/api/login/oauth/introspect", originBackend),
ResponseTypesSupported: []string{"code", "token", "id_token", "code token", "code id_token", "token id_token", "code token id_token", "none"},
@@ -142,7 +161,7 @@ func GetOidcDiscovery(host string, applicationName string) OidcDiscovery {
GrantTypesSupported: []string{"authorization_code", "implicit", "password", "client_credentials", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code", "urn:ietf:params:oauth:grant-type:token-exchange"},
SubjectTypesSupported: []string{"public"},
IdTokenSigningAlgValuesSupported: []string{"RS256", "RS512", "ES256", "ES384", "ES512"},
ScopesSupported: []string{"openid", "email", "profile", "address", "phone", "offline_access"},
ScopesSupported: scopes,
CodeChallengeMethodsSupported: []string{"S256"},
ClaimsSupported: []string{"iss", "ver", "sub", "aud", "iat", "exp", "id", "type", "displayName", "avatar", "permanentAvatar", "email", "phone", "location", "affiliation", "title", "homepage", "bio", "tag", "region", "language", "score", "ranking", "isOnline", "isAdmin", "isForbidden", "signupApplication", "ldap"},
RequestParameterSupported: true,

View File

@@ -39,7 +39,7 @@ func (pp *DummyPaymentProvider) Pay(r *PayReq) (*PayResp, error) {
orderInfo := DummyOrderInfo{
Price: r.Price,
Currency: r.Currency,
ProductDisplayName: r.ProductDisplayName,
ProductDisplayName: "",
}
orderInfoBytes, err := json.Marshal(orderInfo)
if err != nil {

229
proxy/reverse_proxy.go Normal file
View File

@@ -0,0 +1,229 @@
// 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.
package proxy
import (
"fmt"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"github.com/beego/beego/v2/core/logs"
"github.com/casdoor/casdoor/conf"
)
// Application represents a simplified application structure for reverse proxy
type Application struct {
Owner string
Name string
UpstreamHost string
}
// ApplicationLookupFunc is a function type for looking up applications by domain
type ApplicationLookupFunc func(domain string) *Application
var applicationLookup ApplicationLookupFunc
// SetApplicationLookup sets the function to use for looking up applications by domain
func SetApplicationLookup(lookupFunc ApplicationLookupFunc) {
applicationLookup = lookupFunc
}
// getDomainWithoutPort removes the port from a domain string
func getDomainWithoutPort(domain string) string {
if !strings.Contains(domain, ":") {
return domain
}
tokens := strings.SplitN(domain, ":", 2)
if len(tokens) > 1 {
return tokens[0]
}
return domain
}
// forwardHandler creates and configures a reverse proxy for the given target URL
func forwardHandler(targetUrl string, writer http.ResponseWriter, request *http.Request) {
target, err := url.Parse(targetUrl)
if err != nil {
logs.Error("Failed to parse target URL %s: %v", targetUrl, err)
http.Error(writer, "Internal Server Error", http.StatusInternalServerError)
return
}
proxy := httputil.NewSingleHostReverseProxy(target)
// Configure the Director to set proper headers
proxy.Director = func(r *http.Request) {
r.URL.Scheme = target.Scheme
r.URL.Host = target.Host
r.Host = target.Host
// Set X-Real-IP and X-Forwarded-For headers
if clientIP, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
r.Header.Set("X-Forwarded-For", fmt.Sprintf("%s, %s", xff, clientIP))
} else {
r.Header.Set("X-Forwarded-For", clientIP)
}
r.Header.Set("X-Real-IP", clientIP)
}
// Set X-Forwarded-Proto header
if r.TLS != nil {
r.Header.Set("X-Forwarded-Proto", "https")
} else {
r.Header.Set("X-Forwarded-Proto", "http")
}
// Set X-Forwarded-Host header
r.Header.Set("X-Forwarded-Host", request.Host)
}
// Handle ModifyResponse for security enhancements
proxy.ModifyResponse = func(resp *http.Response) error {
// Add Secure flag to all Set-Cookie headers in HTTPS responses
if request.TLS != nil {
// Add HSTS header for HTTPS responses if not already set by backend
if resp.Header.Get("Strict-Transport-Security") == "" {
resp.Header.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
}
cookies := resp.Header["Set-Cookie"]
if len(cookies) > 0 {
// Clear existing Set-Cookie headers
resp.Header.Del("Set-Cookie")
// Add them back with Secure flag if not already present
for _, cookie := range cookies {
// Check if Secure attribute is already present (case-insensitive)
cookieLower := strings.ToLower(cookie)
hasSecure := strings.Contains(cookieLower, ";secure;") ||
strings.Contains(cookieLower, "; secure;") ||
strings.HasSuffix(cookieLower, ";secure") ||
strings.HasSuffix(cookieLower, "; secure")
if !hasSecure {
cookie = cookie + "; Secure"
}
resp.Header.Add("Set-Cookie", cookie)
}
}
}
return nil
}
proxy.ServeHTTP(writer, request)
}
// HandleReverseProxy handles incoming requests and forwards them to the appropriate upstream
func HandleReverseProxy(w http.ResponseWriter, r *http.Request) {
domain := getDomainWithoutPort(r.Host)
if applicationLookup == nil {
logs.Error("Application lookup function not set")
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
// Lookup the application by domain
app := applicationLookup(domain)
if app == nil {
logs.Info("No application found for domain: %s", domain)
http.Error(w, "Not Found", http.StatusNotFound)
return
}
// Check if the application has an upstream host configured
if app.UpstreamHost == "" {
logs.Warn("Application %s/%s has no upstream host configured", app.Owner, app.Name)
http.Error(w, "Not Found", http.StatusNotFound)
return
}
// Build the target URL - just use the upstream host, the actual path/query will be set by the proxy Director
targetUrl := app.UpstreamHost
if !strings.HasPrefix(targetUrl, "http://") && !strings.HasPrefix(targetUrl, "https://") {
targetUrl = "http://" + targetUrl
}
logs.Debug("Forwarding request from %s%s to %s", r.Host, r.RequestURI, targetUrl)
forwardHandler(targetUrl, w, r)
}
// StartProxyServer starts the HTTP and HTTPS proxy servers based on configuration
func StartProxyServer() {
proxyHttpPort := conf.GetConfigString("proxyHttpPort")
proxyHttpsPort := conf.GetConfigString("proxyHttpsPort")
if proxyHttpPort == "" && proxyHttpsPort == "" {
logs.Info("Reverse proxy not enabled (proxyHttpPort and proxyHttpsPort are empty)")
return
}
serverMux := http.NewServeMux()
serverMux.HandleFunc("/", HandleReverseProxy)
// Start HTTP proxy if configured
if proxyHttpPort != "" {
go func() {
addr := fmt.Sprintf(":%s", proxyHttpPort)
logs.Info("Starting reverse proxy HTTP server on %s", addr)
err := http.ListenAndServe(addr, serverMux)
if err != nil {
logs.Error("Failed to start HTTP proxy server: %v", err)
}
}()
}
// Start HTTPS proxy if configured
if proxyHttpsPort != "" {
go func() {
addr := fmt.Sprintf(":%s", proxyHttpsPort)
// For now, HTTPS will need certificate configuration
// This can be enhanced later to use Application's SslCert field
logs.Info("HTTPS proxy server on %s requires certificate configuration - not implemented yet", addr)
// When implemented, use code like:
// server := &http.Server{
// Handler: serverMux,
// Addr: addr,
// TLSConfig: &tls.Config{
// MinVersion: tls.VersionTLS12,
// PreferServerCipherSuites: true,
// CipherSuites: []uint16{
// tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
// tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
// tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
// tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
// tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305,
// tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,
// },
// CurvePreferences: []tls.CurveID{
// tls.X25519,
// tls.CurveP256,
// tls.CurveP384,
// },
// },
// }
// err := server.ListenAndServeTLS("", "")
// if err != nil {
// logs.Error("Failed to start HTTPS proxy server: %v", err)
// }
}()
}
}

View File

@@ -0,0 +1,210 @@
// 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.
package proxy
import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
)
// TestReverseProxyIntegration tests the reverse proxy with a real backend server
func TestReverseProxyIntegration(t *testing.T) {
// Create a test backend server that echoes the request path
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Verify headers
headers := []string{
"X-Forwarded-For",
"X-Forwarded-Proto",
"X-Real-IP",
"X-Forwarded-Host",
}
for _, header := range headers {
if r.Header.Get(header) == "" {
t.Errorf("Expected header %s to be set", header)
}
}
// Echo the path and query
w.Header().Set("Content-Type", "text/plain")
w.WriteHeader(http.StatusOK)
w.Write([]byte("Path: " + r.URL.Path + "\n"))
w.Write([]byte("Query: " + r.URL.RawQuery + "\n"))
w.Write([]byte("Host: " + r.Host + "\n"))
}))
defer backend.Close()
// Set up the application lookup
SetApplicationLookup(func(domain string) *Application {
if domain == "myapp.example.com" {
return &Application{
Owner: "test-owner",
Name: "my-app",
UpstreamHost: backend.URL,
}
}
return nil
})
// Test various request paths
tests := []struct {
name string
path string
query string
expected string
}{
{"Simple path", "/", "", "Path: /\n"},
{"Path with segments", "/api/v1/users", "", "Path: /api/v1/users\n"},
{"Path with query", "/search", "q=test&limit=10", "Query: q=test&limit=10\n"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
url := "http://myapp.example.com" + tt.path
if tt.query != "" {
url += "?" + tt.query
}
req := httptest.NewRequest("GET", url, nil)
req.Host = "myapp.example.com"
w := httptest.NewRecorder()
HandleReverseProxy(w, req)
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}
body, _ := io.ReadAll(w.Body)
bodyStr := string(body)
if !strings.Contains(bodyStr, tt.expected) {
t.Errorf("Expected response to contain %q, got %q", tt.expected, bodyStr)
}
})
}
}
// TestReverseProxyWebSocket tests that WebSocket upgrade headers are preserved
func TestReverseProxyWebSocket(t *testing.T) {
// Note: WebSocket upgrade through httptest.ResponseRecorder has limitations
// This test verifies that WebSocket headers are passed through, but
// full WebSocket functionality would need integration testing with real servers
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Verify WebSocket headers are present
if r.Header.Get("Upgrade") == "websocket" &&
r.Header.Get("Connection") != "" &&
r.Header.Get("Sec-WebSocket-Version") != "" &&
r.Header.Get("Sec-WebSocket-Key") != "" {
// Headers are present - this is what we're testing
w.WriteHeader(http.StatusOK)
w.Write([]byte("WebSocket headers received"))
} else {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("Missing WebSocket headers"))
}
}))
defer backend.Close()
SetApplicationLookup(func(domain string) *Application {
if domain == "ws.example.com" {
return &Application{
Owner: "test-owner",
Name: "ws-app",
UpstreamHost: backend.URL,
}
}
return nil
})
req := httptest.NewRequest("GET", "http://ws.example.com/ws", nil)
req.Host = "ws.example.com"
req.Header.Set("Upgrade", "websocket")
req.Header.Set("Connection", "Upgrade")
req.Header.Set("Sec-WebSocket-Version", "13")
req.Header.Set("Sec-WebSocket-Key", "dGhlIHNhbXBsZSBub25jZQ==")
w := httptest.NewRecorder()
HandleReverseProxy(w, req)
body, _ := io.ReadAll(w.Body)
bodyStr := string(body)
// We expect the headers to be passed through to the backend
if !strings.Contains(bodyStr, "WebSocket headers received") {
t.Errorf("WebSocket headers were not properly forwarded. Got: %s", bodyStr)
}
}
// TestReverseProxyUpstreamHostVariations tests different UpstreamHost formats
func TestReverseProxyUpstreamHostVariations(t *testing.T) {
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
}))
defer backend.Close()
// Parse backend URL to get host
backendURL, err := url.Parse(backend.URL)
if err != nil {
t.Fatalf("Failed to parse backend URL: %v", err)
}
tests := []struct {
name string
upstreamHost string
shouldWork bool
}{
{"Full URL", backend.URL, true},
{"Host only", backendURL.Host, true},
{"Empty", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
SetApplicationLookup(func(domain string) *Application {
if domain == "test.example.com" {
return &Application{
Owner: "test-owner",
Name: "test-app",
UpstreamHost: tt.upstreamHost,
}
}
return nil
})
req := httptest.NewRequest("GET", "http://test.example.com/", nil)
req.Host = "test.example.com"
w := httptest.NewRecorder()
HandleReverseProxy(w, req)
if tt.shouldWork {
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}
} else {
if w.Code == http.StatusOK {
t.Errorf("Expected failure, but got status 200")
}
}
})
}
}

148
proxy/reverse_proxy_test.go Normal file
View File

@@ -0,0 +1,148 @@
// 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.
package proxy
import (
"fmt"
"net/http"
"net/http/httptest"
"testing"
)
func TestGetDomainWithoutPort(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"example.com", "example.com"},
{"example.com:8080", "example.com"},
{"localhost:3000", "localhost"},
{"subdomain.example.com:443", "subdomain.example.com"},
}
for _, test := range tests {
result := getDomainWithoutPort(test.input)
if result != test.expected {
t.Errorf("getDomainWithoutPort(%s) = %s; want %s", test.input, result, test.expected)
}
}
}
func TestHandleReverseProxy(t *testing.T) {
// Create a test backend server
backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check that headers are set correctly
if r.Header.Get("X-Forwarded-For") == "" {
t.Error("X-Forwarded-For header not set")
}
if r.Header.Get("X-Forwarded-Proto") == "" {
t.Error("X-Forwarded-Proto header not set")
}
if r.Header.Get("X-Real-IP") == "" {
t.Error("X-Real-IP header not set")
}
if r.Header.Get("X-Forwarded-Host") == "" {
t.Error("X-Forwarded-Host header not set")
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Backend response")
}))
defer backend.Close()
// Set up a mock application lookup function
SetApplicationLookup(func(domain string) *Application {
if domain == "test.example.com" {
return &Application{
Owner: "test-owner",
Name: "test-app",
UpstreamHost: backend.URL,
}
}
return nil
})
// Test successful proxy
req := httptest.NewRequest("GET", "http://test.example.com/path", nil)
req.Host = "test.example.com"
w := httptest.NewRecorder()
HandleReverseProxy(w, req)
if w.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", w.Code)
}
// Test domain not found
req = httptest.NewRequest("GET", "http://unknown.example.com/path", nil)
req.Host = "unknown.example.com"
w = httptest.NewRecorder()
HandleReverseProxy(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("Expected status 404 for unknown domain, got %d", w.Code)
}
// Test application without upstream host
SetApplicationLookup(func(domain string) *Application {
if domain == "no-upstream.example.com" {
return &Application{
Owner: "test-owner",
Name: "test-app-no-upstream",
UpstreamHost: "",
}
}
return nil
})
req = httptest.NewRequest("GET", "http://no-upstream.example.com/path", nil)
req.Host = "no-upstream.example.com"
w = httptest.NewRecorder()
HandleReverseProxy(w, req)
if w.Code != http.StatusNotFound {
t.Errorf("Expected status 404 for app without upstream, got %d", w.Code)
}
}
func TestApplicationLookup(t *testing.T) {
// Test setting and using the application lookup function
called := false
SetApplicationLookup(func(domain string) *Application {
called = true
return &Application{
Owner: "test",
Name: "app",
UpstreamHost: "http://localhost:8080",
}
})
if applicationLookup == nil {
t.Error("applicationLookup should not be nil after SetApplicationLookup")
}
app := applicationLookup("test.com")
if !called {
t.Error("applicationLookup function was not called")
}
if app == nil {
t.Error("applicationLookup should return non-nil application")
}
if app.Owner != "test" {
t.Errorf("Expected owner 'test', got '%s'", app.Owner)
}
}

View File

@@ -298,6 +298,7 @@ func InitAPI() {
web.Router("/api/login/oauth/access_token", &controllers.ApiController{}, "POST:GetOAuthToken")
web.Router("/api/login/oauth/refresh_token", &controllers.ApiController{}, "POST:RefreshToken")
web.Router("/api/login/oauth/introspect", &controllers.ApiController{}, "POST:IntrospectToken")
web.Router("/api/oauth/register", &controllers.ApiController{}, "POST:DynamicClientRegister")
web.Router("/api/get-records", &controllers.ApiController{}, "GET:GetRecords")
web.Router("/api/get-records-filter", &controllers.ApiController{}, "POST:GetRecordsByFilter")

View File

@@ -89,7 +89,7 @@ func fastAutoSignin(ctx *context.Context) (string, error) {
return "", nil
}
code, err := object.GetOAuthCode(userId, clientId, "", "autoSignin", responseType, redirectUri, scope, state, nonce, codeChallenge, ctx.Request.Host, getAcceptLanguage(ctx))
code, err := object.GetOAuthCode(userId, clientId, "", "autoSignin", responseType, redirectUri, scope, state, nonce, codeChallenge, "", ctx.Request.Host, getAcceptLanguage(ctx))
if err != nil {
return "", err
} else if code.Message != "" {

View File

@@ -158,7 +158,7 @@ class AdapterEditPage extends React.Component {
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Type"), i18next.t("cert:Type - Tooltip"))} :
{Setting.getLabel(i18next.t("general:Type"), i18next.t("general:Type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} disabled={Setting.builtInObject(this.state.adapter)} style={{width: "100%"}} value={this.state.adapter.type} onChange={(value => {

View File

@@ -48,6 +48,7 @@ import ProviderTable from "./table/ProviderTable";
import SigninMethodTable from "./table/SigninMethodTable";
import SignupTable from "./table/SignupTable";
import SamlAttributeTable from "./table/SamlAttributeTable";
import ScopeTable from "./table/ScopeTable";
import PromptPage from "./auth/PromptPage";
import copy from "copy-to-clipboard";
import ThemeEditor from "./common/theme/ThemeEditor";
@@ -307,6 +308,61 @@ class ApplicationEditPage extends React.Component {
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("general:Category"), i18next.t("general:Category - Tooltip"))} :
</Col>
<Col span={21} >
<Select
virtual={false}
style={{width: "100%"}}
value={this.state.application.category}
onChange={(value) => {
this.updateApplicationField("category", value);
if (value === "Agent") {
this.updateApplicationField("type", "MCP");
} else {
this.updateApplicationField("type", "All");
}
}}
>
<Option value="Default">Default</Option>
<Option value="Agent">Agent</Option>
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("general:Type"), i18next.t("general:Type - Tooltip"))} :
</Col>
<Col span={21} >
<Select
virtual={false}
style={{width: "100%"}}
value={this.state.application.type}
onChange={(value) => {
this.updateApplicationField("type", value);
}}
>
{
(this.state.application.category === "Agent") ? (
<>
<Option value="MCP">MCP</Option>
<Option value="A2A">A2A</Option>
</>
) : (
<>
<Option value="All">All</Option>
<Option value="OIDC">OIDC</Option>
<Option value="OAuth">OAuth</Option>
<Option value="SAML">SAML</Option>
<Option value="CAS">CAS</Option>
</>
)
}
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("general:Is shared"), i18next.t("general:Is shared - Tooltip"))} :
@@ -516,6 +572,22 @@ class ApplicationEditPage extends React.Component {
</Select>
</Col>
</Row>
{
(this.state.application.category === "Agent") ? (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("general:Scopes"), i18next.t("general:Scopes - Tooltip"))} :
</Col>
<Col span={21} >
<ScopeTable
title={i18next.t("general:Scopes")}
table={this.state.application.scopes}
onUpdateTable={(value) => {this.updateApplicationField("scopes", value);}}
/>
</Col>
</Row>
) : null
}
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("application:Token format"), i18next.t("application:Token format - Tooltip"))} :
@@ -1301,6 +1373,68 @@ class ApplicationEditPage extends React.Component {
</Col>
</Row>
</React.Fragment>
)}
{this.state.activeMenuKey === "reverse-proxy" && (
<React.Fragment>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("provider:Domain"), i18next.t("provider:Domain - Tooltip"))} :
</Col>
<Col span={21} >
<Input value={this.state.application.domain} placeholder="e.g., blog.example.com" onChange={e => {
this.updateApplicationField("domain", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("application:Other domains"), i18next.t("application:Other domains - Tooltip"))} :
</Col>
<Col span={21} >
<UrlTable
title={i18next.t("application:Other domains")}
table={this.state.application.otherDomains}
onUpdateTable={(value) => {this.updateApplicationField("otherDomains", value);}}
/>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("application:Upstream host"), i18next.t("application:Upstream host - Tooltip"))} :
</Col>
<Col span={21} >
<Input value={this.state.application.upstreamHost} placeholder="e.g., localhost:8080 or 192.168.1.100:3000" onChange={e => {
this.updateApplicationField("upstreamHost", e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("provider:SSL mode"), i18next.t("provider:SSL mode - Tooltip"))} :
</Col>
<Col span={21} >
<Select virtual={false} style={{width: "100%"}} value={this.state.application.sslMode} onChange={(value => {this.updateApplicationField("sslMode", value);})}>
<Option value="">{i18next.t("general:None")}</Option>
<Option value="HTTP">HTTP</Option>
<Option value="HTTPS and HTTP">HTTPS and HTTP</Option>
<Option value="HTTPS Only">HTTPS Only</Option>
</Select>
</Col>
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 3}>
{Setting.getLabel(i18next.t("application:SSL cert"), i18next.t("application:SSL cert - Tooltip"))} :
</Col>
<Col span={21} >
<Select virtual={false} style={{width: "100%"}} value={this.state.application.sslCert} onChange={(value => {this.updateApplicationField("sslCert", value);})}>
<Option value="">{i18next.t("general:None")}</Option>
{
this.state.certs.map((cert, index) => <Option key={index} value={cert.name}>{cert.name}</Option>)
}
</Select>
</Col>
</Row>
</React.Fragment>
)}</>;
}
@@ -1333,6 +1467,7 @@ class ApplicationEditPage extends React.Component {
{label: i18next.t("application:Providers"), key: "providers"},
{label: i18next.t("application:UI Customization"), key: "ui-customization"},
{label: i18next.t("application:Security"), key: "security"},
{label: i18next.t("application:Reverse Proxy"), key: "reverse-proxy"},
]}
/>
</Header>
@@ -1356,6 +1491,7 @@ class ApplicationEditPage extends React.Component {
<Menu.Item key="providers">{i18next.t("application:Providers")}</Menu.Item>
<Menu.Item key="ui-customization">{i18next.t("application:UI Customization")}</Menu.Item>
<Menu.Item key="security">{i18next.t("application:Security")}</Menu.Item>
<Menu.Item key="reverse-proxy">{i18next.t("application:Reverse Proxy")}</Menu.Item>
</Menu>
</Sider>) : null
}

View File

@@ -38,6 +38,9 @@ class ApplicationListPage extends BaseListPage {
organization: organizationName,
createdTime: moment().format(),
displayName: `New Application - ${randomName}`,
category: "Default",
type: "All",
scopes: [],
logo: `${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`,
enablePassword: true,
enableSignUp: true,
@@ -179,6 +182,36 @@ class ApplicationListPage extends BaseListPage {
sorter: true,
...this.getColumnSearchProps("displayName"),
},
{
title: i18next.t("general:Category"),
dataIndex: "category",
key: "category",
width: "120px",
sorter: true,
...this.getColumnSearchProps("category"),
render: (text, record, index) => {
if (!text) {
text = "Default";
}
if (text === "Agent") {
return Setting.getTag("success", text);
} else {
return Setting.getTag("default", text);
}
},
},
{
title: i18next.t("general:Type"),
dataIndex: "type",
key: "type",
width: "100px",
sorter: true,
...this.getColumnSearchProps("type"),
render: (text, record, index) => {
return text;
},
},
{
title: "Logo",
dataIndex: "logo",

View File

@@ -133,7 +133,7 @@ class CertEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Scope"), i18next.t("cert:Scope - Tooltip"))} :
{Setting.getLabel(i18next.t("provider:Scope"), i18next.t("provider:Scope - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.scope} onChange={(value => {
@@ -149,7 +149,7 @@ class CertEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Type"), i18next.t("cert:Type - Tooltip"))} :
{Setting.getLabel(i18next.t("general:Type"), i18next.t("general:Type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.cert.type} onChange={(value => {

View File

@@ -93,7 +93,7 @@ class FormEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}}>
<Col style={{marginTop: "5px"}} span={Setting.isMobile() ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Type"), i18next.t("cert:Type - Tooltip"))} :
{Setting.getLabel(i18next.t("general:Type"), i18next.t("general:Type - Tooltip"))} :
</Col>
<Col span={22}>
<Select

View File

@@ -148,7 +148,7 @@ class GroupEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Type"), i18next.t("cert:Type - Tooltip"))} :
{Setting.getLabel(i18next.t("general:Type"), i18next.t("general:Type - Tooltip"))} :
</Col>
<Col span={22} >
<Select style={{width: "100%"}}

View File

@@ -236,6 +236,13 @@ class OrderListPage extends BaseListPage {
width: "120px",
sorter: true,
...this.getColumnSearchProps("state"),
render: (text, record, index) => {
return (
<Tooltip title={record.message || ""}>
<span>{text}</span>
</Tooltip>
);
},
},
{
title: i18next.t("general:Action"),
@@ -248,7 +255,7 @@ class OrderListPage extends BaseListPage {
return (
<div style={{display: "flex", flexWrap: "wrap", gap: "8px"}}>
<Button onClick={() => this.props.history.push(`/orders/${record.owner}/${record.name}/pay`)}>
{record.state === "Created" ? i18next.t("order:Pay") : i18next.t("general:Detail")}
{(record.state === "Created" || record.state === "Failed") ? i18next.t("order:Pay") : i18next.t("general:Detail")}
</Button>
<Button danger onClick={() => this.cancelOrder(record)} disabled={record.state !== "Created" || !isAdmin}>
{i18next.t("general:Cancel")}

View File

@@ -272,7 +272,7 @@ class OrderPayPage extends React.Component {
const updateTimeMap = {
Paid: i18next.t("order:Payment time"),
Canceled: i18next.t("order:Cancel time"),
PaymentFailed: i18next.t("order:Payment failed time"),
Failed: i18next.t("order:Payment failed time"),
Timeout: i18next.t("order:Timeout time"),
};
const updateTimeLabel = updateTimeMap[state] || i18next.t("general:Updated time");

View File

@@ -232,7 +232,7 @@ class PaymentEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Type"), i18next.t("cert:Type - Tooltip"))} :
{Setting.getLabel(i18next.t("general:Type"), i18next.t("general:Type - Tooltip"))} :
</Col>
<Col span={22} >
<Input disabled={true} value={this.state.payment.type} onChange={e => {

View File

@@ -309,7 +309,7 @@ class PermissionEditPage extends React.Component {
}
const data = res.data.map((role) => Setting.getOption(`${role.owner}/${role.name}`, `${role.owner}/${role.name}`));
if (args?.[1] === 1 && Array.isArray(res?.data)) {
// res.data = [{owner: i18next.t("organization:All"), name: "*"}, ...res.data];
// res.data = [{owner: i18next.t("general:All"), name: "*"}, ...res.data];
res.data = [
Setting.getOption(i18next.t("general:All"), "*"),
...data,

View File

@@ -687,7 +687,7 @@ class ProviderEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Category"), i18next.t("provider:Category - Tooltip"))} :
{Setting.getLabel(i18next.t("general:Category"), i18next.t("general:Category - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.provider.category} onChange={(value => {
@@ -751,7 +751,7 @@ class ProviderEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Type"), i18next.t("cert:Type - Tooltip"))} :
{Setting.getLabel(i18next.t("general:Type"), i18next.t("general:Type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} showSearch value={this.state.provider.type} onChange={(value => {
@@ -893,7 +893,7 @@ class ProviderEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Scope"), i18next.t("cert:Scope - Tooltip"))}
{Setting.getLabel(i18next.t("provider:Scope"), i18next.t("provider:Scope - Tooltip"))}
</Col>
<Col span={22} >
<Input value={this.state.provider.scopes} onChange={e => {
@@ -1300,9 +1300,9 @@ class ProviderEditPage extends React.Component {
<Select virtual={false} style={{width: "200px"}} value={this.state.provider.sslMode || "Auto"} onChange={value => {
this.updateProviderField("sslMode", value);
}}>
<Option value="Auto">{i18next.t("provider:Auto")}</Option>
<Option value="Enable">{i18next.t("provider:Enable")}</Option>
<Option value="Disable">{i18next.t("provider:Disable")}</Option>
<Option value="Auto">{i18next.t("general:Auto")}</Option>
<Option value="Enable">{i18next.t("general:Enable")}</Option>
<Option value="Disable">{i18next.t("general:Disable")}</Option>
</Select>
</Col>
</Row>

View File

@@ -139,7 +139,7 @@ class ProviderListPage extends BaseListPage {
...this.getColumnSearchProps("displayName"),
},
{
title: i18next.t("provider:Category"),
title: i18next.t("general:Category"),
dataIndex: "category",
key: "category",
filterMultiple: false,

View File

@@ -2267,7 +2267,7 @@ export function getFormTypeItems(formType) {
{name: "owner", label: "general:Organization", visible: true, width: "150"},
{name: "createdTime", label: "general:Created time", visible: true, width: "180"},
{name: "displayName", label: "general:Display name", visible: true, width: "150"},
{name: "category", label: "provider:Category", visible: true, width: "110"},
{name: "category", label: "general:Category", visible: true, width: "110"},
{name: "type", label: "general:Type", visible: true, width: "110"},
{name: "clientId", label: "provider:Client ID", visible: true, width: "100"},
{name: "providerUrl", label: "provider:Provider URL", visible: true, width: "150"},

View File

@@ -826,7 +826,7 @@ class SyncerEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Type"), i18next.t("cert:Type - Tooltip"))} :
{Setting.getLabel(i18next.t("general:Type"), i18next.t("general:Type - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.syncer.type} onChange={(value => {
@@ -878,7 +878,7 @@ class SyncerEditPage extends React.Component {
this.state.syncer.databaseType !== "postgres" ? null : (
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("syncer:SSL mode"), i18next.t("syncer:SSL mode - Tooltip"))} :
{Setting.getLabel(i18next.t("provider:SSL mode"), i18next.t("provider:SSL mode - Tooltip"))} :
</Col>
<Col span={22} >
<Select virtual={false} style={{width: "100%"}} value={this.state.syncer.sslMode} onChange={(value => {this.updateSyncerField("sslMode", value);})}>

View File

@@ -158,7 +158,7 @@ class TokenEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Scope"), i18next.t("cert:Scope - Tooltip"))}
{Setting.getLabel(i18next.t("provider:Scope"), i18next.t("provider:Scope - Tooltip"))}
</Col>
<Col span={22} >
<Input value={this.state.token.scope} onChange={e => {

View File

@@ -261,7 +261,7 @@ class TransactionEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("provider:Category"), i18next.t("provider:Category - Tooltip"))} :
{Setting.getLabel(i18next.t("general:Category"), i18next.t("general:Category - Tooltip"))} :
</Col>
<Col span={22} >
<Input disabled={true} value={this.state.transaction.category} />
@@ -269,7 +269,7 @@ class TransactionEditPage extends React.Component {
</Row>
<Row style={{marginTop: "20px"}} >
<Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{Setting.getLabel(i18next.t("general:Type"), i18next.t("cert:Type - Tooltip"))} :
{Setting.getLabel(i18next.t("general:Type"), i18next.t("general:Type - Tooltip"))} :
</Col>
<Col span={22} >
<Input disabled={true} value={this.state.transaction.type} onChange={e => {

View File

@@ -70,6 +70,7 @@
"Enable signin session - Tooltip": "Ob Casdoor eine Sitzung aufrechterhält, nachdem man sich von der Anwendung aus bei Casdoor angemeldet hat",
"Enable signup": "Registrierung aktivieren",
"Enable signup - Tooltip": "Ob Benutzern erlaubt werden soll, ein neues Konto zu registrieren",
"Existing Field": "Existing Field",
"Failed signin frozen time": "Sperrzeit bei fehlgeschlagenem Login",
"Failed signin frozen time - Tooltip": "Zeit, für die das Konto nach fehlgeschlagenen Anmeldeversuchen gesperrt wird",
"Failed signin limit": "Limit für fehlgeschlagene Logins",
@@ -151,6 +152,7 @@
"Signup items - Tooltip": "Elemente für Benutzer, die beim Registrieren neuer Konten ausgefüllt werden müssen - Hinweis",
"Single Choice": "Einfachauswahl",
"Small icon": "Kleines Symbol",
"Static Value": "Static Value",
"String": "String",
"Tags - Tooltip": "Nur Benutzer mit einem Tag, das in den Anwendungstags aufgeführt ist, können sich anmelden",
"The application does not allow to sign up new account": "Die Anwendung erlaubt es nicht, ein neues Konto zu registrieren",
@@ -184,9 +186,7 @@
"Expire in years - Tooltip": "Gültigkeitsdauer des Zertifikats in Jahren",
"New Cert": "Neues Zertifikat",
"Private key": "Private-Key",
"Private key - Tooltip": "Privater Schlüssel, der zum öffentlichen Schlüsselzertifikat gehört",
"Scope - Tooltip": "Nutzungsszenarien des Zertifikats",
"Type - Tooltip": "Art des Zertifikats"
"Private key - Tooltip": "Privater Schlüssel, der zum öffentlichen Schlüsselzertifikat gehört"
},
"code": {
"Code you received": "Der Code, den Sie erhalten haben",
@@ -275,6 +275,7 @@
"Applications that require authentication": "Anwendungen, die eine Authentifizierung erfordern",
"Apps": "Anwendungen",
"Authorization": "Autorisierung",
"Auto": "Auto",
"Avatar": "Profilbild",
"Avatar - Tooltip": "Öffentliches Avatarbild für den Benutzer",
"Back": "Zurück",
@@ -283,6 +284,8 @@
"Cancel": "Abbrechen",
"Captcha": "Captcha",
"Cart": "Warenkorb",
"Category": "Category",
"Category - Tooltip": "Category - Tooltip",
"Cert": "Zertifikat",
"Cert - Tooltip": "Das Public-Key-Zertifikat, das vom Client-SDK, das mit dieser Anwendung korrespondiert, verifiziert werden muss",
"Certs": "Zertifikate",
@@ -476,6 +479,8 @@
"SSH type - Tooltip": "Der Authentifizierungstyp für SSH-Verbindungen",
"Save": "Speichern",
"Save & Exit": "Speichern und verlassen",
"Scopes": "Scopes",
"Scopes - Tooltip": "Scopes - Tooltip",
"Search": "Suchen",
"Send": "Senden",
"Session ID": "Session-ID",
@@ -530,6 +535,7 @@
"Transactions": "Transaktionen",
"True": "Wahr",
"Type": "Typ",
"Type - Tooltip": "Type - Tooltip",
"URL": "URL",
"URL - Tooltip": "URL-Link",
"Unknown application name": "Unbekannter Anwendungsname",
@@ -867,6 +873,8 @@
},
"plan": {
"Edit Plan": "Plan bearbeiten",
"Is exclusive": "Is exclusive",
"Is exclusive - Tooltip": "Is exclusive - Tooltip",
"New Plan": "Neuer Plan",
"Period": "Zeitraum",
"Period - Tooltip": "Zeitraum",
@@ -897,6 +905,7 @@
"Amount": "Betrag",
"Buy": "Kaufen",
"Buy Product": "Produkt kaufen",
"Cart contains invalid products, please delete them before placing an order": "Cart contains invalid products, please delete them before placing an order",
"Custom amount available": "Benutzerdefinierter Betrag verfügbar",
"Custom price should be greater than zero": "Benutzerdefinierter Preis muss größer als null sein",
"Detail - Tooltip": "Detail des Produkts",
@@ -909,9 +918,11 @@
"Image": "Bild",
"Image - Tooltip": "Bild des Produkts",
"Information": "Information",
"Invalid product": "Invalid product",
"Is recharge": "Ist Aufladung",
"Is recharge - Tooltip": "Ob das Produkt zum Aufladen des Guthabens dient",
"New Product": "Neues Produkt",
"No recharge options available": "No recharge options available",
"Order created successfully": "Bestellung erfolgreich erstellt",
"PayPal": "PayPal",
"Payment cancelled": "Zahlung storniert",
@@ -924,10 +935,12 @@
"Please select at least one payment provider": "Bitte wählen Sie mindestens einen Zahlungsanbieter aus",
"Processing payment...": "Zahlung wird verarbeitet...",
"Product list cannot be empty": "Produktliste darf nicht leer sein",
"Product not found or invalid": "Product not found or invalid",
"Quantity": "Menge",
"Quantity - Tooltip": "Menge des Produkts",
"Recharge options": "Aufladeoptionen",
"Recharge options - Tooltip": "Aufladeoptionen - Tooltip",
"Recharge products need to go to the product detail page to set custom amount": "Recharge products need to go to the product detail page to set custom amount",
"Return URL": "Rückkeht-URL",
"Return URL - Tooltip": "URL für die Rückkehr nach einem erfolgreichen Kauf",
"SKU": "SKU",
@@ -972,8 +985,6 @@
"Can signin": "Kann sich einloggen",
"Can signup": "Kann sich registrieren",
"Can unlink": "Entlinken möglich",
"Category": "Kategorie",
"Category - Tooltip": "Kennung zur Kategorisierung und Gruppierung von Elementen oder Inhalten, erleichtert Filterung und Verwaltung",
"Channel No.": "Kanal Nr.",
"Channel No. - Tooltip": "Eindeutige Nummer zur Identifizierung eines Kommunikations- oder Datenübertragungskanals, verwendet zur Unterscheidung verschiedener Übertragungswege",
"Chat ID": "Chat-ID",
@@ -990,8 +1001,6 @@
"Content - Tooltip": "Spezifische Informationen oder Daten in Nachrichten, Benachrichtigungen oder Dokumenten",
"DB test": "DB-Test",
"DB test - Tooltip": "DB-Test - Tooltip",
"Disable SSL": "SSL deaktivieren",
"Disable SSL - Tooltip": "Ob die Deaktivierung des SSL-Protokolls bei der Kommunikation mit dem STMP-Server erfolgen soll",
"Domain": "Domäne",
"Domain - Tooltip": "Benutzerdefinierte Domain für Objektspeicher",
"Edit Provider": "Provider bearbeiten",
@@ -1074,9 +1083,12 @@
"SP ACS URL": "SP-ACS-URL",
"SP ACS URL - Tooltip": "SP ACS URL",
"SP Entity ID": "SP-Entitäts-ID",
"SSL mode": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip",
"Scene": "Szene",
"Scene - Tooltip": "Spezifisches Geschäftsszenario, in dem die Funktion oder Operation angewendet wird, verwendet zur Anpassung der logischen Verarbeitung für verschiedene Szenarien",
"Scope": "Umfang",
"Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "Secret-Access-Key",
"Secret access key - Tooltip": "Privater Schlüssel, der mit dem Zugriffsschlüssel gepaart ist, verwendet zum Signieren sensibler Operationen zur Verbesserung der Zugriffssicherheit",
"Secret key": "Secret-Key",
@@ -1238,6 +1250,9 @@
},
"syncer": {
"API Token / Password": "API Token / Password",
"AWS Access Key ID": "AWS Access Key ID",
"AWS Region": "AWS Region",
"AWS Secret Access Key": "AWS Secret Access Key",
"Admin Email": "Admin-E-Mail",
"Affiliation table": "Zuordnungstabelle",
"Affiliation table - Tooltip": "Datenbanktabellenname der Arbeitseinheit",
@@ -1269,8 +1284,6 @@
"SSH password": "SSH-Passwort",
"SSH port": "SSH-Port",
"SSH user": "SSH-Benutzer",
"SSL mode": "SSL-Modus",
"SSL mode - Tooltip": "SSL-Modus",
"Service account key": "Service-Account-Schlüssel",
"Sync interval": "Synchronisierungsintervall",
"Sync interval - Tooltip": "Einheit in Sekunden",

View File

@@ -70,6 +70,7 @@
"Enable signin session - Tooltip": "Whether Casdoor maintains a session after logging into Casdoor from the application",
"Enable signup": "Enable signup",
"Enable signup - Tooltip": "Whether to allow users to register a new account",
"Existing Field": "Existing Field",
"Failed signin frozen time": "Failed signin frozen time",
"Failed signin frozen time - Tooltip": "Waiting time after exceeding the number of failed login attempts. Users can only log in again after the waiting time expires. Default value is 15 minutes. The set value must be a positive integer",
"Failed signin limit": "Failed signin limit",
@@ -151,6 +152,7 @@
"Signup items - Tooltip": "Items for users to fill in when registering new accounts",
"Single Choice": "Single Choice",
"Small icon": "Small icon",
"Static Value": "Static Value",
"String": "String",
"Tags - Tooltip": "Only users with the tag that is listed in the application tags can login",
"The application does not allow to sign up new account": "The application does not allow to sign up new account",
@@ -184,9 +186,7 @@
"Expire in years - Tooltip": "Validity period of the certificate, in years",
"New Cert": "New Cert",
"Private key": "Private key",
"Private key - Tooltip": "Private key corresponding to the public key certificate",
"Scope - Tooltip": "Usage scenarios of the certificate",
"Type - Tooltip": "Type of certificate"
"Private key - Tooltip": "Private key corresponding to the public key certificate"
},
"code": {
"Code you received": "Code you received",
@@ -275,6 +275,7 @@
"Applications that require authentication": "Applications that require authentication",
"Apps": "Apps",
"Authorization": "Authorization",
"Auto": "Auto",
"Avatar": "Avatar",
"Avatar - Tooltip": "Public avatar image for the user",
"Back": "Back",
@@ -283,6 +284,8 @@
"Cancel": "Cancel",
"Captcha": "Captcha",
"Cart": "Cart",
"Category": "Category",
"Category - Tooltip": "Category - Tooltip",
"Cert": "Cert",
"Cert - Tooltip": "The public key certificate that needs to be verified by the client SDK corresponding to this application",
"Certs": "Certs",
@@ -476,6 +479,8 @@
"SSH type - Tooltip": "The auth type of SSH connection",
"Save": "Save",
"Save & Exit": "Save & Exit",
"Scopes": "Scopes",
"Scopes - Tooltip": "Scopes - Tooltip",
"Search": "Search",
"Send": "Send",
"Session ID": "Session ID",
@@ -530,6 +535,7 @@
"Transactions": "Transactions",
"True": "True",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip",
"URL": "URL",
"URL - Tooltip": "URL link",
"Unknown application name": "Unknown application name",
@@ -867,6 +873,8 @@
},
"plan": {
"Edit Plan": "Edit Plan",
"Is exclusive": "Is exclusive",
"Is exclusive - Tooltip": "Is exclusive - Tooltip",
"New Plan": "New Plan",
"Period": "Period",
"Period - Tooltip": "Period for the plan",
@@ -897,6 +905,7 @@
"Amount": "Amount",
"Buy": "Buy",
"Buy Product": "Buy Product",
"Cart contains invalid products, please delete them before placing an order": "Cart contains invalid products, please delete them before placing an order",
"Custom amount available": "Custom amount available",
"Custom price should be greater than zero": "Custom price should be greater than zero",
"Detail - Tooltip": "Detail of product",
@@ -909,9 +918,11 @@
"Image": "Image",
"Image - Tooltip": "Image of product",
"Information": "Information",
"Invalid product": "Invalid product",
"Is recharge": "Is recharge",
"Is recharge - Tooltip": "Whether the current product is to recharge balance",
"New Product": "New Product",
"No recharge options available": "No recharge options available",
"Order created successfully": "Order created successfully",
"PayPal": "PayPal",
"Payment cancelled": "Payment cancelled",
@@ -924,10 +935,12 @@
"Please select at least one payment provider": "Please select at least one payment provider",
"Processing payment...": "Processing payment...",
"Product list cannot be empty": "Product list cannot be empty",
"Product not found or invalid": "Product not found or invalid",
"Quantity": "Quantity",
"Quantity - Tooltip": "Quantity of product",
"Recharge options": "Recharge options",
"Recharge options - Tooltip": "Preset recharge amounts",
"Recharge products need to go to the product detail page to set custom amount": "Recharge products need to go to the product detail page to set custom amount",
"Return URL": "Return URL",
"Return URL - Tooltip": "URL to return to after successful purchase",
"SKU": "SKU",
@@ -972,8 +985,6 @@
"Can signin": "Can signin",
"Can signup": "Can signup",
"Can unlink": "Can unlink",
"Category": "Category",
"Category - Tooltip": "Identifier for categorizing and grouping items or content, facilitating filtering and management",
"Channel No.": "Channel No.",
"Channel No. - Tooltip": "Unique number identifying a communication or data transmission channel, used to distinguish different transmission paths",
"Chat ID": "Chat ID",
@@ -990,8 +1001,6 @@
"Content - Tooltip": "Specific information or data contained in messages, notifications, or documents",
"DB test": "DB test",
"DB test - Tooltip": "DB test - Tooltip",
"Disable SSL": "Disable SSL",
"Disable SSL - Tooltip": "Whether to disable SSL protocol when communicating with STMP server",
"Domain": "Domain",
"Domain - Tooltip": "Custom domain for object storage",
"Edit Provider": "Edit Provider",
@@ -1074,9 +1083,12 @@
"SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL",
"SP Entity ID": "SP Entity ID",
"SSL mode": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip",
"Scene": "Scene",
"Scene - Tooltip": "Specific business scenario where the function or operation applies, used to adapt logic processing for different scenarios",
"Scope": "Scope",
"Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "Secret access key",
"Secret access key - Tooltip": "Private key paired with the access key, used for signing sensitive operations to enhance access security",
"Secret key": "Secret key",
@@ -1238,6 +1250,9 @@
},
"syncer": {
"API Token / Password": "API Token / Password",
"AWS Access Key ID": "AWS Access Key ID",
"AWS Region": "AWS Region",
"AWS Secret Access Key": "AWS Secret Access Key",
"Admin Email": "Admin Email",
"Affiliation table": "Affiliation table",
"Affiliation table - Tooltip": "Database table name of the work unit",
@@ -1269,8 +1284,6 @@
"SSH password": "SSH password",
"SSH port": "SSH port",
"SSH user": "SSH user",
"SSL mode": "SSL mode",
"SSL mode - Tooltip": "The SSL mode used when connecting to the database",
"Service account key": "Service account key",
"Sync interval": "Sync interval",
"Sync interval - Tooltip": "Unit in seconds",

View File

@@ -70,6 +70,7 @@
"Enable signin session - Tooltip": "Si Casdoor mantiene una sesión después de iniciar sesión en Casdoor desde la aplicación",
"Enable signup": "Habilitar registro",
"Enable signup - Tooltip": "Ya sea permitir que los usuarios registren una nueva cuenta",
"Existing Field": "Existing Field",
"Failed signin frozen time": "Tiempo de congelación tras inicio fallido",
"Failed signin frozen time - Tooltip": "Tiempo durante el cual la cuenta está congelada después de intentos fallidos de inicio de sesión",
"Failed signin limit": "Límite de intentos fallidos de inicio",
@@ -134,7 +135,6 @@
"SAML metadata": "Metadatos de SAML",
"SAML metadata - Tooltip": "Los metadatos del protocolo SAML - Sugerencia",
"SAML reply URL": "URL de respuesta SAML",
"SAML reply URL - Tooltip": "Personalizar el código HTML del panel lateral de la página de inicio de sesión - Sugerencia",
"Security": "Seguridad",
"Select": "Seleccionar",
"Side panel HTML": "Panel lateral HTML",
@@ -152,6 +152,7 @@
"Signup items - Tooltip": "Elementos para que los usuarios completen al registrar nuevas cuentas - Sugerencia",
"Single Choice": "Opción única",
"Small icon": "Icono pequeño",
"Static Value": "Static Value",
"String": "Cadena",
"Tags - Tooltip": "Solo los usuarios con la etiqueta que esté listada en las etiquetas de la aplicación pueden iniciar sesión - Sugerencia",
"The application does not allow to sign up new account": "La aplicación no permite registrarse una cuenta nueva",
@@ -185,9 +186,7 @@
"Expire in years - Tooltip": "Período de validez del certificado, en años",
"New Cert": "Nuevo certificado",
"Private key": "Clave privada",
"Private key - Tooltip": "Clave privada correspondiente al certificado de clave pública",
"Scope - Tooltip": "Escenarios de uso del certificado",
"Type - Tooltip": "Tipo de certificado"
"Private key - Tooltip": "Clave privada correspondiente al certificado de clave pública"
},
"code": {
"Code you received": "Código que recibió",
@@ -276,6 +275,7 @@
"Applications that require authentication": "Aplicaciones que requieren autenticación",
"Apps": "Aplicaciones",
"Authorization": "Autorización",
"Auto": "Auto",
"Avatar": "Avatar",
"Avatar - Tooltip": "Imagen de avatar pública para el usuario",
"Back": "Atrás",
@@ -284,6 +284,8 @@
"Cancel": "Cancelar",
"Captcha": "Captcha",
"Cart": "Carrito",
"Category": "Category",
"Category - Tooltip": "Category - Tooltip",
"Cert": "Certificado",
"Cert - Tooltip": "El certificado de clave pública que necesita ser verificado por el SDK del cliente correspondiente a esta aplicación",
"Certs": "Certificaciones",
@@ -477,6 +479,8 @@
"SSH type - Tooltip": "El tipo de autenticación de conexión SSH",
"Save": "Guardar",
"Save & Exit": "Guardar y salir",
"Scopes": "Scopes",
"Scopes - Tooltip": "Scopes - Tooltip",
"Search": "Buscar",
"Send": "Enviar",
"Session ID": "ID de sesión",
@@ -531,6 +535,7 @@
"Transactions": "Transacciones",
"True": "Verdadero",
"Type": "Tipo",
"Type - Tooltip": "Type - Tooltip",
"URL": "URL",
"URL - Tooltip": "Enlace de URL",
"Unknown application name": "Nombre de aplicación desconocido",
@@ -868,6 +873,8 @@
},
"plan": {
"Edit Plan": "Editar plan",
"Is exclusive": "Is exclusive",
"Is exclusive - Tooltip": "Is exclusive - Tooltip",
"New Plan": "Nuevo plan",
"Period": "Período",
"Period - Tooltip": "Período",
@@ -898,6 +905,7 @@
"Amount": "Importe",
"Buy": "Comprar",
"Buy Product": "Comprar producto",
"Cart contains invalid products, please delete them before placing an order": "Cart contains invalid products, please delete them before placing an order",
"Custom amount available": "Importe personalizado disponible",
"Custom price should be greater than zero": "El precio personalizado debe ser mayor que cero",
"Detail - Tooltip": "Detalle del producto",
@@ -910,9 +918,11 @@
"Image": "Imagen",
"Image - Tooltip": "Imagen del producto",
"Information": "Información",
"Invalid product": "Invalid product",
"Is recharge": "Es recarga",
"Is recharge - Tooltip": "Indica si el producto actual es para recargar saldo",
"New Product": "Nuevo producto",
"No recharge options available": "No recharge options available",
"Order created successfully": "Pedido creado con éxito",
"PayPal": "PayPal",
"Payment cancelled": "Pago cancelado",
@@ -925,10 +935,12 @@
"Please select at least one payment provider": "Por favor, selecciona al menos un proveedor de pago",
"Processing payment...": "Procesando el pago...",
"Product list cannot be empty": "La lista de productos no puede estar vacía",
"Product not found or invalid": "Product not found or invalid",
"Quantity": "Cantidad",
"Quantity - Tooltip": "Cantidad de producto",
"Recharge options": "Opciones de recarga",
"Recharge options - Tooltip": "Opciones de recarga - Tooltip",
"Recharge products need to go to the product detail page to set custom amount": "Recharge products need to go to the product detail page to set custom amount",
"Return URL": "URL de retorno",
"Return URL - Tooltip": "URL para regresar después de una compra exitosa",
"SKU": "SKU",
@@ -973,8 +985,6 @@
"Can signin": "¿Puedes iniciar sesión?",
"Can signup": "Puede registrarse",
"Can unlink": "Desvincular",
"Category": "Categoría",
"Category - Tooltip": "Identificador para categorizar y agrupar elementos o contenido, facilitando el filtrado y la gestión",
"Channel No.": "Canal No.",
"Channel No. - Tooltip": "Número único que identifica un canal de comunicación o transmisión de datos, utilizado para distinguir diferentes rutas de transmisión",
"Chat ID": "ID de chat",
@@ -991,8 +1001,6 @@
"Content - Tooltip": "Contenido - Información adicional",
"DB test": "Prueba de BD",
"DB test - Tooltip": "Prueba de BD - Tooltip",
"Disable SSL": "Desactivar SSL",
"Disable SSL - Tooltip": "¿Hay que desactivar el protocolo SSL al comunicarse con el servidor STMP?",
"Domain": "Dominio",
"Domain - Tooltip": "Dominio personalizado para almacenamiento de objetos",
"Edit Provider": "Editar proveedor",
@@ -1075,9 +1083,12 @@
"SP ACS URL": "URL de ACS de SP",
"SP ACS URL - Tooltip": "URL del ACS de SP",
"SP Entity ID": "ID de entidad SP",
"SSL mode": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip",
"Scene": "Escena",
"Scene - Tooltip": "Escena",
"Scope": "Alcance",
"Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "Clave de acceso secreta",
"Secret access key - Tooltip": "Clave de acceso secreta",
"Secret key": "Clave secreta",
@@ -1239,6 +1250,9 @@
},
"syncer": {
"API Token / Password": "Token API / Contraseña",
"AWS Access Key ID": "AWS Access Key ID",
"AWS Region": "AWS Region",
"AWS Secret Access Key": "AWS Secret Access Key",
"Admin Email": "Correo del administrador",
"Affiliation table": "Tabla de afiliación",
"Affiliation table - Tooltip": "Nombre de la tabla de base de datos de la unidad de trabajo",
@@ -1270,8 +1284,6 @@
"SSH password": "Contraseña SSH",
"SSH port": "Puerto SSH",
"SSH user": "Usuario SSH",
"SSL mode": "Modo SSL",
"SSL mode - Tooltip": "Modo SSL",
"Service account key": "Clave de la cuenta de servicio",
"Sync interval": "Intervalo de sincronización",
"Sync interval - Tooltip": "Unidad en segundos",

View File

@@ -70,6 +70,7 @@
"Enable signin session - Tooltip": "Conserver une session après la connexion à Casdoor à partir de l'application",
"Enable signup": "Activer l'inscription",
"Enable signup - Tooltip": "Autoriser la création de nouveaux comptes",
"Existing Field": "Existing Field",
"Failed signin frozen time": "Temps de blocage après échec de connexion",
"Failed signin frozen time - Tooltip": "Durée pendant laquelle le compte est gelé après des tentatives de connexion échouées",
"Failed signin limit": "Limite d'échecs de connexion",
@@ -151,6 +152,7 @@
"Signup items - Tooltip": "Éléments à remplir par les utilisateurs lors de la création de nouveaux comptes - Info-bulle",
"Single Choice": "Choix unique",
"Small icon": "Petite icône",
"Static Value": "Static Value",
"String": "String",
"Tags - Tooltip": "Seuls les utilisateurs avec le tag listé dans les tags de l'application peuvent se connecter - Info-bulle",
"The application does not allow to sign up new account": "L'application ne permet pas de créer un nouveau compte",
@@ -184,9 +186,7 @@
"Expire in years - Tooltip": "Période de validité du certificat, en années",
"New Cert": "Nouveau Certificat",
"Private key": "Clé privée",
"Private key - Tooltip": "Clé privée correspondant au certificat de la clé publique",
"Scope - Tooltip": "Scénarios d'utilisation du certificat",
"Type - Tooltip": "Type de certificat"
"Private key - Tooltip": "Clé privée correspondant au certificat de la clé publique"
},
"code": {
"Code you received": "Le code que vous avez reçu",
@@ -275,6 +275,7 @@
"Applications that require authentication": "Applications qui nécessitent une authentification",
"Apps": "Applications",
"Authorization": "Autorisation",
"Auto": "Auto",
"Avatar": "Avatar",
"Avatar - Tooltip": "Image d'avatar publique pour le compte",
"Back": "Retour",
@@ -283,6 +284,8 @@
"Cancel": "Annuler",
"Captcha": "Captcha",
"Cart": "Panier",
"Category": "Category",
"Category - Tooltip": "Category - Tooltip",
"Cert": "Certificat",
"Cert - Tooltip": "La clé publique du certificat qui doit être vérifiée par le kit de développement client correspondant à cette application",
"Certs": "Certificats",
@@ -476,6 +479,8 @@
"SSH type - Tooltip": "Type d'authentification de connexion SSH",
"Save": "Enregistrer",
"Save & Exit": "Enregistrer et quitter",
"Scopes": "Scopes",
"Scopes - Tooltip": "Scopes - Tooltip",
"Search": "Rechercher",
"Send": "Envoyer",
"Session ID": "Identifiant de session",
@@ -530,6 +535,7 @@
"Transactions": "Transactions",
"True": "Vrai",
"Type": "Type",
"Type - Tooltip": "Type - Tooltip",
"URL": "URL",
"URL - Tooltip": "Lien de l'URL",
"Unknown application name": "Nom d'application inconnu",
@@ -867,6 +873,8 @@
},
"plan": {
"Edit Plan": "Modifier le plan",
"Is exclusive": "Is exclusive",
"Is exclusive - Tooltip": "Is exclusive - Tooltip",
"New Plan": "Nouveau plan",
"Period": "Période",
"Period - Tooltip": "Période",
@@ -897,6 +905,7 @@
"Amount": "Montant",
"Buy": "Acheter",
"Buy Product": "Acheter un produit",
"Cart contains invalid products, please delete them before placing an order": "Cart contains invalid products, please delete them before placing an order",
"Custom amount available": "Montant personnalisé disponible",
"Custom price should be greater than zero": "Le prix personnalisé doit être supérieur à zéro",
"Detail - Tooltip": "Détail du produit - Infobulle",
@@ -909,9 +918,11 @@
"Image": "Image",
"Image - Tooltip": "Image du produit",
"Information": "Informations",
"Invalid product": "Invalid product",
"Is recharge": "Est un rechargement",
"Is recharge - Tooltip": "Indique si le produit actuel permet de recharger le solde",
"New Product": "Nouveau produit",
"No recharge options available": "No recharge options available",
"Order created successfully": "Commande créée avec succès",
"PayPal": "PayPal",
"Payment cancelled": "Paiement annulé",
@@ -924,10 +935,12 @@
"Please select at least one payment provider": "Veuillez sélectionner au moins un fournisseur de paiement",
"Processing payment...": "Traitement du paiement...",
"Product list cannot be empty": "La liste des produits ne peut pas être vide",
"Product not found or invalid": "Product not found or invalid",
"Quantity": "Quantité",
"Quantity - Tooltip": "Quantité du produit",
"Recharge options": "Options de recharge",
"Recharge options - Tooltip": "Recharge options - Tooltip",
"Recharge products need to go to the product detail page to set custom amount": "Recharge products need to go to the product detail page to set custom amount",
"Return URL": "URL de retour",
"Return URL - Tooltip": "URL de retour après l'achat réussi",
"SKU": "SKU",
@@ -972,8 +985,6 @@
"Can signin": "Pouvez-vous vous connecter?",
"Can signup": "Peut s'inscrire",
"Can unlink": "Peut annuler le lien",
"Category": "Catégorie",
"Category - Tooltip": "Sélectionnez une catégorie",
"Channel No.": "chaîne n°",
"Channel No. - Tooltip": "Canal N°",
"Chat ID": "ID de chat",
@@ -990,8 +1001,6 @@
"Content - Tooltip": "Contenu - Infobulle",
"DB test": "Test BD",
"DB test - Tooltip": "Test BD - Infobulle",
"Disable SSL": "Désactiver SSL",
"Disable SSL - Tooltip": "Désactiver le protocole SSL lors de la communication avec le serveur STMP",
"Domain": "Domaine",
"Domain - Tooltip": "Domaine personnalisé pour le stockage d'objets",
"Edit Provider": "Modifier le fournisseur",
@@ -1074,9 +1083,12 @@
"SP ACS URL": "URL du SP ACS",
"SP ACS URL - Tooltip": "URL de l'ACS du fournisseur de service",
"SP Entity ID": "Identifiant d'entité SP",
"SSL mode": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip",
"Scene": "Scène",
"Scene - Tooltip": "Scène",
"Scope": "Portée",
"Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "Clé d'accès secrète",
"Secret access key - Tooltip": "Clé d'accès secrète",
"Secret key": "Clé secrète",
@@ -1238,6 +1250,9 @@
},
"syncer": {
"API Token / Password": "Jeton API / Mot de passe",
"AWS Access Key ID": "AWS Access Key ID",
"AWS Region": "AWS Region",
"AWS Secret Access Key": "AWS Secret Access Key",
"Admin Email": "E-mail admin",
"Affiliation table": "Table d'affiliation",
"Affiliation table - Tooltip": "Nom de la table de la base de données de l'unité de travail",
@@ -1269,8 +1284,6 @@
"SSH password": "Mot de passe SSH",
"SSH port": "Port SSH",
"SSH user": "Utilisateur SSH",
"SSL mode": "Mode SSL",
"SSL mode - Tooltip": "Mode SSL",
"Service account key": "Clé du compte de service",
"Sync interval": "Intervalle de synchronisation",
"Sync interval - Tooltip": "Unité en secondes",

View File

@@ -70,6 +70,7 @@
"Enable signin session - Tooltip": "アプリケーションから Casdoor にログイン後、Casdoor がセッションを維持しているかどうか",
"Enable signup": "サインアップを有効にする",
"Enable signup - Tooltip": "新しいアカウントの登録をユーザーに許可するかどうか",
"Existing Field": "Existing Field",
"Failed signin frozen time": "サインイン失敗時の凍結時間",
"Failed signin frozen time - Tooltip": "サインイン失敗後にアカウントが凍結される時間",
"Failed signin limit": "サインイン失敗回数制限",
@@ -117,7 +118,6 @@
"Please input your organization!": "あなたの組織を入力してください!",
"Please select a HTML file": "HTMLファイルを選択してください",
"Pop up": "ポップアップ",
"Pop up - Tooltip": "ポップアップ - ヒント",
"Providers": "プロバイダー",
"Random": "ランダム",
"Real name": "本名",
@@ -135,7 +135,6 @@
"SAML metadata": "SAMLメタデータ",
"SAML metadata - Tooltip": "SAMLプロトコルのメタデータ - ヒント",
"SAML reply URL": "SAMLリプライURL",
"SAML reply URL - Tooltip": "SAMLリプライURL - ヒント",
"Security": "セキュリティ",
"Select": "選択",
"Side panel HTML": "サイドパネルのHTML",
@@ -153,6 +152,7 @@
"Signup items - Tooltip": "新しいアカウントを登録する際にユーザーが入力するアイテム",
"Single Choice": "単一選択",
"Small icon": "小さいアイコン",
"Static Value": "Static Value",
"String": "文字列",
"Tags - Tooltip": "アプリケーションタグに含まれるタグを持つユーザーのみログイン可能です",
"The application does not allow to sign up new account": "アプリケーションでは新しいアカウントの登録ができません",
@@ -186,9 +186,7 @@
"Expire in years - Tooltip": "証明書の有効期間、年数で",
"New Cert": "新しい証明書",
"Private key": "プライベートキー",
"Private key - Tooltip": "公開鍵証明書に対応する秘密鍵",
"Scope - Tooltip": "証明書の使用シナリオ",
"Type - Tooltip": "証明書の種類"
"Private key - Tooltip": "公開鍵証明書に対応する秘密鍵"
},
"code": {
"Code you received": "受け取ったコード",
@@ -277,6 +275,7 @@
"Applications that require authentication": "認証が必要なアプリケーション",
"Apps": "アプリ",
"Authorization": "認可",
"Auto": "Auto",
"Avatar": "アバター",
"Avatar - Tooltip": "ユーザーのパブリックアバター画像",
"Back": "戻る",
@@ -285,6 +284,8 @@
"Cancel": "キャンセルします",
"Captcha": "キャプチャ",
"Cart": "カート",
"Category": "Category",
"Category - Tooltip": "Category - Tooltip",
"Cert": "証明書",
"Cert - Tooltip": "このアプリケーションに対応するクライアントSDKによって検証する必要がある公開鍵証明書",
"Certs": "証明書",
@@ -478,6 +479,8 @@
"SSH type - Tooltip": "SSH接続の認証タイプ",
"Save": "保存",
"Save & Exit": "保存して終了",
"Scopes": "Scopes",
"Scopes - Tooltip": "Scopes - Tooltip",
"Search": "検索",
"Send": "送信",
"Session ID": "セッションID",
@@ -532,6 +535,7 @@
"Transactions": "取引",
"True": "真",
"Type": "タイプ",
"Type - Tooltip": "Type - Tooltip",
"URL": "URL",
"URL - Tooltip": "URLリンク",
"Unknown application name": "不明なアプリケーション名",
@@ -869,6 +873,8 @@
},
"plan": {
"Edit Plan": "プランを編集",
"Is exclusive": "Is exclusive",
"Is exclusive - Tooltip": "Is exclusive - Tooltip",
"New Plan": "新しいプラン",
"Period": "期間",
"Period - Tooltip": "期間",
@@ -899,6 +905,7 @@
"Amount": "金額",
"Buy": "購入",
"Buy Product": "製品を購入する",
"Cart contains invalid products, please delete them before placing an order": "Cart contains invalid products, please delete them before placing an order",
"Custom amount available": "任意金額を利用可能",
"Custom price should be greater than zero": "カスタム価格は0より大きくする必要があります",
"Detail - Tooltip": "製品の詳細",
@@ -911,9 +918,11 @@
"Image": "画像",
"Image - Tooltip": "製品のイメージ",
"Information": "情報",
"Invalid product": "Invalid product",
"Is recharge": "チャージ用か",
"Is recharge - Tooltip": "現在の製品が残高をチャージするためかどうか",
"New Product": "新製品",
"No recharge options available": "No recharge options available",
"Order created successfully": "注文が正常に作成されました",
"PayPal": "ペイパル",
"Payment cancelled": "支払いキャンセル",
@@ -926,10 +935,12 @@
"Please select at least one payment provider": "少なくとも1つの支払いプロバイダーを選択してください",
"Processing payment...": "支払い処理中...",
"Product list cannot be empty": "商品リストを空にできません",
"Product not found or invalid": "Product not found or invalid",
"Quantity": "量",
"Quantity - Tooltip": "製品の量",
"Recharge options": "チャージオプション",
"Recharge options - Tooltip": "チャージオプション - ツールチップ",
"Recharge products need to go to the product detail page to set custom amount": "Recharge products need to go to the product detail page to set custom amount",
"Return URL": "戻りURL",
"Return URL - Tooltip": "成功した購入後に戻るURL",
"SKU": "SKU",
@@ -974,8 +985,6 @@
"Can signin": "サインインできますか?",
"Can signup": "サインアップできますか?",
"Can unlink": "アンリンクすることができます",
"Category": "カテゴリー",
"Category - Tooltip": "カテゴリーを選択してください",
"Channel No.": "チャンネル番号",
"Channel No. - Tooltip": "チャンネル番号",
"Chat ID": "チャットID",
@@ -992,8 +1001,6 @@
"Content - Tooltip": "コンテンツ - ツールチップ",
"DB test": "DBテスト",
"DB test - Tooltip": "DBテスト - ツールチップ",
"Disable SSL": "SSLを無効にする",
"Disable SSL - Tooltip": "SMTPサーバーと通信する場合にSSLプロトコルを無効にするかどうか",
"Domain": "ドメイン",
"Domain - Tooltip": "オブジェクトストレージのカスタムドメイン",
"Edit Provider": "編集プロバイダー",
@@ -1076,9 +1083,12 @@
"SP ACS URL": "SP ACS URL",
"SP ACS URL - Tooltip": "SP ACS URL - ツールチップ",
"SP Entity ID": "SPエンティティID",
"SSL mode": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip",
"Scene": "シーン",
"Scene - Tooltip": "シーン",
"Scope": "範囲",
"Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "秘密のアクセスキー",
"Secret access key - Tooltip": "秘密のアクセスキー",
"Secret key": "秘密鍵",
@@ -1240,6 +1250,9 @@
},
"syncer": {
"API Token / Password": "APIトークン / パスワード",
"AWS Access Key ID": "AWS Access Key ID",
"AWS Region": "AWS Region",
"AWS Secret Access Key": "AWS Secret Access Key",
"Admin Email": "管理者メール",
"Affiliation table": "所属テーブル",
"Affiliation table - Tooltip": "作業単位のデータベーステーブル名",
@@ -1271,8 +1284,6 @@
"SSH password": "SSHパスワード",
"SSH port": "SSHポート",
"SSH user": "SSHユーザー",
"SSL mode": "SSLモード",
"SSL mode - Tooltip": "SSLモード",
"Service account key": "サービスアカウントキー",
"Sync interval": "同期の間隔",
"Sync interval - Tooltip": "単位は秒です",

View File

@@ -70,6 +70,7 @@
"Enable signin session - Tooltip": "Czy Casdoor utrzymuje sesję po zalogowaniu do Casdoor z poziomu aplikacji",
"Enable signup": "Włącz rejestrację",
"Enable signup - Tooltip": "Czy zezwolić użytkownikom na rejestrację nowych kont",
"Existing Field": "Existing Field",
"Failed signin frozen time": "Czas blokady po nieudanym logowaniu",
"Failed signin frozen time - Tooltip": "Czas w którym konto jest zablokowane po nieudanych próbach logowania - Podpowiedź",
"Failed signin limit": "Limit nieudanych logowań",
@@ -151,6 +152,7 @@
"Signup items - Tooltip": "Elementy, które użytkownicy muszą wypełnić podczas rejestracji nowych kont",
"Single Choice": "Jednokrotny wybór",
"Small icon": "Mała ikona",
"Static Value": "Static Value",
"String": "Ciąg",
"Tags - Tooltip": "Tylko użytkownicy z tagiem wymienionym w tagach aplikacji mogą się zalogować",
"The application does not allow to sign up new account": "Aplikacja nie zezwala na rejestrację nowego konta",
@@ -184,9 +186,7 @@
"Expire in years - Tooltip": "Okres ważności certyfikatu, w latach",
"New Cert": "Nowy certyfikat",
"Private key": "Klucz prywatny",
"Private key - Tooltip": "Klucz prywatny odpowiadający certyfikatowi klucza publicznego",
"Scope - Tooltip": "Scenariusze użycia certyfikatu",
"Type - Tooltip": "Typ certyfikatu"
"Private key - Tooltip": "Klucz prywatny odpowiadający certyfikatowi klucza publicznego"
},
"code": {
"Code you received": "Kod, który otrzymałeś",
@@ -275,6 +275,7 @@
"Applications that require authentication": "Aplikacje wymagające uwierzytelniania",
"Apps": "Aplikacje",
"Authorization": "Autoryzacja",
"Auto": "Auto",
"Avatar": "Awatar",
"Avatar - Tooltip": "Publiczny obraz awatara użytkownika",
"Back": "Wstecz",
@@ -283,6 +284,8 @@
"Cancel": "Anuluj",
"Captcha": "Captcha",
"Cart": "Koszyk",
"Category": "Category",
"Category - Tooltip": "Category - Tooltip",
"Cert": "Certyfikat",
"Cert - Tooltip": "Certyfikat klucza publicznego, który musi być zweryfikowany przez odpowiednią aplikację SDK po stronie klienta",
"Certs": "Certyfikaty",
@@ -476,6 +479,8 @@
"SSH type - Tooltip": "Typ uwierzytelniania połączenia SSH",
"Save": "Zapisz",
"Save & Exit": "Zapisz i wyjdź",
"Scopes": "Scopes",
"Scopes - Tooltip": "Scopes - Tooltip",
"Search": "Szukaj",
"Send": "Wyślij",
"Session ID": "ID sesji",
@@ -530,6 +535,7 @@
"Transactions": "Transakcje",
"True": "Prawda",
"Type": "Typ",
"Type - Tooltip": "Type - Tooltip",
"URL": "URL",
"URL - Tooltip": "Link URL",
"Unknown application name": "Nieznana nazwa aplikacji",
@@ -867,6 +873,8 @@
},
"plan": {
"Edit Plan": "Edytuj plan",
"Is exclusive": "Is exclusive",
"Is exclusive - Tooltip": "Is exclusive - Tooltip",
"New Plan": "Nowy plan",
"Period": "Okres",
"Period - Tooltip": "Okres",
@@ -897,6 +905,7 @@
"Amount": "Kwota",
"Buy": "Kup",
"Buy Product": "Kup produkt",
"Cart contains invalid products, please delete them before placing an order": "Cart contains invalid products, please delete them before placing an order",
"Custom amount available": "Dostępna kwota niestandardowa",
"Custom price should be greater than zero": "Cena niestandardowa musi być większa od zera",
"Detail - Tooltip": "Szczegóły produktu",
@@ -909,9 +918,11 @@
"Image": "Obrazek",
"Image - Tooltip": "Obrazek produktu",
"Information": "Informacje",
"Invalid product": "Invalid product",
"Is recharge": "Jest doładowaniem",
"Is recharge - Tooltip": "Czy bieżący produkt służy do doładowania salda",
"New Product": "Nowy produkt",
"No recharge options available": "No recharge options available",
"Order created successfully": "Zamówienie utworzone pomyślnie",
"PayPal": "PayPal",
"Payment cancelled": "Płatność anulowana",
@@ -924,10 +935,12 @@
"Please select at least one payment provider": "Wybierz co najmniej jednego dostawcę płatności",
"Processing payment...": "Przetwarzanie płatności...",
"Product list cannot be empty": "Lista produktów nie może być pusta",
"Product not found or invalid": "Product not found or invalid",
"Quantity": "Ilość",
"Quantity - Tooltip": "Ilość produktu",
"Recharge options": "Opcje doładowania",
"Recharge options - Tooltip": "Opcje doładowania - Podpowiedź",
"Recharge products need to go to the product detail page to set custom amount": "Recharge products need to go to the product detail page to set custom amount",
"Return URL": "Adres powrotu",
"Return URL - Tooltip": "Adres do powrotu po udanym zakupie",
"SKU": "SKU",
@@ -972,8 +985,10 @@
"Can signin": "Można się zalogować",
"Can signup": "Można się zarejestrować",
"Can unlink": "Można odłączyć",
"Category": "Kategoria",
"Category - Tooltip": "Wybierz kategorię",
"Channel No.": "Channel No.",
"Channel No. - Tooltip": "Channel No. - Tooltip",
"Chat ID": "Chat ID",
"Chat ID - Tooltip": "Chat ID - Tooltip",
"Client ID": "ID klienta",
"Client ID - Tooltip": "ID klienta",
"Client ID 2": "ID klienta 2",
@@ -986,6 +1001,37 @@
"Content - Tooltip": "Treść",
"DB test": "Test bazy danych",
"DB test - Tooltip": "Test bazy danych",
"Domain": "Domain",
"Domain - Tooltip": "Domain - Tooltip",
"Edit Provider": "Edit Provider",
"Email content": "Email content",
"Email content - Tooltip": "Email content - Tooltip",
"Email regex": "Email regex",
"Email regex - Tooltip": "Email regex - Tooltip",
"Email title": "Email title",
"Email title - Tooltip": "Email title - Tooltip",
"Enable PKCE": "Enable PKCE",
"Enable PKCE - Tooltip": "Enable PKCE - Tooltip",
"Enable proxy": "Enable proxy",
"Enable proxy - Tooltip": "Enable proxy - Tooltip",
"Endpoint": "Endpoint",
"Endpoint (Intranet)": "Endpoint (Intranet)",
"Endpoint - Tooltip": "Endpoint - Tooltip",
"Follow-up action": "Follow-up action",
"Follow-up action - Tooltip": "Follow-up action - Tooltip",
"From address": "From address",
"From address - Tooltip": "From address - Tooltip",
"From name": "From name",
"From name - Tooltip": "From name - Tooltip",
"Get phone number": "Get phone number",
"Get phone number - Tooltip": "Get phone number - Tooltip",
"HTTP body mapping": "HTTP body mapping",
"HTTP body mapping - Tooltip": "HTTP body mapping - Tooltip",
"HTTP header": "HTTP header",
"HTTP header - Tooltip": "HTTP header - Tooltip",
"Host": "Host",
"Host - Tooltip": "Host - Tooltip",
"IdP": "IdP",
"IdP certificate": "Certyfikat IdP",
"Internal": "Wewnętrzny",
"Issuer URL": "Adres URL wystawcy",
@@ -1037,9 +1083,12 @@
"SP ACS URL": "Adres URL SP ACS",
"SP ACS URL - Tooltip": "Adres URL SP ACS",
"SP Entity ID": "ID jednostki SP",
"SSL mode": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip",
"Scene": "Scena",
"Scene - Tooltip": "Scena",
"Scope": "Zakres",
"Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "Tajny klucz dostępu",
"Secret access key - Tooltip": "Tajny klucz dostępu",
"Secret key": "Tajny klucz",
@@ -1201,6 +1250,9 @@
},
"syncer": {
"API Token / Password": "API Token / Password",
"AWS Access Key ID": "AWS Access Key ID",
"AWS Region": "AWS Region",
"AWS Secret Access Key": "AWS Secret Access Key",
"Admin Email": "Admin Email",
"Affiliation table": "Tabela przynależności",
"Affiliation table - Tooltip": "Nazwa tabeli bazy danych jednostki pracy",
@@ -1232,8 +1284,6 @@
"SSH password": "Hasło SSH",
"SSH port": "Port SSH",
"SSH user": "Użytkownik SSH",
"SSL mode": "Tryb SSL",
"SSL mode - Tooltip": "Tryb SSL - etykietka",
"Service account key": "Klucz konta usługi",
"Sync interval": "Interwał synchronizacji",
"Sync interval - Tooltip": "Jednostka w sekundach",

View File

@@ -70,6 +70,7 @@
"Enable signin session - Tooltip": "Se o Casdoor mantém uma sessão depois de fazer login no Casdoor a partir da aplicação",
"Enable signup": "Ativar registro",
"Enable signup - Tooltip": "Se permite que os usuários registrem uma nova conta",
"Existing Field": "Existing Field",
"Failed signin frozen time": "Tempo de bloqueio após falha de login",
"Failed signin frozen time - Tooltip": "Tempo em que a conta fica congelada após tentativas de login falhadas",
"Failed signin limit": "Limite de tentativas de login falhadas",
@@ -151,6 +152,7 @@
"Signup items - Tooltip": "Itens para os usuários preencherem ao fazer login - Dica",
"Single Choice": "Escolha única",
"Small icon": "Ícone pequeno",
"Static Value": "Static Value",
"String": "String",
"Tags - Tooltip": "Apenas usuários com a tag listada nas tags da aplicação podem fazer login - Dica",
"The application does not allow to sign up new account": "A aplicação não permite o registro de novas contas",
@@ -184,9 +186,7 @@
"Expire in years - Tooltip": "Período de validade do certificado, em anos",
"New Cert": "Novo Certificado",
"Private key": "Chave privada",
"Private key - Tooltip": "Chave privada correspondente ao certificado de chave pública",
"Scope - Tooltip": "Cenários de uso do certificado",
"Type - Tooltip": "Tipo de certificado"
"Private key - Tooltip": "Chave privada correspondente ao certificado de chave pública"
},
"code": {
"Code you received": "Código que você recebeu",
@@ -275,6 +275,7 @@
"Applications that require authentication": "Aplicações que requerem autenticação",
"Apps": "Aplicativos",
"Authorization": "Autorização",
"Auto": "Auto",
"Avatar": "Avatar",
"Avatar - Tooltip": "Imagem de avatar pública do usuário",
"Back": "Voltar",
@@ -283,6 +284,8 @@
"Cancel": "Cancelar",
"Captcha": "Captcha",
"Cart": "Carrinho",
"Category": "Category",
"Category - Tooltip": "Category - Tooltip",
"Cert": "Certificado",
"Cert - Tooltip": "O certificado da chave pública que precisa ser verificado pelo SDK do cliente correspondente a esta aplicação",
"Certs": "Certificados",
@@ -476,6 +479,8 @@
"SSH type - Tooltip": "Tipo de autenticação para conexão SSH",
"Save": "Salvar",
"Save & Exit": "Salvar e Sair",
"Scopes": "Scopes",
"Scopes - Tooltip": "Scopes - Tooltip",
"Search": "Buscar",
"Send": "Enviar",
"Session ID": "ID da sessão",
@@ -530,6 +535,7 @@
"Transactions": "Transações",
"True": "Verdadeiro",
"Type": "Tipo",
"Type - Tooltip": "Type - Tooltip",
"URL": "URL",
"URL - Tooltip": "Link da URL",
"Unknown application name": "Nome de aplicação desconhecido",
@@ -867,6 +873,8 @@
},
"plan": {
"Edit Plan": "Editar Plano",
"Is exclusive": "Is exclusive",
"Is exclusive - Tooltip": "Is exclusive - Tooltip",
"New Plan": "Novo Plano",
"Period": "Período",
"Period - Tooltip": "Período",
@@ -897,6 +905,7 @@
"Amount": "Valor",
"Buy": "Comprar",
"Buy Product": "Comprar Produto",
"Cart contains invalid products, please delete them before placing an order": "Cart contains invalid products, please delete them before placing an order",
"Custom amount available": "Valor personalizado disponível",
"Custom price should be greater than zero": "O preço personalizado deve ser maior que zero",
"Detail - Tooltip": "Detalhes do produto",
@@ -909,9 +918,11 @@
"Image": "Imagem",
"Image - Tooltip": "Imagem do produto",
"Information": "Informações",
"Invalid product": "Invalid product",
"Is recharge": "É recarga",
"Is recharge - Tooltip": "Se o produto atual é para recarregar saldo",
"New Product": "Novo Produto",
"No recharge options available": "No recharge options available",
"Order created successfully": "Pedido criado com sucesso",
"PayPal": "PayPal",
"Payment cancelled": "Pagamento cancelado",
@@ -924,10 +935,12 @@
"Please select at least one payment provider": "Por favor, selecione pelo menos um provedor de pagamento",
"Processing payment...": "Processando pagamento...",
"Product list cannot be empty": "A lista de produtos não pode estar vazia",
"Product not found or invalid": "Product not found or invalid",
"Quantity": "Quantidade",
"Quantity - Tooltip": "Quantidade do produto",
"Recharge options": "Opções de recarga",
"Recharge options - Tooltip": "Dica: opções de recarga",
"Recharge products need to go to the product detail page to set custom amount": "Recharge products need to go to the product detail page to set custom amount",
"Return URL": "URL de Retorno",
"Return URL - Tooltip": "URL para retornar após a compra bem-sucedida",
"SKU": "SKU",
@@ -972,8 +985,6 @@
"Can signin": "Pode fazer login",
"Can signup": "Pode se inscrever",
"Can unlink": "Pode desvincular",
"Category": "Categoria",
"Category - Tooltip": "Selecione uma categoria",
"Channel No.": "Número do canal",
"Channel No. - Tooltip": "Número do canal",
"Chat ID": "ID do chat",
@@ -990,8 +1001,6 @@
"Content - Tooltip": "Dica: conteúdo",
"DB test": "Teste do banco de dados",
"DB test - Tooltip": "Dica: teste do banco de dados",
"Disable SSL": "Desabilitar SSL",
"Disable SSL - Tooltip": "Se deve desabilitar o protocolo SSL ao comunicar com o servidor SMTP",
"Domain": "Domínio",
"Domain - Tooltip": "Domínio personalizado para armazenamento de objetos",
"Edit Provider": "Editar Provedor",
@@ -1074,9 +1083,12 @@
"SP ACS URL": "URL SP ACS",
"SP ACS URL - Tooltip": "URL SP ACS",
"SP Entity ID": "ID da Entidade SP",
"SSL mode": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip",
"Scene": "Cenário",
"Scene - Tooltip": "Cenário",
"Scope": "Escopo",
"Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "Chave de acesso secreta",
"Secret access key - Tooltip": "Chave de acesso secreta",
"Secret key": "Chave secreta",
@@ -1238,6 +1250,9 @@
},
"syncer": {
"API Token / Password": "Token de API / Senha",
"AWS Access Key ID": "AWS Access Key ID",
"AWS Region": "AWS Region",
"AWS Secret Access Key": "AWS Secret Access Key",
"Admin Email": "E-mail do administrador",
"Affiliation table": "Tabela de Afiliação",
"Affiliation table - Tooltip": "Nome da tabela no banco de dados da unidade de trabalho",
@@ -1269,8 +1284,6 @@
"SSH password": "Senha SSH",
"SSH port": "Porta SSH",
"SSH user": "Usuário SSH",
"SSL mode": "Modo SSL",
"SSL mode - Tooltip": "Dica: modo SSL",
"Service account key": "Chave da conta de serviço",
"Sync interval": "Intervalo de sincronização",
"Sync interval - Tooltip": "Unidade em segundos",

View File

@@ -70,6 +70,7 @@
"Enable signin session - Tooltip": "Uygulamadan Casdoor'a giriş yaptıktan sonra Casdoor'un bir oturum sürdürüp sürdürmeyeceği",
"Enable signup": "Kayıtı Etkinleştir",
"Enable signup - Tooltip": "Kullanıcıların yeni bir hesap kaydetmesine izin verilip verilmeyeceği",
"Existing Field": "Existing Field",
"Failed signin frozen time": "Başarısız giriş dondurma süresi",
"Failed signin frozen time - Tooltip": "Başarısız giriş denemelerinden sonra hesabın dondurulduğu süre",
"Failed signin limit": "Başarısız giriş limiti",
@@ -151,6 +152,7 @@
"Signup items - Tooltip": "Kullanıcıların yeni hesaplar kaydederken doldurması gereken öğeler - İpucu",
"Single Choice": "Tek Seçim",
"Small icon": "Küçük simge",
"Static Value": "Static Value",
"String": "Dize",
"Tags - Tooltip": "Yalnızca uygulama etiketlerinde listelenen etikete sahip kullanıcılar giriş yapabilir - İpucu",
"The application does not allow to sign up new account": "Uygulama yeni hesap kaydetmeyi izin vermemektedir",
@@ -184,9 +186,7 @@
"Expire in years - Tooltip": "Sertifikanın geçerlilik süresi, yıllarda",
"New Cert": "Yeni Sertifika",
"Private key": "Özel anahtar",
"Private key - Tooltip": "Genel anahtar sertifikasına karşılık gelen özel anahtar",
"Scope - Tooltip": "Sertifikanın kullanım senaryoları",
"Type - Tooltip": "Sertifika türü"
"Private key - Tooltip": "Genel anahtar sertifikasına karşılık gelen özel anahtar"
},
"code": {
"Code you received": "Aldığınız kod",
@@ -275,6 +275,7 @@
"Applications that require authentication": "Kimlik doğrulaması gerektiren uygulamalar",
"Apps": "Uygulamalar",
"Authorization": "Yetkilendirme",
"Auto": "Auto",
"Avatar": "Avatar",
"Avatar - Tooltip": "Kullanıcı için genel avatar resmi",
"Back": "Geri",
@@ -283,6 +284,8 @@
"Cancel": "Vazgeç",
"Captcha": "Captcha",
"Cart": "Sepet",
"Category": "Category",
"Category - Tooltip": "Category - Tooltip",
"Cert": "Sertifika",
"Cert - Tooltip": "Bu uygulamaya karşılık gelen istemci SDK tarafından doğrulanması gereken genel anahtar sertifikası",
"Certs": "Sertifikalar",
@@ -476,6 +479,8 @@
"SSH type - Tooltip": "SSH bağlantısının kimlik doğrulama türü",
"Save": "Kaydet",
"Save & Exit": "Kaydet ve Çık",
"Scopes": "Scopes",
"Scopes - Tooltip": "Scopes - Tooltip",
"Search": "Ara",
"Send": "Gönder",
"Session ID": "Oturum ID",
@@ -530,6 +535,7 @@
"Transactions": "İşlemler",
"True": "Doğru",
"Type": "Tür",
"Type - Tooltip": "Type - Tooltip",
"URL": "URL",
"URL - Tooltip": "URL bağlantısı",
"Unknown application name": "Bilinmeyen uygulama adı",
@@ -867,6 +873,8 @@
},
"plan": {
"Edit Plan": "Planı Düzenle",
"Is exclusive": "Is exclusive",
"Is exclusive - Tooltip": "Is exclusive - Tooltip",
"New Plan": "Yeni Plan",
"Period": "Dönem",
"Period - Tooltip": "Dönem",
@@ -897,6 +905,7 @@
"Amount": "Tutar",
"Buy": "Satın Al",
"Buy Product": "Ürün Satın Al",
"Cart contains invalid products, please delete them before placing an order": "Cart contains invalid products, please delete them before placing an order",
"Custom amount available": "Özel tutar kullanılabilir",
"Custom price should be greater than zero": "Özel fiyat sıfırdan büyük olmalıdır",
"Detail - Tooltip": "Ürün detayı",
@@ -909,9 +918,11 @@
"Image": "Resim",
"Image - Tooltip": "Ürün resmi",
"Information": "Bilgi",
"Invalid product": "Invalid product",
"Is recharge": "Yeniden yükleme mi",
"Is recharge - Tooltip": "Mevcut ürün bakiye yeniden yüklemesi ise",
"New Product": "Yeni Ürün",
"No recharge options available": "No recharge options available",
"Order created successfully": "Sipariş başarıyla oluşturuldu",
"PayPal": "PayPal",
"Payment cancelled": "Ödeme iptal edildi",
@@ -924,10 +935,12 @@
"Please select at least one payment provider": "Lütfen en az bir ödeme sağlayıcısı seçin",
"Processing payment...": "Ödeme işleniyor...",
"Product list cannot be empty": "Ürün listesi boş olamaz",
"Product not found or invalid": "Product not found or invalid",
"Quantity": "Miktar",
"Quantity - Tooltip": "Ürün miktarı",
"Recharge options": "Yeniden yükleme seçenekleri",
"Recharge options - Tooltip": "Yeniden yükleme seçenekleri - Araç ipucu",
"Recharge products need to go to the product detail page to set custom amount": "Recharge products need to go to the product detail page to set custom amount",
"Return URL": "Dönüş URL'si",
"Return URL - Tooltip": "Satın alımdan sonra dönülecek URL",
"SKU": "SKU",
@@ -972,8 +985,6 @@
"Can signin": "Giriş yapabilir",
"Can signup": "Kayıt yapabilir",
"Can unlink": "Bağlantıyı kesebilir",
"Category": "Kategori",
"Category - Tooltip": "Bir kategori seçin",
"Channel No.": "Kanal Numarası",
"Channel No. - Tooltip": "Kanal Numarası",
"Chat ID": "Sohbet Kimliği",
@@ -990,8 +1001,6 @@
"Content - Tooltip": "İçerik - Araç ipucu",
"DB test": "Veritabanı testi",
"DB test - Tooltip": "Veritabanı testi - Araç ipucu",
"Disable SSL": "SSL'yi Devre Dışı Bırak",
"Disable SSL - Tooltip": "STMP sunucusu ile iletişim kurarken SSL protokolünü devre dışı bırakıp bırakmayacağı",
"Domain": "Alan adı",
"Domain - Tooltip": "Nesne depolama için özel alan adı",
"Edit Provider": "Sağlayıcıyı Düzenle",
@@ -1074,9 +1083,12 @@
"SP ACS URL": "SP ACS URL'si",
"SP ACS URL - Tooltip": "SP ACS URL'si",
"SP Entity ID": "SP Varlık ID'si",
"SSL mode": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip",
"Scene": "Senaryo",
"Scene - Tooltip": "Senaryo",
"Scope": "Kapsam",
"Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "Gizli erişim anahtarı",
"Secret access key - Tooltip": "Gizli erişim anahtarı",
"Secret key": "Gizli anahtar",
@@ -1238,6 +1250,9 @@
},
"syncer": {
"API Token / Password": "API Token / Password",
"AWS Access Key ID": "AWS Access Key ID",
"AWS Region": "AWS Region",
"AWS Secret Access Key": "AWS Secret Access Key",
"Admin Email": "Admin Email",
"Affiliation table": "İlişki tablosu",
"Affiliation table - Tooltip": "Çalışma biriminin veritabanı tablo adı",
@@ -1269,8 +1284,6 @@
"SSH password": "SSH şifresi",
"SSH port": "SSH portu",
"SSH user": "SSH kullanıcısı",
"SSL mode": "SSL modu",
"SSL mode - Tooltip": "SSL modu - İpucu",
"Service account key": "Service account key",
"Sync interval": "Senkronizasyon aralığı",
"Sync interval - Tooltip": "Birimi saniye cinsinden",

View File

@@ -70,6 +70,7 @@
"Enable signin session - Tooltip": "Чи підтримує Casdoor сеанс після входу в Casdoor із програми",
"Enable signup": "Увімкнути реєстрацію",
"Enable signup - Tooltip": "Чи дозволяти користувачам реєструвати новий обліковий запис",
"Existing Field": "Existing Field",
"Failed signin frozen time": "Помилка входу заморожений час",
"Failed signin frozen time - Tooltip": "Час після якого обліковий запис заморожується після невдалих спроб входу - Підказка",
"Failed signin limit": "Обмеження невдалого входу",
@@ -151,6 +152,7 @@
"Signup items - Tooltip": "Пункти, які користувачі повинні заповнити під час реєстрації нових облікових записів",
"Single Choice": "Один варіант",
"Small icon": "Маленький значок",
"Static Value": "Static Value",
"String": "Рядок",
"Tags - Tooltip": "Увійти можуть лише користувачі з тегом, указаним у тегах програми",
"The application does not allow to sign up new account": "Програма не дозволяє зареєструвати новий обліковий запис",
@@ -184,9 +186,7 @@
"Expire in years - Tooltip": "Термін дії сертифіката, років",
"New Cert": "Новий сертифікат",
"Private key": "Приватний ключ",
"Private key - Tooltip": "Закритий ключ, що відповідає сертифікату відкритого ключа",
"Scope - Tooltip": "Сценарії використання сертифіката",
"Type - Tooltip": "Тип сертифіката"
"Private key - Tooltip": "Закритий ключ, що відповідає сертифікату відкритого ключа"
},
"code": {
"Code you received": "Код, який ви отримали",
@@ -275,6 +275,7 @@
"Applications that require authentication": "Програми, які потребують автентифікації",
"Apps": "програми",
"Authorization": "Авторизація",
"Auto": "Auto",
"Avatar": "Аватар",
"Avatar - Tooltip": "Публічний аватар користувача",
"Back": "Назад",
@@ -283,6 +284,8 @@
"Cancel": "Скасувати",
"Captcha": "Капча",
"Cart": "Кошик",
"Category": "Category",
"Category - Tooltip": "Category - Tooltip",
"Cert": "сертифікат",
"Cert - Tooltip": "Сертифікат відкритого ключа, який потрібно перевірити клієнтським SDK, що відповідає цій програмі",
"Certs": "Сертифікати",
@@ -476,6 +479,8 @@
"SSH type - Tooltip": "Тип авторизації підключення SSH",
"Save": "зберегти",
"Save & Exit": "зберегти",
"Scopes": "Scopes",
"Scopes - Tooltip": "Scopes - Tooltip",
"Search": "Пошук",
"Send": "Надіслати",
"Session ID": "Ідентифікатор сеансу",
@@ -530,6 +535,7 @@
"Transactions": "транзакції",
"True": "Так",
"Type": "Тип",
"Type - Tooltip": "Type - Tooltip",
"URL": "URL",
"URL - Tooltip": "URL-посилання",
"Unknown application name": "Невідома назва програми",
@@ -737,48 +743,59 @@
"New Order": "Нове замовлення",
"Order not found": "Замовлення не знайдено",
"Pay": "Оплатити",
"Account menu - Tooltip": "Меню облікового запису - підказка",
"Admin navbar items": "Пункти панелі навігації адміністратора",
"Admin navbar items - Tooltip": "Пункти панелі навігації адміністратора - підказка",
"Balance credit": "Баланс кредиту",
"Balance credit - Tooltip": "Баланс кредиту - підказка",
"Balance currency": "Валюта балансу",
"Balance currency - Tooltip": "Валюта балансу - підказка",
"Edit Organization": "Редагувати організацію",
"Follow global theme": "Дотримуйтеся глобальної теми",
"Has privilege consent": "Має згоду на привілеї",
"Has privilege consent - Tooltip": "Заборонити додавання користувачів до вбудованої організації, якщо HasPrivilegeConsent встановлено в false",
"Has privilege consent warning": "Додавання нового користувача до організації «built-in» (вбудованої) на даний момент вимкнено. Зауважте: усі користувачі в організації «built-in» є глобальними адміністраторами в Casdoor. Дивіться документацію: https://casdoor.org/docs/basic/core-concepts#how-does-casdoor-manage-itself. Якщо ви все ще хочете створити користувача для організації «built-in», перейдіть на сторінку налаштувань організації та увімкніть опцію «Має згоду на привілеї».",
"Init score": "Початкова оцінка",
"Init score - Tooltip": "Початкові бали, нараховані користувачам під час реєстрації",
"Is profile public": "Профіль загальнодоступний",
"Is profile public - Tooltip": "Після закриття лише глобальні адміністратори або користувачі в одній організації можуть отримати доступ до сторінки профілю користувача",
"Modify rule": "Змінити правило",
"New Organization": "Нова організація",
"Optional": "Додатково",
"Org balance": "Баланс організації",
"Org balance - Tooltip": "Баланс організації - підказка",
"Password expire days": "Кількість днів дії паролю",
"Password expire days - Tooltip": "Кількість днів дії паролю - підказка",
"Prompt": "Підкажіть",
"Required": "вимагається",
"Soft deletion": "М'яке видалення",
"Soft deletion - Tooltip": "Якщо ввімкнено, видалення користувачів не призведе до їх повного видалення з бази даних. ",
"Tags": "Теги",
"Use Email as username": "Використовувати Email як ім'я користувача",
"Use Email as username - Tooltip": "Використовувати Email як ім'я користувача, якщо поле імені користувача не відображається під час реєстрації",
"User balance": "Баланс користувача",
"User balance - Tooltip": "Баланс користувача - підказка",
"User navbar items": "Пункти панелі навігації користувача",
"User navbar items - Tooltip": "Пункти панелі навігації користувача - підказка",
"User types": "Типи користувачів",
"User types - Tooltip": "Типи користувачів - підказка",
"View rule": "Переглянути правило",
"Visible": "Видно",
"Website URL": "адреса вебсайту",
"Website URL - Tooltip": "URL-адреса домашньої сторінки організації. ",
"Widget items": "Елементи віджета",
"Widget items - Tooltip": "Елементи віджета - підказка"
"Payment failed time": "Payment failed time",
"Payment time": "Payment time",
"Price": "Price",
"Return to Order List": "Return to Order List",
"Timeout time": "Timeout time",
"View Order": "View Order"
},
"organization": {
"Account items": "Account items",
"Account items - Tooltip": "Account items - Tooltip",
"Account menu": "Account menu",
"Account menu - Tooltip": "Account menu - Tooltip",
"Admin navbar items": "Admin navbar items",
"Admin navbar items - Tooltip": "Admin navbar items - Tooltip",
"Balance credit": "Balance credit",
"Balance credit - Tooltip": "Balance credit - Tooltip",
"Balance currency": "Balance currency",
"Balance currency - Tooltip": "Balance currency - Tooltip",
"Edit Organization": "Edit Organization",
"Follow global theme": "Follow global theme",
"Has privilege consent": "Has privilege consent",
"Has privilege consent - Tooltip": "Has privilege consent - Tooltip",
"Has privilege consent warning": "Has privilege consent warning",
"Init score": "Init score",
"Init score - Tooltip": "Init score - Tooltip",
"Is profile public": "Is profile public",
"Is profile public - Tooltip": "Is profile public - Tooltip",
"Modify rule": "Modify rule",
"New Organization": "New Organization",
"Optional": "Optional",
"Org balance": "Org balance",
"Org balance - Tooltip": "Org balance - Tooltip",
"Password expire days": "Password expire days",
"Password expire days - Tooltip": "Password expire days - Tooltip",
"Prompt": "Prompt",
"Required": "Required",
"Soft deletion": "Soft deletion",
"Soft deletion - Tooltip": "Soft deletion - Tooltip",
"Tags": "Tags",
"Use Email as username": "Use Email as username",
"Use Email as username - Tooltip": "Use Email as username - Tooltip",
"User balance": "User balance",
"User balance - Tooltip": "User balance - Tooltip",
"User navbar items": "User navbar items",
"User navbar items - Tooltip": "User navbar items - Tooltip",
"User types": "User types",
"User types - Tooltip": "User types - Tooltip",
"View rule": "View rule",
"Visible": "Visible",
"Website URL": "Website URL",
"Website URL - Tooltip": "Website URL - Tooltip",
"Widget items": "Widget items",
"Widget items - Tooltip": "Widget items - Tooltip"
},
"payment": {
"Confirm your invoice information": "Підтвердьте інформацію про рахунок",
@@ -856,6 +873,8 @@
},
"plan": {
"Edit Plan": "Редагувати план",
"Is exclusive": "Is exclusive",
"Is exclusive - Tooltip": "Is exclusive - Tooltip",
"New Plan": "Новий план",
"Period": "Крапка",
"Period - Tooltip": "Період",
@@ -886,6 +905,7 @@
"Amount": "Amount",
"Buy": "купити",
"Buy Product": "Купити товар",
"Cart contains invalid products, please delete them before placing an order": "Cart contains invalid products, please delete them before placing an order",
"Custom amount available": "Custom amount available",
"Custom price should be greater than zero": "Custom price should be greater than zero",
"Detail - Tooltip": "Деталь продукту",
@@ -898,9 +918,11 @@
"Image": "Зображення",
"Image - Tooltip": "Зображення товару",
"Information": "Information",
"Invalid product": "Invalid product",
"Is recharge": "Чи є поповненням",
"Is recharge - Tooltip": "Чи є поточний продукт для поповнення балансу",
"New Product": "Новий продукт",
"No recharge options available": "No recharge options available",
"Order created successfully": "Order created successfully",
"PayPal": "Пейпал",
"Payment cancelled": "Платіж скасовано",
@@ -913,10 +935,12 @@
"Please select at least one payment provider": "Please select at least one payment provider",
"Processing payment...": "Processing payment...",
"Product list cannot be empty": "Product list cannot be empty",
"Product not found or invalid": "Product not found or invalid",
"Quantity": "Кількість",
"Quantity - Tooltip": "Кількість товару",
"Recharge options": "Recharge options",
"Recharge options - Tooltip": "Варіанти поповнення - Підказка",
"Recharge products need to go to the product detail page to set custom amount": "Recharge products need to go to the product detail page to set custom amount",
"Return URL": "Повернута URL-адреса",
"Return URL - Tooltip": "URL-адреса для повернення після успішної покупки",
"SKU": "SKU",
@@ -961,8 +985,6 @@
"Can signin": "Можна ввійти",
"Can signup": "Можна записатися",
"Can unlink": "Можна від’єднати",
"Category": "Категорія",
"Category - Tooltip": "Ідентифікатор для категоризації та групування елементів або контенту, що полегшує фільтрацію та управління",
"Channel No.": "Номер каналу",
"Channel No. - Tooltip": "Унікальний номер, що ідентифікує канал зв'язку або передачі даних, використовується для розрізнення різних шляхів передачі",
"Chat ID": "Ідентифікатор чату",
@@ -979,8 +1001,6 @@
"Content - Tooltip": "Вміст підказка",
"DB test": "Тест БД",
"DB test - Tooltip": "Тест бази даних - підказка",
"Disable SSL": "Вимкнути SSL",
"Disable SSL - Tooltip": "Чи вимикати протокол SSL під час зв’язку із сервером STMP",
"Domain": "Домен",
"Domain - Tooltip": "Спеціальний домен для зберігання об'єктів",
"Edit Provider": "Редагувати постачальника",
@@ -1063,9 +1083,12 @@
"SP ACS URL": "URL ACS СП",
"SP ACS URL - Tooltip": "URL ACS СП",
"SP Entity ID": "Ідентифікатор особи SP",
"SSL mode": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip",
"Scene": "Сцена",
"Scene - Tooltip": "Сцена",
"Scope": "Область застосування",
"Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "Секретний ключ доступу",
"Secret access key - Tooltip": "Секретний ключ доступу",
"Secret key": "Секретний ключ",
@@ -1227,6 +1250,9 @@
},
"syncer": {
"API Token / Password": "API Token / Password",
"AWS Access Key ID": "AWS Access Key ID",
"AWS Region": "AWS Region",
"AWS Secret Access Key": "AWS Secret Access Key",
"Admin Email": "Admin Email",
"Affiliation table": "Таблиця приналежності",
"Affiliation table - Tooltip": "Назва робочої одиниці таблиці бази даних",
@@ -1258,8 +1284,6 @@
"SSH password": "пароль SSH",
"SSH port": "порт SSH",
"SSH user": "Користувач SSH",
"SSL mode": "Режим SSL",
"SSL mode - Tooltip": "Режим SSL підказка",
"Service account key": "Service account key",
"Sync interval": "Інтервал синхронізації",
"Sync interval - Tooltip": "Одиниця в секундах",

View File

@@ -70,6 +70,7 @@
"Enable signin session - Tooltip": "Có phải Casdoor duy trì phiên sau khi đăng nhập vào Casdoor từ ứng dụng không?",
"Enable signup": "Kích hoạt đăng ký",
"Enable signup - Tooltip": "Có cho phép người dùng đăng ký tài khoản mới không?",
"Existing Field": "Existing Field",
"Failed signin frozen time": "Thời gian khóa khi đăng nhập thất bại",
"Failed signin frozen time - Tooltip": "Thời gian tài khoản bị đóng băng sau các lần đăng nhập thất bại",
"Failed signin limit": "Giới hạn đăng nhập thất bại",
@@ -151,6 +152,7 @@
"Signup items - Tooltip": "Mục cho người dùng đề điền khi đăng nhập - Gợi ý",
"Single Choice": "Lựa chọn đơn",
"Small icon": "Biểu tượng nhỏ",
"Static Value": "Static Value",
"String": "Chuỗi",
"Tags - Tooltip": "Chỉ người dùng có thẻ được liệt kê trong thẻ ứng dụng mới có thể đăng nhập - Gợi ý",
"The application does not allow to sign up new account": "Ứng dụng không cho phép đăng ký tài khoản mới",
@@ -184,9 +186,7 @@
"Expire in years - Tooltip": "Thời hạn hiệu lực của chứng chỉ, tính bằng năm",
"New Cert": "Chứng chỉ mới",
"Private key": "Khóa bí mật",
"Private key - Tooltip": "Khóa riêng tương ứng với chứng thư khóa công khai",
"Scope - Tooltip": "Các kịch bản sử dụng của giấy chứng nhận",
"Type - Tooltip": "Loại chứng chỉ"
"Private key - Tooltip": "Khóa riêng tương ứng với chứng thư khóa công khai"
},
"code": {
"Code you received": "Mã bạn nhận được",
@@ -275,6 +275,7 @@
"Applications that require authentication": "Các ứng dụng yêu cầu xác thực",
"Apps": "Ứng dụng",
"Authorization": "Ủy quyền",
"Auto": "Auto",
"Avatar": "Ảnh đại diện",
"Avatar - Tooltip": "Ảnh đại diện công khai cho người dùng",
"Back": "Quay lại",
@@ -283,6 +284,8 @@
"Cancel": "Hủy bỏ",
"Captcha": "Mã xác nhận",
"Cart": "Giỏ hàng",
"Category": "Category",
"Category - Tooltip": "Category - Tooltip",
"Cert": "Chứng chỉ",
"Cert - Tooltip": "Chứng chỉ khóa công khai cần được xác minh bởi SDK khách hàng tương ứng với ứng dụng này",
"Certs": "Chứng chỉ",
@@ -476,6 +479,8 @@
"SSH type - Tooltip": "Loại xác thực kết nối SSH",
"Save": "Lưu",
"Save & Exit": "Lưu và Thoát",
"Scopes": "Scopes",
"Scopes - Tooltip": "Scopes - Tooltip",
"Search": "Tìm kiếm",
"Send": "Gửi",
"Session ID": "ID phiên làm việc",
@@ -530,6 +535,7 @@
"Transactions": "Giao dịch",
"True": "Đúng",
"Type": "Loại",
"Type - Tooltip": "Type - Tooltip",
"URL": "URL",
"URL - Tooltip": "Đường dẫn URL",
"Unknown application name": "Tên ứng dụng không xác định",
@@ -867,6 +873,8 @@
},
"plan": {
"Edit Plan": "Chỉnh sửa gói",
"Is exclusive": "Is exclusive",
"Is exclusive - Tooltip": "Is exclusive - Tooltip",
"New Plan": "Gói mới",
"Period": "Kỳ",
"Period - Tooltip": "Thời kỳ",
@@ -897,6 +905,7 @@
"Amount": "Số tiền",
"Buy": "Mua",
"Buy Product": "Mua sản phẩm",
"Cart contains invalid products, please delete them before placing an order": "Cart contains invalid products, please delete them before placing an order",
"Custom amount available": "Số tiền tùy chỉnh có sẵn",
"Custom price should be greater than zero": "Giá tùy chỉnh phải lớn hơn không",
"Detail - Tooltip": "Chi tiết sản phẩm",
@@ -909,9 +918,11 @@
"Image": "Ảnh",
"Image - Tooltip": "Hình ảnh sản phẩm",
"Information": "Thông tin",
"Invalid product": "Invalid product",
"Is recharge": "Là nạp tiền",
"Is recharge - Tooltip": "Sản phẩm hiện tại có phải để nạp số dư",
"New Product": "Sản phẩm mới",
"No recharge options available": "No recharge options available",
"Order created successfully": "Tạo đơn hàng thành công",
"PayPal": "PayPal",
"Payment cancelled": "Thanh toán đã bị hủy",
@@ -924,10 +935,12 @@
"Please select at least one payment provider": "Vui lòng chọn ít nhất một nhà cung cấp thanh toán",
"Processing payment...": "Đang xử lý thanh toán...",
"Product list cannot be empty": "Danh sách sản phẩm không thể trống",
"Product not found or invalid": "Product not found or invalid",
"Quantity": "Số lượng",
"Quantity - Tooltip": "Số lượng sản phẩm",
"Recharge options": "Tùy chọn nạp tiền",
"Recharge options - Tooltip": "Tùy chọn nạp tiền - Gợi ý",
"Recharge products need to go to the product detail page to set custom amount": "Recharge products need to go to the product detail page to set custom amount",
"Return URL": "Địa chỉ URL trở lại",
"Return URL - Tooltip": "URL để quay lại sau khi mua hàng thành công",
"SKU": "SKU",
@@ -972,8 +985,6 @@
"Can signin": "Đăng nhập được không?",
"Can signup": "Đăng ký có thể được thực hiện",
"Can unlink": "Không liên kết được",
"Category": "Thể loại",
"Category - Tooltip": "Chọn một danh mục",
"Channel No.": "Kênh số.",
"Channel No. - Tooltip": "Kênh Số.",
"Chat ID": "ID trò chuyện",
@@ -990,8 +1001,6 @@
"Content - Tooltip": "Gợi ý nội dung",
"DB test": "DB test",
"DB test - Tooltip": "Kiểm tra DB - Gợi ý",
"Disable SSL": "Vô hiệu hóa SSL",
"Disable SSL - Tooltip": "Có nên vô hiệu hóa giao thức SSL khi giao tiếp với máy chủ STMP hay không?",
"Domain": "Miền",
"Domain - Tooltip": "Tên miền tùy chỉnh cho lưu trữ đối tượng",
"Edit Provider": "Chỉnh sửa nhà cung cấp",
@@ -1074,9 +1083,12 @@
"SP ACS URL": "SP ACC URL",
"SP ACS URL - Tooltip": "URL ACS của SP - Gợi ý",
"SP Entity ID": "SP Entity ID: Định danh thực thể SP",
"SSL mode": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip",
"Scene": "Cảnh",
"Scene - Tooltip": "Cảnh",
"Scope": "Phạm vi",
"Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "Chìa khóa truy cập bí mật",
"Secret access key - Tooltip": "Khóa truy cập bí mật",
"Secret key": "Chìa khóa bí mật",
@@ -1238,6 +1250,9 @@
},
"syncer": {
"API Token / Password": "Mã thông báo API / Mật khẩu",
"AWS Access Key ID": "AWS Access Key ID",
"AWS Region": "AWS Region",
"AWS Secret Access Key": "AWS Secret Access Key",
"Admin Email": "Email quản trị viên",
"Affiliation table": "Bảng liên kết",
"Affiliation table - Tooltip": "Bảng liên kết - Gợi ý",
@@ -1245,11 +1260,8 @@
"Avatar base URL - Tooltip": "Địa chỉ cơ sở URL ảnh đại diện - Gợi ý",
"Bind DN": "Kết nối DN",
"Casdoor column": "Cột Casdoor",
"Casdoor column - Tooltip": "Tên cột trong bảng Casdoor tương ứng",
"Column name": "Tên cột",
"Column name - Tooltip": "Tên cột trong bảng cơ sở dữ liệu",
"Column type": "Loại cột",
"Column type - Tooltip": "Kiểu dữ liệu của cột",
"Connect successfully": "Kết nối thành công",
"Corp ID": "Mã doanh nghiệp",
"Corp secret": "Bí mật doanh nghiệp",
@@ -1268,15 +1280,11 @@
"New Syncer": "Đồng bộ mới",
"Paste your Google Workspace service account JSON key here": "Dán khóa JSON tài khoản dịch vụ Google Workspace của bạn tại đây",
"SCIM Server URL": "URL máy chủ SCIM",
"SCIM Server URL - Tooltip": "Địa chỉ URL của máy chủ SCIM",
"SSH host": "Máy chủ SSH",
"SSH password": "Mật khẩu SSH",
"SSH port": "Cổng SSH",
"SSH user": "Người dùng SSH",
"SSL mode": "Chế độ SSL",
"SSL mode - Tooltip": "Chế độ kết nối SSL với cơ sở dữ liệu",
"Service account key": "Khóa tài khoản dịch vụ",
"Service account key - Tooltip": "Khóa JSON của tài khoản dịch vụ",
"Sync interval": "Khoảng thời gian đồng bộ",
"Sync interval - Tooltip": "Khoảng thời gian giữa các lần đồng bộ (tính bằng giây)",
"Table": "Bảng",
@@ -1291,7 +1299,6 @@
"API Latency": "Độ trễ API",
"API Throughput": "Thông lượng API",
"About Casdoor": "Về Casdoor",
"About Casdoor - Tooltip": "Thông tin về nền tảng Casdoor",
"An Identity and Access Management (IAM) / Single-Sign-On (SSO) platform with web UI supporting OAuth 2.0, OIDC, SAML and CAS": "Một nền tảng Quản lý Danh tính và Truy cập (IAM) / Đăng nhập Một lần (SSO) với giao diện người dùng web hỗ trợ OAuth 2.0, OIDC, SAML và CAS",
"CPU Usage": "Sử dụng CPU",
"Community": "Cộng đồng",

View File

@@ -70,6 +70,7 @@
"Enable signin session - Tooltip": "从应用登录Casdoor后Casdoor是否保持会话",
"Enable signup": "启用注册",
"Enable signup - Tooltip": "是否允许用户注册",
"Existing Field": "Existing Field",
"Failed signin frozen time": "登入重试等待时间",
"Failed signin frozen time - Tooltip": "超过登入错误重试次数后的等待时间只有超过等待时间后用户才能重新登入默认值为15分钟设置的值需为正整数",
"Failed signin limit": "登入错误次数限制",
@@ -151,6 +152,7 @@
"Signup items - Tooltip": "注册用户注册时需要填写的项目",
"Single Choice": "单选",
"Small icon": "小图标",
"Static Value": "Static Value",
"String": "字符串",
"Tags - Tooltip": "用户的标签在应用的标签集合中时,用户才可以登录该应用",
"The application does not allow to sign up new account": "该应用不允许注册新账户",
@@ -184,9 +186,7 @@
"Expire in years - Tooltip": "公钥证书的有效期,以年为单位",
"New Cert": "添加证书",
"Private key": "私钥",
"Private key - Tooltip": "公钥证书对应的私钥",
"Scope - Tooltip": "公钥证书的使用场景",
"Type - Tooltip": "公钥证书的类型"
"Private key - Tooltip": "公钥证书对应的私钥"
},
"code": {
"Code you received": "验证码",
@@ -275,6 +275,7 @@
"Applications that require authentication": "需要认证和鉴权的应用",
"Apps": "应用列表",
"Authorization": "Casbin权限管理",
"Auto": "Auto",
"Avatar": "头像",
"Avatar - Tooltip": "公开展示的用户头像",
"Back": "返回",
@@ -283,6 +284,8 @@
"Cancel": "取消",
"Captcha": "人机验证码",
"Cart": "购物车",
"Category": "Category",
"Category - Tooltip": "Category - Tooltip",
"Cert": "证书",
"Cert - Tooltip": "该应用所对应的客户端SDK需要验证的公钥证书",
"Certs": "证书",
@@ -476,6 +479,8 @@
"SSH type - Tooltip": "SSH连接的认证类型",
"Save": "保存",
"Save & Exit": "保存 & 退出",
"Scopes": "Scopes",
"Scopes - Tooltip": "Scopes - Tooltip",
"Search": "搜索",
"Send": "发送",
"Session ID": "会话ID",
@@ -530,6 +535,7 @@
"Transactions": "交易",
"True": "真",
"Type": "类型",
"Type - Tooltip": "Type - Tooltip",
"URL": "链接",
"URL - Tooltip": "URL链接",
"Unknown application name": "未知的应用程序名称",
@@ -867,6 +873,8 @@
},
"plan": {
"Edit Plan": "编辑计划",
"Is exclusive": "Is exclusive",
"Is exclusive - Tooltip": "Is exclusive - Tooltip",
"New Plan": "添加计划",
"Period": "期限",
"Period - Tooltip": "计划对应的期限",
@@ -897,6 +905,7 @@
"Amount": "金额",
"Buy": "购买",
"Buy Product": "购买商品",
"Cart contains invalid products, please delete them before placing an order": "Cart contains invalid products, please delete them before placing an order",
"Custom amount available": "可自定义金额",
"Custom price should be greater than zero": "自定义价格必须大于零",
"Detail - Tooltip": "商品详情",
@@ -909,9 +918,11 @@
"Image": "图片",
"Image - Tooltip": "商品图片",
"Information": "信息",
"Invalid product": "Invalid product",
"Is recharge": "充值",
"Is recharge - Tooltip": "当前商品是否为充值商品",
"New Product": "添加商品",
"No recharge options available": "No recharge options available",
"Order created successfully": "订单创建成功",
"PayPal": "PayPal",
"Payment cancelled": "支付取消",
@@ -924,10 +935,12 @@
"Please select at least one payment provider": "请至少选择一个支付提供商",
"Processing payment...": "正在处理支付...",
"Product list cannot be empty": "商品列表不能为空",
"Product not found or invalid": "Product not found or invalid",
"Quantity": "库存",
"Quantity - Tooltip": "库存的数量",
"Recharge options": "充值选项",
"Recharge options - Tooltip": "预设充值金额",
"Recharge products need to go to the product detail page to set custom amount": "Recharge products need to go to the product detail page to set custom amount",
"Return URL": "返回URL",
"Return URL - Tooltip": "购买成功后返回的URL",
"SKU": "货号",
@@ -972,8 +985,6 @@
"Can signin": "可用于登录",
"Can signup": "可用于注册",
"Can unlink": "可解绑定",
"Category": "分类",
"Category - Tooltip": "用于对项目或内容进行归类分组的标识",
"Channel No.": "Channel号码",
"Channel No. - Tooltip": "标识通信或数据传输通道的唯一编号",
"Chat ID": "聊天ID",
@@ -990,8 +1001,6 @@
"Content - Tooltip": "消息、通知或文档中包含的具体信息或数据内容",
"DB test": "数据库测试",
"DB test - Tooltip": "测试数据库连接是否正常",
"Disable SSL": "禁用SSL",
"Disable SSL - Tooltip": "与STMP服务器通信时是否禁用SSL协议",
"Domain": "域名",
"Domain - Tooltip": "对象存储的自定义域名",
"Edit Provider": "编辑提供商",
@@ -1074,9 +1083,12 @@
"SP ACS URL": "SP ACS 网址",
"SP ACS URL - Tooltip": "服务提供商SP的断言消费者服务ACS地址",
"SP Entity ID": "SP 实体 ID",
"SSL mode": "SSL mode",
"SSL mode - Tooltip": "SSL mode - Tooltip",
"Scene": "场景",
"Scene - Tooltip": "表示功能或操作适用的具体业务场景,用于适配不同场景下的逻辑处理",
"Scope": "范围",
"Scope - Tooltip": "Scope - Tooltip",
"Secret access key": "秘密访问密钥",
"Secret access key - Tooltip": "与访问密钥配套的私密密钥",
"Secret key": "密钥",
@@ -1238,6 +1250,9 @@
},
"syncer": {
"API Token / Password": "API Token / Password",
"AWS Access Key ID": "AWS Access Key ID",
"AWS Region": "AWS Region",
"AWS Secret Access Key": "AWS Secret Access Key",
"Admin Email": "Admin Email",
"Affiliation table": "工作单位表",
"Affiliation table - Tooltip": "工作单位的数据库表名",
@@ -1269,8 +1284,6 @@
"SSH password": "SSH密码",
"SSH port": "SSH端口",
"SSH user": "SSH用户",
"SSL mode": "SSL模式",
"SSL mode - Tooltip": "连接数据库采用哪种SSL模式",
"Service account key": "Service account key",
"Sync interval": "同步间隔",
"Sync interval - Tooltip": "单位为秒",

View File

@@ -21,7 +21,7 @@ class CartTable extends React.Component {
render() {
const columns = [
{
title: i18next.t("product:Name"),
title: i18next.t("general:Name"),
dataIndex: "displayName",
key: "displayName",
width: "200px",
@@ -43,7 +43,7 @@ class CartTable extends React.Component {
},
},
{
title: i18next.t("product:Price"),
title: i18next.t("order:Price"),
dataIndex: "price",
key: "price",
width: "120px",

View File

@@ -104,7 +104,7 @@ class ProviderTable extends React.Component {
},
},
{
title: i18next.t("provider:Category"),
title: i18next.t("general:Category"),
dataIndex: "category",
key: "category",
width: "100px",

166
web/src/table/ScopeTable.js Normal file
View File

@@ -0,0 +1,166 @@
// Copyright 2026 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 React from "react";
import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Input, Table, Tooltip} from "antd";
import * as Setting from "../Setting";
import i18next from "i18next";
class ScopeTable extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
};
}
updateTable(table) {
this.props.onUpdateTable(table);
}
updateField(table, index, key, value) {
table[index][key] = value;
this.updateTable(table);
}
addRow(table) {
const row = {name: "", displayName: "", description: ""};
if (table === undefined) {
table = [];
}
table = Setting.addRow(table, row);
this.updateTable(table);
}
deleteRow(table, i) {
table = Setting.deleteRow(table, i);
this.updateTable(table);
}
upRow(table, i) {
table = Setting.swapRow(table, i - 1, i);
this.updateTable(table);
}
downRow(table, i) {
table = Setting.swapRow(table, i, i + 1);
this.updateTable(table);
}
renderTable(table) {
if (table === null) {
return null;
}
const columns = [
{
title: i18next.t("general:Name"),
dataIndex: "name",
key: "name",
width: "25%",
render: (text, record, index) => {
return (
<Input
value={text}
placeholder="e.g., files:read"
onChange={e => {
this.updateField(table, index, "name", e.target.value);
}}
/>
);
},
},
{
title: i18next.t("general:Display name"),
dataIndex: "displayName",
key: "displayName",
width: "25%",
render: (text, record, index) => {
return (
<Input
value={text}
placeholder="e.g., Read Files"
onChange={e => {
this.updateField(table, index, "displayName", e.target.value);
}}
/>
);
},
},
{
title: i18next.t("general:Description"),
dataIndex: "description",
key: "description",
width: "40%",
render: (text, record, index) => {
return (
<Input
value={text}
placeholder="e.g., Allow reading your files and documents"
onChange={e => {
this.updateField(table, index, "description", e.target.value);
}}
/>
);
},
},
{
title: i18next.t("general:Action"),
key: "action",
width: "10%",
render: (text, record, index) => {
return (
<div>
<Tooltip placement="bottomLeft" title={i18next.t("general:Up")}>
<Button style={{marginRight: "5px"}} disabled={index === 0} icon={<UpOutlined />} size="small" onClick={() => this.upRow(table, index)} />
</Tooltip>
<Tooltip placement="topLeft" title={i18next.t("general:Down")}>
<Button style={{marginRight: "5px"}} disabled={index === table.length - 1} icon={<DownOutlined />} size="small" onClick={() => this.downRow(table, index)} />
</Tooltip>
<Tooltip placement="topLeft" title={i18next.t("general:Delete")}>
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
</Tooltip>
</div>
);
},
},
];
return (
<div>
<Table scroll={{x: "max-content"}} rowKey={(record, index) => index} columns={columns} dataSource={table} size="middle" bordered pagination={false}
title={() => (
<div>
{this.props.title}&nbsp;&nbsp;&nbsp;&nbsp;
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
</div>
)}
/>
</div>
);
}
render() {
return (
<div>
{
this.renderTable(this.props.table)
}
</div>
);
}
}
export default ScopeTable;

View File

@@ -168,7 +168,7 @@ export function getTransactionTableColumns(options = {}) {
});
columns.push({
title: i18next.t("provider:Category"),
title: i18next.t("general:Category"),
dataIndex: "category",
key: "category",
width: "120px",