forked from casdoor/casdoor
Compare commits
36 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c38a99973 | |||
| 7c26dbb7d0 | |||
| 61bc75b12e | |||
| 18a8694d28 | |||
| 8478543c6b | |||
|
|
25d8595e66 | ||
|
|
3aafa91937 | ||
|
|
0077839549 | ||
|
|
e1ee2ddee8 | ||
|
|
b93be2d3e2 | ||
|
|
77b56a2e40 | ||
|
|
c0591f316e | ||
|
|
6749d46561 | ||
|
|
a4a50f182b | ||
|
|
221d10a172 | ||
|
|
5c051ba03d | ||
|
|
c16f4d2fb5 | ||
|
|
fe185f880c | ||
|
|
b3bed1992b | ||
|
|
be38d178fd | ||
|
|
3eb164e149 | ||
|
|
6c3cd8a74b | ||
|
|
c5ab4eec59 | ||
|
|
e8170884d7 | ||
|
|
729b21e8ae | ||
|
|
bed67a1ff2 | ||
|
|
df5f5def31 | ||
|
|
76c56e9b2d | ||
|
|
f46e229d5b | ||
|
|
112be9714b | ||
|
|
9d85362a24 | ||
|
|
37e2f13d99 | ||
|
|
f35398ea5c | ||
|
|
5a5470d5a3 | ||
|
|
948fc017e1 | ||
|
|
c63184fc67 |
31
.gitea/workflows/build.yml
Normal file
31
.gitea/workflows/build.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Build & Push Docker Image
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- custom
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Login to Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: gromlab.ru
|
||||
username: ${{ secrets.CR_USER }}
|
||||
password: ${{ secrets.CR_TOKEN }}
|
||||
|
||||
- name: Build and Push
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
target: STANDARD
|
||||
push: true
|
||||
tags: |
|
||||
gromlab.ru/gromov/casdoor:latest
|
||||
gromlab.ru/gromov/casdoor:${{ github.sha }}
|
||||
@@ -19,7 +19,6 @@ RUN go mod download
|
||||
# Copy source files
|
||||
COPY . .
|
||||
|
||||
RUN go test -v -run TestGetVersionInfo ./util/system_test.go ./util/system.go ./util/variable.go
|
||||
RUN ./build.sh
|
||||
|
||||
FROM alpine:latest AS STANDARD
|
||||
|
||||
@@ -1,43 +1,37 @@
|
||||
appname = casdoor
|
||||
httpport = 8000
|
||||
runmode = dev
|
||||
copyrequestbody = true
|
||||
driverName = mysql
|
||||
dataSourceName = root:123456@tcp(localhost:3306)/
|
||||
dbName = casdoor
|
||||
tableNamePrefix =
|
||||
showSql = false
|
||||
redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
socks5Proxy = "127.0.0.1:10808"
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 0
|
||||
logPostOnly = true
|
||||
isUsernameLowered = false
|
||||
origin =
|
||||
originFrontend =
|
||||
staticBaseUrl = "https://cdn.casbin.org"
|
||||
isDemoMode = false
|
||||
batchSize = 100
|
||||
showGithubCorner = false
|
||||
forceLanguage = ""
|
||||
defaultLanguage = "en"
|
||||
aiAssistantUrl = "https://ai.casbin.com"
|
||||
defaultApplication = "app-built-in"
|
||||
maxItemsForFlatMenu = 7
|
||||
enableErrorMask = false
|
||||
enableGzip = true
|
||||
inactiveTimeoutMinutes =
|
||||
ldapServerPort = 389
|
||||
ldapsCertId = ""
|
||||
ldapsServerPort = 636
|
||||
radiusServerPort = 1812
|
||||
radiusDefaultOrganization = "built-in"
|
||||
radiusSecret = "secret"
|
||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||
logConfig = {"adapter":"file", "filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||
initDataNewOnly = false
|
||||
initDataFile = "./init_data.json"
|
||||
frontendBaseDir = "../cc_0"
|
||||
appname = casdoor
|
||||
httpport = 8000
|
||||
runmode = dev
|
||||
copyrequestbody = true
|
||||
driverName = postgres
|
||||
dataSourceName = "user=casdoor password=casdoor_dev host=localhost port=5434 sslmode=disable dbname=casdoor"
|
||||
dbName = casdoor
|
||||
tableNamePrefix =
|
||||
showSql = false
|
||||
redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
socks5Proxy = ""
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 0
|
||||
logPostOnly = true
|
||||
isUsernameLowered = false
|
||||
origin = "http://localhost:8000"
|
||||
originFrontend = "http://localhost:7001"
|
||||
staticBaseUrl = "https://cdn.casbin.org"
|
||||
isDemoMode = false
|
||||
batchSize = 100
|
||||
showGithubCorner = false
|
||||
forceLanguage = ""
|
||||
defaultLanguage = "ru"
|
||||
enableErrorMask = false
|
||||
enableGzip = true
|
||||
ldapServerPort = 389
|
||||
ldapsServerPort = 636
|
||||
radiusServerPort = 1812
|
||||
radiusDefaultOrganization = "built-in"
|
||||
radiusSecret = "secret"
|
||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||
logConfig = {"adapter":"file", "filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||
initDataNewOnly = false
|
||||
initDataFile = "./init_data.json"
|
||||
|
||||
43
conf/app.conf.orig
Normal file
43
conf/app.conf.orig
Normal file
@@ -0,0 +1,43 @@
|
||||
appname = casdoor
|
||||
httpport = 8000
|
||||
runmode = dev
|
||||
copyrequestbody = true
|
||||
driverName = mysql
|
||||
dataSourceName = root:123456@tcp(localhost:3306)/
|
||||
dbName = casdoor
|
||||
tableNamePrefix =
|
||||
showSql = false
|
||||
redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
socks5Proxy = "127.0.0.1:10808"
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 0
|
||||
logPostOnly = true
|
||||
isUsernameLowered = false
|
||||
origin =
|
||||
originFrontend =
|
||||
staticBaseUrl = "https://cdn.casbin.org"
|
||||
isDemoMode = false
|
||||
batchSize = 100
|
||||
showGithubCorner = false
|
||||
forceLanguage = ""
|
||||
defaultLanguage = "en"
|
||||
aiAssistantUrl = "https://ai.casbin.com"
|
||||
defaultApplication = "app-built-in"
|
||||
maxItemsForFlatMenu = 7
|
||||
enableErrorMask = false
|
||||
enableGzip = true
|
||||
inactiveTimeoutMinutes =
|
||||
ldapServerPort = 389
|
||||
ldapsCertId = ""
|
||||
ldapsServerPort = 636
|
||||
radiusServerPort = 1812
|
||||
radiusDefaultOrganization = "built-in"
|
||||
radiusSecret = "secret"
|
||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||
logConfig = {"adapter":"file", "filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||
initDataNewOnly = false
|
||||
initDataFile = "./init_data.json"
|
||||
frontendBaseDir = "../cc_0"
|
||||
37
conf/app.dev.conf
Normal file
37
conf/app.dev.conf
Normal file
@@ -0,0 +1,37 @@
|
||||
appname = casdoor
|
||||
httpport = 8000
|
||||
runmode = dev
|
||||
copyrequestbody = true
|
||||
driverName = postgres
|
||||
dataSourceName = "user=casdoor password=casdoor_dev host=localhost port=5434 sslmode=disable dbname=casdoor"
|
||||
dbName = casdoor
|
||||
tableNamePrefix =
|
||||
showSql = false
|
||||
redisEndpoint =
|
||||
defaultStorageProvider =
|
||||
isCloudIntranet = false
|
||||
authState = "casdoor"
|
||||
socks5Proxy = ""
|
||||
verificationCodeTimeout = 10
|
||||
initScore = 0
|
||||
logPostOnly = true
|
||||
isUsernameLowered = false
|
||||
origin = "http://localhost:8000"
|
||||
originFrontend = "http://localhost:7001"
|
||||
staticBaseUrl = "https://cdn.casbin.org"
|
||||
isDemoMode = false
|
||||
batchSize = 100
|
||||
showGithubCorner = false
|
||||
forceLanguage = ""
|
||||
defaultLanguage = "ru"
|
||||
enableErrorMask = false
|
||||
enableGzip = true
|
||||
ldapServerPort = 389
|
||||
ldapsServerPort = 636
|
||||
radiusServerPort = 1812
|
||||
radiusDefaultOrganization = "built-in"
|
||||
radiusSecret = "secret"
|
||||
quota = {"organization": -1, "user": -1, "application": -1, "provider": -1}
|
||||
logConfig = {"adapter":"file", "filename": "logs/casdoor.log", "maxdays":99999, "perm":"0770"}
|
||||
initDataNewOnly = false
|
||||
initDataFile = "./init_data.json"
|
||||
20
docker-compose.dev.yml
Normal file
20
docker-compose.dev.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
services:
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "5434:5432"
|
||||
environment:
|
||||
POSTGRES_USER: casdoor
|
||||
POSTGRES_PASSWORD: casdoor_dev
|
||||
POSTGRES_DB: casdoor
|
||||
volumes:
|
||||
- casdoor_pg_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U casdoor"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
casdoor_pg_data:
|
||||
230
i18n/locales/ru/data.json
Normal file
230
i18n/locales/ru/data.json
Normal file
@@ -0,0 +1,230 @@
|
||||
{
|
||||
"account": {
|
||||
"Failed to add user": "Не удалось добавить пользователя",
|
||||
"Get init score failed, error: %w": "Не удалось получить исходный балл, ошибка: %w",
|
||||
"The application does not allow to sign up new account": "Приложение не позволяет зарегистрироваться новому аккаунту"
|
||||
},
|
||||
"auth": {
|
||||
"Challenge method should be S256": "Метод проверки должен быть S256",
|
||||
"DeviceCode Invalid": "Неверный код устройства",
|
||||
"Failed to create user, user information is invalid: %s": "Не удалось создать пользователя, информация о пользователе недействительна: %s",
|
||||
"Failed to login in: %s": "Не удалось войти в систему: %s",
|
||||
"Invalid token": "Недействительный токен",
|
||||
"State expected: %s, but got: %s": "Ожидался статус: %s, но получен: %s",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account via %s, please use another way to sign up": "Аккаунт провайдера: %s и имя пользователя: %s (%s) не существует и не может быть зарегистрирован через %s, пожалуйста, используйте другой способ регистрации",
|
||||
"The account for provider: %s and username: %s (%s) does not exist and is not allowed to sign up as new account, please contact your IT support": "Аккаунт для провайдера: %s и имя пользователя: %s (%s) не существует и не может быть зарегистрирован как новый аккаунт. Пожалуйста, обратитесь в службу поддержки IT",
|
||||
"The account for provider: %s and username: %s (%s) is already linked to another account: %s (%s)": "Аккаунт поставщика: %s и имя пользователя: %s (%s) уже связаны с другим аккаунтом: %s (%s)",
|
||||
"The application: %s does not exist": "Приложение: %s не существует",
|
||||
"The application: %s has disabled users to signin": "Приложение: %s отключило вход пользователей",
|
||||
"The group: %s does not exist": "Группа: %s не существует",
|
||||
"The login method: login with LDAP is not enabled for the application": "Метод входа через LDAP отключен для этого приложения",
|
||||
"The login method: login with SMS is not enabled for the application": "Метод входа через SMS отключен для этого приложения",
|
||||
"The login method: login with email is not enabled for the application": "Метод входа через электронную почту отключен для этого приложения",
|
||||
"The login method: login with face is not enabled for the application": "Метод входа через распознавание лица отключен для этого приложения",
|
||||
"The login method: login with password is not enabled for the application": "Метод входа: вход с паролем не включен для приложения",
|
||||
"The order: %s does not exist": "The order: %s does not exist",
|
||||
"The organization: %s does not exist": "Организация: %s не существует",
|
||||
"The organization: %s has disabled users to signin": "Организация: %s отключила вход пользователей",
|
||||
"The plan: %s does not exist": "План: %s не существует",
|
||||
"The pricing: %s does not exist": "Тариф: %s не существует",
|
||||
"The pricing: %s does not have plan: %s": "Тариф: %s не имеет план: %s",
|
||||
"The provider: %s does not exist": "Провайдер: %s не существует",
|
||||
"The provider: %s is not enabled for the application": "Провайдер: %s не включен для приложения",
|
||||
"Unauthorized operation": "Несанкционированная операция",
|
||||
"Unknown authentication type (not password or provider), form = %s": "Неизвестный тип аутентификации (не пароль и не провайдер), форма = %s",
|
||||
"User's tag: %s is not listed in the application's tags": "Тег пользователя: %s отсутствует в списке тегов приложения",
|
||||
"UserCode Expired": "Срок действия кода пользователя истек",
|
||||
"UserCode Invalid": "Неверный код пользователя",
|
||||
"paid-user %s does not have active or pending subscription and the application: %s does not have default pricing": "Платный пользователь %s не имеет активной или ожидающей подписки, а приложение %s не имеет цены по умолчанию",
|
||||
"the application for user %s is not found": "Приложение для пользователя %s не найдено",
|
||||
"the organization: %s is not found": "Организация: %s не найдена"
|
||||
},
|
||||
"cas": {
|
||||
"Service %s and %s do not match": "Сервисы %s и %s не совпадают"
|
||||
},
|
||||
"check": {
|
||||
"%s does not meet the CIDR format requirements: %s": "%s не соответствует требованиям формата CIDR: %s",
|
||||
"Affiliation cannot be blank": "Принадлежность не может быть пустым значением",
|
||||
"CIDR for IP: %s should not be empty": "CIDR для IP: %s не должен быть пустым",
|
||||
"Default code does not match the code's matching rules": "Код по умолчанию не соответствует правилам соответствия кода",
|
||||
"DisplayName cannot be blank": "Имя отображения не может быть пустым",
|
||||
"DisplayName is not valid real name": "DisplayName не является действительным именем",
|
||||
"Email already exists": "Электронная почта уже существует",
|
||||
"Email cannot be empty": "Электронная почта не может быть пустой",
|
||||
"Email is invalid": "Адрес электронной почты недействительный",
|
||||
"Empty username.": "Пустое имя пользователя.",
|
||||
"Face data does not exist, cannot log in": "Данные лица отсутствуют, вход невозможен",
|
||||
"Face data mismatch": "Несоответствие данных лица",
|
||||
"Failed to parse client IP: %s": "Не удалось разобрать IP клиента: %s",
|
||||
"FirstName cannot be blank": "Имя не может быть пустым",
|
||||
"Guest users must upgrade their account by setting a username and password before they can sign in directly": "Guest users must upgrade their account by setting a username and password before they can sign in directly",
|
||||
"Invitation code cannot be blank": "Код приглашения не может быть пустым",
|
||||
"Invitation code exhausted": "Код приглашения исчерпан",
|
||||
"Invitation code is invalid": "Код приглашения недействителен",
|
||||
"Invitation code suspended": "Код приглашения приостановлен",
|
||||
"LastName cannot be blank": "Фамилия не может быть пустой",
|
||||
"Multiple accounts with same uid, please check your ldap server": "Множественные учетные записи с тем же UID. Пожалуйста, проверьте свой сервер LDAP",
|
||||
"Organization does not exist": "Организация не существует",
|
||||
"Password cannot be empty": "Пароль не может быть пустым",
|
||||
"Phone already exists": "Телефон уже существует",
|
||||
"Phone cannot be empty": "Телефон не может быть пустым",
|
||||
"Phone number is invalid": "Номер телефона является недействительным",
|
||||
"Please register using the email corresponding to the invitation code": "Пожалуйста, зарегистрируйтесь, используя электронную почту, соответствующую коду приглашения",
|
||||
"Please register using the phone corresponding to the invitation code": "Пожалуйста, зарегистрируйтесь, используя номер телефона, соответствующий коду приглашения",
|
||||
"Please register using the username corresponding to the invitation code": "Пожалуйста, зарегистрируйтесь, используя имя пользователя, соответствующее коду приглашения",
|
||||
"Session outdated, please login again": "Сессия устарела, пожалуйста, войдите снова",
|
||||
"The invitation code has already been used": "Код приглашения уже использован",
|
||||
"The password must contain at least one special character": "Пароль должен содержать хотя бы один специальный символ",
|
||||
"The password must contain at least one uppercase letter, one lowercase letter and one digit": "Пароль должен содержать хотя бы одну заглавную букву, одну строчную букву и одну цифру",
|
||||
"The password must have at least 6 characters": "Пароль должен содержать не менее 6 символов",
|
||||
"The password must have at least 8 characters": "Пароль должен содержать не менее 8 символов",
|
||||
"The password must not contain any repeated characters": "Пароль не должен содержать повторяющихся символов",
|
||||
"The user has been deleted and cannot be used to sign in, please contact the administrator": "Пользователь был удален и не может быть использован для входа, пожалуйста, свяжитесь с администратором",
|
||||
"The user is forbidden to sign in, please contact the administrator": "Пользователю запрещен вход, пожалуйста, обратитесь к администратору",
|
||||
"The user: %s doesn't exist in LDAP server": "Пользователь: %s не существует на сервере LDAP",
|
||||
"The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline.": "Имя пользователя может состоять только из буквенно-цифровых символов, нижних подчеркиваний или дефисов, не может содержать последовательные дефисы или подчеркивания, а также не может начинаться или заканчиваться на дефис или подчеркивание.",
|
||||
"The value \"%s\" for account field \"%s\" doesn't match the account item regex": "The value \"%s\" for account field \"%s\" doesn't match the account item regex",
|
||||
"The value \"%s\" for signup field \"%s\" doesn't match the signup item regex of the application \"%s\"": "The value \"%s\" for signup field \"%s\" doesn't match the signup item regex of the application \"%s\"",
|
||||
"Username already exists": "Имя пользователя уже существует",
|
||||
"Username cannot be an email address": "Имя пользователя не может быть адресом электронной почты",
|
||||
"Username cannot contain white spaces": "Имя пользователя не может содержать пробелы",
|
||||
"Username cannot start with a digit": "Имя пользователя не может начинаться с цифры",
|
||||
"Username is too long (maximum is 255 characters).": "Имя пользователя слишком длинное (максимальная длина - 255 символов).",
|
||||
"Username must have at least 2 characters": "Имя пользователя должно содержать не менее 2 символов",
|
||||
"Username supports email format. Also The username may only contain alphanumeric characters, underlines or hyphens, cannot have consecutive hyphens or underlines, and cannot begin or end with a hyphen or underline. Also pay attention to the email format.": "Имя пользователя поддерживает формат электронной почты. Также имя пользователя может содержать только буквенно-цифровые символы, подчеркивания или дефисы, не может иметь последовательных дефисов или подчеркиваний и не может начинаться или заканчиваться дефисом или подчеркиванием. Также обратите внимание на формат электронной почты.",
|
||||
"You have entered the wrong password or code too many times, please wait for %d minutes and try again": "Вы ввели неправильный пароль или код слишком много раз, пожалуйста, подождите %d минут и попробуйте снова",
|
||||
"Your IP address: %s has been banned according to the configuration of: ": "Ваш IP-адрес: %s заблокирован согласно конфигурации: ",
|
||||
"Your password has expired. Please reset your password by clicking \"Forgot password\"": "Срок действия вашего пароля истек. Пожалуйста, сбросьте пароль, нажав \"Забыли пароль\"",
|
||||
"Your region is not allow to signup by phone": "Ваш регион не разрешает регистрацию по телефону",
|
||||
"password or code is incorrect": "пароль или код неверны",
|
||||
"password or code is incorrect, you have %s remaining chances": "Неправильный пароль или код, у вас осталось %s попыток",
|
||||
"unsupported password type: %s": "неподдерживаемый тип пароля: %s"
|
||||
},
|
||||
"enforcer": {
|
||||
"the adapter: %s is not found": "адаптер: %s не найден"
|
||||
},
|
||||
"general": {
|
||||
"Failed to import groups": "Не удалось импортировать группы",
|
||||
"Failed to import users": "Не удалось импортировать пользователей",
|
||||
"Insufficient balance: new balance %v would be below credit limit %v": "Insufficient balance: new balance %v would be below credit limit %v",
|
||||
"Insufficient balance: new organization balance %v would be below credit limit %v": "Insufficient balance: new organization balance %v would be below credit limit %v",
|
||||
"Missing parameter": "Отсутствующий параметр",
|
||||
"Only admin user can specify user": "Только администратор может указать пользователя",
|
||||
"Please login first": "Пожалуйста, сначала войдите в систему",
|
||||
"The LDAP: %s does not exist": "Группа LDAP: %s не существует",
|
||||
"The organization: %s should have one application at least": "Организация: %s должна иметь хотя бы одно приложение",
|
||||
"The syncer: %s does not exist": "The syncer: %s does not exist",
|
||||
"The user: %s doesn't exist": "Пользователь %s не существует",
|
||||
"The user: %s is not found": "The user: %s is not found",
|
||||
"User is required for User category transaction": "User is required for User category transaction",
|
||||
"Wrong userId": "Неверный идентификатор пользователя",
|
||||
"don't support captchaProvider: ": "неподдерживаемый captchaProvider: ",
|
||||
"this operation is not allowed in demo mode": "эта операция недоступна в демонстрационном режиме",
|
||||
"this operation requires administrator to perform": "эта операция требует прав администратора"
|
||||
},
|
||||
"invitation": {
|
||||
"Invitation %s does not exist": "Приглашение %s не существует"
|
||||
},
|
||||
"ldap": {
|
||||
"Ldap server exist": "LDAP-сервер существует"
|
||||
},
|
||||
"link": {
|
||||
"Please link first": "Пожалуйста, сначала установите ссылку",
|
||||
"This application has no providers": "Это приложение не имеет провайдеров",
|
||||
"This application has no providers of type": "Это приложение не имеет провайдеров данного типа",
|
||||
"This provider can't be unlinked": "Этот провайдер не может быть отсоединен",
|
||||
"You are not the global admin, you can't unlink other users": "Вы не являетесь глобальным администратором, вы не можете отсоединять других пользователей",
|
||||
"You can't unlink yourself, you are not a member of any application": "Вы не можете отвязаться, так как вы не являетесь участником никакого приложения"
|
||||
},
|
||||
"organization": {
|
||||
"Only admin can modify the %s.": "Только администратор может изменять %s.",
|
||||
"The %s is immutable.": "%s неизменяемый.",
|
||||
"Unknown modify rule %s.": "Неизвестное изменение правила %s.",
|
||||
"adding a new user to the 'built-in' organization is currently disabled. Please note: all users in the 'built-in' organization are global administrators in Casdoor. Refer to the docs: https://casdoor.org/docs/basic/core-concepts#how-does-casdoor-manage-itself. If you still wish to create a user for the 'built-in' organization, go to the organization's settings page and enable the 'Has privilege consent' option.": "Добавление нового пользователя в организацию «built-in» (встроенная) в настоящее время отключено. Обратите внимание: все пользователи в организации «built-in» являются глобальными администраторами в Casdoor. См. документацию: https://casdoor.org/docs/basic/core-concepts#how-does-casdoor-manage-itself. Если вы все еще хотите создать пользователя для организации «built-in», перейдите на страницу настроек организации и включите опцию «Имеет согласие на привилегии»."
|
||||
},
|
||||
"permission": {
|
||||
"The permission: \"%s\" doesn't exist": "The permission: \"%s\" doesn't exist"
|
||||
},
|
||||
"product": {
|
||||
"Product list cannot be empty": "Product list cannot be empty"
|
||||
},
|
||||
"provider": {
|
||||
"Failed to initialize ID Verification provider": "Failed to initialize ID Verification provider",
|
||||
"Invalid application id": "Неверный идентификатор приложения",
|
||||
"No ID Verification provider configured": "No ID Verification provider configured",
|
||||
"Provider is not an ID Verification provider": "Provider is not an ID Verification provider",
|
||||
"the provider: %s does not exist": "Провайдер: %s не существует"
|
||||
},
|
||||
"resource": {
|
||||
"User is nil for tag: avatar": "Пользователь равен нулю для тега: аватар",
|
||||
"Username or fullFilePath is empty: username = %s, fullFilePath = %s": "Имя пользователя или полный путь к файлу пусты: имя_пользователя = %s, полный_путь_к_файлу = %s"
|
||||
},
|
||||
"saml": {
|
||||
"Application %s not found": "Приложение %s не найдено"
|
||||
},
|
||||
"saml_sp": {
|
||||
"provider %s's category is not SAML": "Категория провайдера %s не является SAML"
|
||||
},
|
||||
"service": {
|
||||
"Empty parameters for emailForm: %v": "Пустые параметры для emailForm: %v",
|
||||
"Invalid Email receivers: %s": "Некорректные получатели электронной почты: %s",
|
||||
"Invalid phone receivers: %s": "Некорректные получатели телефонных звонков: %s"
|
||||
},
|
||||
"session": {
|
||||
"session id %s is the current session and cannot be deleted": "session id %s is the current session and cannot be deleted"
|
||||
},
|
||||
"storage": {
|
||||
"The objectKey: %s is not allowed": "Объект «objectKey: %s» не разрешен",
|
||||
"The provider type: %s is not supported": "Тип провайдера: %s не поддерживается"
|
||||
},
|
||||
"subscription": {
|
||||
"Error": "Ошибка"
|
||||
},
|
||||
"ticket": {
|
||||
"Ticket not found": "Ticket not found"
|
||||
},
|
||||
"token": {
|
||||
"Grant_type: %s is not supported in this application": "Тип предоставления: %s не поддерживается в данном приложении",
|
||||
"Invalid application or wrong clientSecret": "Недействительное приложение или неправильный clientSecret",
|
||||
"Invalid client_id": "Недействительный идентификатор клиента",
|
||||
"Redirect URI: %s doesn't exist in the allowed Redirect URI list": "URI перенаправления: %s не существует в списке разрешенных URI перенаправления",
|
||||
"Token not found, invalid accessToken": "Токен не найден, недействительный accessToken"
|
||||
},
|
||||
"user": {
|
||||
"Display name cannot be empty": "Отображаемое имя не может быть пустым",
|
||||
"ID card information and real name are required": "ID card information and real name are required",
|
||||
"Identity verification failed": "Identity verification failed",
|
||||
"MFA email is enabled but email is empty": "MFA по электронной почте включен, но электронная почта не указана",
|
||||
"MFA phone is enabled but phone number is empty": "MFA по телефону включен, но номер телефона не указан",
|
||||
"New password cannot contain blank space.": "Новый пароль не может содержать пробелы.",
|
||||
"No application found for user": "No application found for user",
|
||||
"The new password must be different from your current password": "Новый пароль должен отличаться от текущего пароля",
|
||||
"User is already verified": "Пользователь уже подтвержден",
|
||||
"the user's owner and name should not be empty": "владелец и имя пользователя не должны быть пустыми"
|
||||
},
|
||||
"util": {
|
||||
"No application is found for userId: %s": "Не найдено заявки для пользователя с идентификатором: %s",
|
||||
"No provider for category: %s is found for application: %s": "Нет провайдера для категории: %s для приложения: %s",
|
||||
"The provider: %s is not found": "Поставщик: %s не найден"
|
||||
},
|
||||
"verification": {
|
||||
"Invalid captcha provider.": "Недействительный поставщик CAPTCHA.",
|
||||
"Phone number is invalid in your region %s": "Номер телефона недействителен в вашем регионе %s",
|
||||
"The forgot password feature is disabled": "The forgot password feature is disabled",
|
||||
"The verification code has already been used!": "Код подтверждения уже использован!",
|
||||
"The verification code has not been sent yet!": "Код подтверждения еще не был отправлен!",
|
||||
"Turing test failed.": "Тест Тьюринга не удался.",
|
||||
"Unable to get the email modify rule.": "Невозможно получить правило изменения электронной почты.",
|
||||
"Unable to get the phone modify rule.": "Невозможно получить правило изменения телефона.",
|
||||
"Unknown type": "Неизвестный тип",
|
||||
"Wrong verification code!": "Неправильный код подтверждения!",
|
||||
"You should verify your code in %d min!": "Вы должны проверить свой код через %d минут!",
|
||||
"please add a SMS provider to the \"Providers\" list for the application: %s": "Пожалуйста, добавьте SMS-провайдера в список \"Провайдеры\" для приложения: %s",
|
||||
"please add an Email provider to the \"Providers\" list for the application: %s": "пожалуйста, добавьте Email-провайдера в список \\\"Провайдеры\\\" для приложения: %s",
|
||||
"the user does not exist, please sign up first": "Пользователь не существует, пожалуйста, сначала зарегистрируйтесь"
|
||||
},
|
||||
"webauthn": {
|
||||
"Found no credentials for this user": "Учетные данные для этого пользователя не найдены",
|
||||
"Please call WebAuthnSigninBegin first": "Пожалуйста, сначала вызовите WebAuthnSigninBegin"
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,7 @@
|
||||
"pt",
|
||||
"tr",
|
||||
"pl",
|
||||
"ru",
|
||||
"uk"
|
||||
],
|
||||
"masterPassword": "",
|
||||
|
||||
@@ -134,7 +134,7 @@ func initBuiltInOrganization() bool {
|
||||
DefaultAvatar: fmt.Sprintf("%s/img/casbin.svg", conf.GetConfigString("staticBaseUrl")),
|
||||
UserTypes: []string{},
|
||||
Tags: []string{},
|
||||
Languages: []string{"en", "es", "fr", "de", "ja", "zh", "vi", "pt", "tr", "pl", "uk"},
|
||||
Languages: []string{"en", "es", "fr", "de", "ja", "zh", "vi", "pt", "tr", "pl", "ru", "uk"},
|
||||
InitScore: 2000,
|
||||
AccountItems: getBuiltInAccountItems(),
|
||||
EnableSoftDeletion: false,
|
||||
|
||||
6
package-lock.json
generated
Normal file
6
package-lock.json
generated
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "casdoor",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
@@ -3,8 +3,8 @@
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ant-design/cssinjs": "^1.23.0",
|
||||
"@ant-design/icons": "^5.6.1",
|
||||
"@ant-design/cssinjs": "^2.1.2",
|
||||
"@ant-design/icons": "^6.1.1",
|
||||
"@craco/craco": "^6.4.5",
|
||||
"@crowdin/cli": "^3.7.10",
|
||||
"@ctrl/tinycolor": "^3.5.0",
|
||||
@@ -24,8 +24,8 @@
|
||||
"@web3-onboard/sequence": "^2.0.8",
|
||||
"@web3-onboard/taho": "^2.0.5",
|
||||
"@web3-onboard/trust": "^2.0.4",
|
||||
"antd": "5.24.1",
|
||||
"antd-token-previewer": "^2.0.8",
|
||||
"antd": "6.3.5",
|
||||
"antd-token-previewer": "^3.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"codemirror": "^6.0.1",
|
||||
"cookie": "0.5.0",
|
||||
@@ -107,6 +107,10 @@
|
||||
"stylelint-config-recommended-less": "^1.0.4",
|
||||
"stylelint-config-standard": "^28.0.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@ant-design/cssinjs": "^2.1.2",
|
||||
"rc-util": "^5.43.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"src/**/*.{css,less}": [
|
||||
"stylelint --fix"
|
||||
|
||||
@@ -125,7 +125,7 @@ class AdapterListPage extends BaseListPage {
|
||||
title: i18next.t("adapter:Use same DB"),
|
||||
dataIndex: "useSameDb",
|
||||
key: "useSameDb",
|
||||
width: "120px",
|
||||
width: "130px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
@@ -148,7 +148,7 @@ class AdapterListPage extends BaseListPage {
|
||||
title: i18next.t("syncer:Database type"),
|
||||
dataIndex: "databaseType",
|
||||
key: "databaseType",
|
||||
width: "120px",
|
||||
width: "140px",
|
||||
sorter: (a, b) => a.databaseType.localeCompare(b.databaseType),
|
||||
},
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@ import {Alert, Button, ConfigProvider, Drawer, FloatButton, Layout, Result, Tool
|
||||
import {Route, Switch, withRouter} from "react-router-dom";
|
||||
import CustomGithubCorner from "./common/CustomGithubCorner";
|
||||
import * as Conf from "./Conf";
|
||||
import {shadcnThemeComponents, shadcnThemeToken} from "./shadcnTheme";
|
||||
|
||||
import * as Auth from "./auth/Auth";
|
||||
import EntryPage from "./EntryPage";
|
||||
@@ -177,7 +178,7 @@ class App extends Component {
|
||||
"/applications", "/providers", "/resources", "/certs", "/keys", // Identity
|
||||
"/roles", "/permissions", "/models", "/adapters", "/enforcers", // Authorization
|
||||
"/agents", "/servers", "/server-store", "/entries", "/sites", "/rules", // LLM AI
|
||||
"/sessions", "/records", "/tokens", "/verifications", // Logging & Auditing
|
||||
"/sessions", "/records", "/tokens", "/verifications", // Auditing
|
||||
"/products", "/orders", "/payments", "/plans", "/pricings", "/subscriptions", "/transactions", // Business
|
||||
"/sysinfo", "/forms", "/syncers", "/webhooks", "/webhook-events", "/tickets", "/swagger", // Admin
|
||||
];
|
||||
@@ -614,9 +615,11 @@ class App extends Component {
|
||||
locale={getAntdLocale(Setting.getLanguage())}
|
||||
theme={{
|
||||
token: {
|
||||
...shadcnThemeToken,
|
||||
colorPrimary: themeData.colorPrimary,
|
||||
borderRadius: themeData.borderRadius,
|
||||
},
|
||||
components: shadcnThemeComponents,
|
||||
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
|
||||
}}>
|
||||
<StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}>
|
||||
@@ -756,10 +759,12 @@ class App extends Component {
|
||||
locale={getAntdLocale(Setting.getLanguage())}
|
||||
theme={{
|
||||
token: {
|
||||
...shadcnThemeToken,
|
||||
colorPrimary: this.state.themeData.colorPrimary,
|
||||
colorInfo: this.state.themeData.colorPrimary,
|
||||
borderRadius: this.state.themeData.borderRadius,
|
||||
},
|
||||
components: shadcnThemeComponents,
|
||||
algorithm: Setting.getAlgorithm(this.state.themeAlgorithm),
|
||||
}}>
|
||||
<StyleProvider hashPriority="high" transformers={[legacyLogicalPropertiesTransformer]}>
|
||||
|
||||
@@ -49,7 +49,7 @@ img {
|
||||
justify-content: center;
|
||||
border-radius: 5px;
|
||||
width: 45px;
|
||||
height: 64px;
|
||||
height: 52px;
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -58,14 +58,34 @@ img {
|
||||
}
|
||||
}
|
||||
|
||||
.saas-hosting-btn {
|
||||
font-weight: bold;
|
||||
background-color: rgba(87, 52, 211, 0.4);
|
||||
padding: 0 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 40px;
|
||||
border-radius: 5px;
|
||||
transition: background-color 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(87, 52, 211, 0.65);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(87, 52, 211, 0.35);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
.org-select {
|
||||
display: flex;
|
||||
position: relative;
|
||||
transform: translateY(50%);
|
||||
margin: 0 10px !important;
|
||||
float: right;
|
||||
min-width: 120px;
|
||||
max-width: 180px;
|
||||
min-width: 200px;
|
||||
max-width: 360px;
|
||||
}
|
||||
|
||||
.rightDropDown {
|
||||
@@ -85,6 +105,8 @@ img {
|
||||
box-shadow: 0 1px 5px 0 rgb(51 51 51 / 14%);
|
||||
flex: 1;
|
||||
align-items: stretch;
|
||||
margin: 0 3px 3px 3px !important;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
.side-image {
|
||||
@@ -151,3 +173,12 @@ img {
|
||||
.ant-menu-horizontal {
|
||||
border-bottom: none !important;
|
||||
}
|
||||
|
||||
.ant-layout-sider-children {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ant-menu-inline {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ export let StaticBaseUrl = "https://cdn.casbin.org";
|
||||
export const InitThemeAlgorithm = true;
|
||||
export const ThemeDefault = {
|
||||
themeType: "default",
|
||||
colorPrimary: "#5734d3",
|
||||
borderRadius: 6,
|
||||
colorPrimary: "#262626",
|
||||
borderRadius: 10,
|
||||
isCompact: false,
|
||||
};
|
||||
|
||||
|
||||
@@ -164,7 +164,7 @@ class InvitationListPage extends BaseListPage {
|
||||
title: i18next.t("invitation:Used count"),
|
||||
dataIndex: "usedCount",
|
||||
key: "usedCount",
|
||||
width: "130px",
|
||||
width: "140px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("usedCount"),
|
||||
},
|
||||
|
||||
@@ -14,17 +14,19 @@
|
||||
|
||||
import * as Setting from "./Setting";
|
||||
import {Avatar, Button, Card, Drawer, Dropdown, Menu, Result, Tooltip} from "antd";
|
||||
import Sider from "antd/es/layout/Sider";
|
||||
import EnableMfaNotification from "./common/notifaction/EnableMfaNotification";
|
||||
import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
||||
import React, {useState} from "react";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import i18next from "i18next";
|
||||
import {
|
||||
AppstoreTwoTone,
|
||||
BarsOutlined, CheckCircleTwoTone, DeploymentUnitOutlined, DollarTwoTone, DownOutlined,
|
||||
HomeTwoTone,
|
||||
LockTwoTone, LogoutOutlined,
|
||||
SafetyCertificateTwoTone, SettingOutlined, SettingTwoTone,
|
||||
WalletTwoTone
|
||||
AppstoreOutlined,
|
||||
BarsOutlined, CheckCircleOutlined, DeploymentUnitOutlined, DollarOutlined, DownOutlined,
|
||||
HomeOutlined,
|
||||
LockOutlined, LogoutOutlined,
|
||||
MenuFoldOutlined, MenuUnfoldOutlined,
|
||||
SafetyCertificateOutlined, SettingOutlined,
|
||||
WalletOutlined
|
||||
} from "@ant-design/icons";
|
||||
import Dashboard from "./basic/Dashboard";
|
||||
import AppListPage from "./basic/AppListPage";
|
||||
@@ -97,6 +99,7 @@ import ThemeSelect from "./common/select/ThemeSelect";
|
||||
import OpenTour from "./common/OpenTour";
|
||||
import OrganizationSelect from "./common/select/OrganizationSelect";
|
||||
import AccountAvatar from "./account/AccountAvatar";
|
||||
import BreadcrumbBar from "./common/BreadcrumbBar";
|
||||
import {Content, Header} from "antd/es/layout/layout";
|
||||
import * as AuthBackend from "./auth/AuthBackend";
|
||||
import {clearWeb3AuthToken} from "./auth/Web3Auth";
|
||||
@@ -119,11 +122,57 @@ import SiteEditPage from "./SiteEditPage";
|
||||
import RuleListPage from "./RuleListPage";
|
||||
import RuleEditPage from "./RuleEditPage";
|
||||
|
||||
function getMenuParentKey(uri) {
|
||||
if (!uri) {return null;}
|
||||
if (uri === "/" || uri.includes("/shortcuts") || uri.includes("/apps")) {return "/home";}
|
||||
if (uri.includes("/organizations") || uri.includes("/trees") || uri.includes("/groups") || uri.includes("/users") || uri.includes("/invitations")) {return "/orgs";}
|
||||
if (uri.includes("/applications") || uri.includes("/providers") || uri.includes("/resources") || uri.includes("/certs") || uri.includes("/keys")) {return "/identity";}
|
||||
if (uri.includes("/agents") || uri.includes("/servers") || uri.includes("/entries") || uri.includes("/sites") || uri.includes("/rules")) {return "/gateway";}
|
||||
if (uri.includes("/roles") || uri.includes("/permissions") || uri.includes("/models") || uri.includes("/adapters") || uri.includes("/enforcers")) {return "/auth";}
|
||||
if (uri.includes("/records") || uri.includes("/tokens") || uri.includes("/sessions") || uri.includes("/verifications")) {return "/logs";}
|
||||
if (uri.includes("/products") || uri.includes("/orders") || uri.includes("/payments") || uri.includes("/plans") || uri.includes("/pricings") || uri.includes("/subscriptions") || uri.includes("/transactions") || uri.includes("/cart")) {return "/business";}
|
||||
if (uri.includes("/sysinfo") || uri.includes("/forms") || uri.includes("/syncers") || uri.includes("/webhooks") || uri.includes("/tickets")) {return "/admin";}
|
||||
return null;
|
||||
}
|
||||
|
||||
function ManagementPage(props) {
|
||||
const [menuVisible, setMenuVisible] = useState(false);
|
||||
const [siderCollapsed, setSiderCollapsed] = useState(() => localStorage.getItem("siderCollapsed") === "true");
|
||||
const [menuOpenKeys, setMenuOpenKeys] = useState(() => {
|
||||
const parentKey = getMenuParentKey(props.uri || location.pathname);
|
||||
return parentKey ? [parentKey] : [];
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const parentKey = getMenuParentKey(props.uri);
|
||||
if (parentKey) {
|
||||
setMenuOpenKeys(prev =>
|
||||
prev.includes(parentKey) ? prev : [...prev, parentKey]
|
||||
);
|
||||
}
|
||||
}, [props.uri]);
|
||||
const organization = props.account?.organization;
|
||||
const navItems = Setting.isLocalAdminUser(props.account) ? organization?.navItems : (organization?.userNavItems ?? []);
|
||||
const widgetItems = organization?.widgetItems;
|
||||
const currentUri = props.uri || location.pathname;
|
||||
const selectedLeafKey = "/" + (currentUri.split("/").filter(Boolean)[0] || "");
|
||||
|
||||
const isDark = props.themeAlgorithm.includes("dark");
|
||||
const textColor = isDark ? "white" : "black";
|
||||
const siderLogo = (() => {
|
||||
if (!props.account?.organization) {return Setting.getLogo(props.themeAlgorithm);}
|
||||
let logo = props.account.organization.logo || Setting.getLogo(props.themeAlgorithm);
|
||||
if (isDark && props.account.organization.logoDark) {
|
||||
logo = props.account.organization.logoDark;
|
||||
}
|
||||
return logo;
|
||||
})();
|
||||
|
||||
const toggleSider = () => {
|
||||
const next = !siderCollapsed;
|
||||
setSiderCollapsed(next);
|
||||
localStorage.setItem("siderCollapsed", String(next));
|
||||
};
|
||||
|
||||
function logout() {
|
||||
AuthBackend.logout()
|
||||
@@ -150,13 +199,13 @@ function ManagementPage(props) {
|
||||
function renderAvatar() {
|
||||
if (props.account.avatar === "") {
|
||||
return (
|
||||
<Avatar style={{backgroundColor: Setting.getAvatarColor(props.account.name), verticalAlign: "middle"}} size="large">
|
||||
<Avatar style={{backgroundColor: Setting.getAvatarColor(props.account.name), verticalAlign: "middle", marginLeft: 8}} size="large">
|
||||
{Setting.getShortName(props.account.name)}
|
||||
</Avatar>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<Avatar src={props.account.avatar} style={{verticalAlign: "middle"}} size="large"
|
||||
<Avatar src={props.account.avatar} style={{verticalAlign: "middle", marginLeft: 8}} size="large"
|
||||
icon={<AccountAvatar src={props.account.avatar} style={{verticalAlign: "middle"}} size={40} />}
|
||||
>
|
||||
{Setting.getShortName(props.account.name)}
|
||||
@@ -235,7 +284,7 @@ function ManagementPage(props) {
|
||||
Setting.getItem(<ThemeSelect themeAlgorithm={props.themeAlgorithm} onChange={props.setLogoAndThemeAlgorithm} />, "theme"),
|
||||
Setting.getItem(<LanguageSelect languages={props.account.organization.languages} />, "language"),
|
||||
Setting.getItem(Conf.AiAssistantUrl?.trim() && (
|
||||
<Tooltip title="Click to open AI assistant">
|
||||
<Tooltip title={i18next.t("general:Click to open AI assistant")}>
|
||||
<div className="select-box" onClick={props.openAiAssistant}>
|
||||
<DeploymentUnitOutlined style={{fontSize: "24px"}} />
|
||||
</div>
|
||||
@@ -245,10 +294,10 @@ function ManagementPage(props) {
|
||||
];
|
||||
|
||||
if (widgetItemsIsAll()) {
|
||||
return widgets.map(item => item.label);
|
||||
return widgets.reverse().map(item => item.label);
|
||||
}
|
||||
|
||||
return widgets.filter(item => widgetItems.includes(item.key)).map(item => item.label);
|
||||
return widgets.filter(item => widgetItems.includes(item.key)).reverse().map(item => item.label);
|
||||
}
|
||||
|
||||
function renderAccountMenu() {
|
||||
@@ -263,8 +312,13 @@ function ManagementPage(props) {
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{renderRightDropdown()}
|
||||
{renderWidgets()}
|
||||
{Setting.isLocalAdminUser(props.account) && Conf.ShowGithubCorner && !Setting.isMobile() &&
|
||||
<a href={"https://casdoor.com"} target="_blank" rel="noreferrer" style={{marginRight: "8px"}}>
|
||||
<span className="saas-hosting-btn">
|
||||
🚀 SaaS Hosting 🔥
|
||||
</span>
|
||||
</a>
|
||||
}
|
||||
{Setting.isAdminUser(props.account) && (props.uri.indexOf("/trees") === -1) &&
|
||||
<OrganizationSelect
|
||||
initValue={Setting.getOrganization()}
|
||||
@@ -276,6 +330,8 @@ function ManagementPage(props) {
|
||||
}}
|
||||
/>
|
||||
}
|
||||
{renderWidgets()}
|
||||
{renderRightDropdown()}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
@@ -288,51 +344,20 @@ function ManagementPage(props) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let textColor = "black";
|
||||
const twoToneColor = props.themeData.colorPrimary;
|
||||
|
||||
let logo = props.account.organization.logo ? props.account.organization.logo : Setting.getLogo(props.themeAlgorithm);
|
||||
if (props.themeAlgorithm.includes("dark")) {
|
||||
if (props.account.organization.logoDark) {
|
||||
logo = props.account.organization.logoDark;
|
||||
}
|
||||
textColor = "white";
|
||||
}
|
||||
|
||||
!Setting.isMobile() ? res.push({
|
||||
label:
|
||||
<Link to="/">
|
||||
<img className="logo" src={logo ?? props.logo} alt="logo" />
|
||||
</Link>,
|
||||
disabled: true, key: "logo",
|
||||
style: {
|
||||
padding: 0,
|
||||
height: "auto",
|
||||
},
|
||||
}) : null;
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/">{i18next.t("general:Home")}</Link>, "/home", <HomeTwoTone twoToneColor={twoToneColor} />, [
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/">{i18next.t("general:Home")}</Link>, "/home", <HomeOutlined />, [
|
||||
Setting.getItem(<Link to="/">{i18next.t("general:Dashboard")}</Link>, "/"),
|
||||
Setting.getItem(<Link to="/shortcuts">{i18next.t("general:Shortcuts")}</Link>, "/shortcuts"),
|
||||
Setting.getItem(<Link to="/apps">{i18next.t("general:Apps")}</Link>, "/apps"),
|
||||
]));
|
||||
|
||||
if (Setting.isLocalAdminUser(props.account) && Conf.ShowGithubCorner) {
|
||||
res.push(Setting.getItem(<a href={"https://casdoor.com"}>
|
||||
<span style={{fontWeight: "bold", backgroundColor: "rgba(87,52,211,0.4)", marginTop: "12px", paddingLeft: "5px", paddingRight: "5px", display: "flex", alignItems: "center", height: "40px", borderRadius: "5px"}}>
|
||||
🚀 SaaS Hosting 🔥
|
||||
</span>
|
||||
</a>, "#"));
|
||||
}
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/organizations">{i18next.t("general:User Management")}</Link>, "/orgs", <AppstoreTwoTone twoToneColor={twoToneColor} />, [
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/organizations">{i18next.t("general:User Management")}</Link>, "/orgs", <AppstoreOutlined />, [
|
||||
Setting.getItem(<Link to="/organizations">{i18next.t("general:Organizations")}</Link>, "/organizations"),
|
||||
Setting.getItem(<Link to="/groups">{i18next.t("general:Groups")}</Link>, "/groups"),
|
||||
Setting.getItem(<Link to="/users">{i18next.t("general:Users")}</Link>, "/users"),
|
||||
Setting.getItem(<Link to="/invitations">{i18next.t("general:Invitations")}</Link>, "/invitations"),
|
||||
]));
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/applications">{i18next.t("general:Identity")}</Link>, "/identity", <LockTwoTone twoToneColor={twoToneColor} />, [
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/applications">{i18next.t("general:Identity")}</Link>, "/identity", <LockOutlined />, [
|
||||
Setting.getItem(<Link to="/applications">{i18next.t("general:Applications")}</Link>, "/applications"),
|
||||
Setting.getItem(<Link to="/providers">{i18next.t("application:Providers")}</Link>, "/providers"),
|
||||
Setting.getItem(<Link to="/resources">{i18next.t("general:Resources")}</Link>, "/resources"),
|
||||
@@ -340,7 +365,7 @@ function ManagementPage(props) {
|
||||
Setting.getItem(<Link to="/keys">{i18next.t("general:Keys")}</Link>, "/keys"),
|
||||
]));
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/roles">{i18next.t("general:Authorization")}</Link>, "/auth", <SafetyCertificateTwoTone twoToneColor={twoToneColor} />, [
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/roles">{i18next.t("general:Authorization")}</Link>, "/auth", <SafetyCertificateOutlined />, [
|
||||
Setting.getItem(<Link to="/roles">{i18next.t("general:Roles")}</Link>, "/roles"),
|
||||
Setting.getItem(<Link to="/permissions">{i18next.t("general:Permissions")}</Link>, "/permissions"),
|
||||
Setting.getItem(<Link to="/models">{i18next.t("general:Models")}</Link>, "/models"),
|
||||
@@ -354,7 +379,7 @@ function ManagementPage(props) {
|
||||
}
|
||||
})));
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/sites">{i18next.t("general:LLM AI")}</Link>, "/gateway", <CheckCircleTwoTone twoToneColor={twoToneColor} />, [
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/sites">{i18next.t("general:LLM AI")}</Link>, "/gateway", <CheckCircleOutlined />, [
|
||||
Setting.getItem(<Link to="/agents">{i18next.t("general:Agents")}</Link>, "/agents"),
|
||||
Setting.getItem(<Link to="/servers">{i18next.t("general:MCP Servers")}</Link>, "/servers"),
|
||||
Setting.getItem(<Link to="/server-store">{i18next.t("general:MCP Store")}</Link>, "/server-store"),
|
||||
@@ -363,14 +388,14 @@ function ManagementPage(props) {
|
||||
Setting.getItem(<Link to="/rules">{i18next.t("general:Rules")}</Link>, "/rules"),
|
||||
]));
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/sessions">{i18next.t("general:Logging & Auditing")}</Link>, "/logs", <WalletTwoTone twoToneColor={twoToneColor} />, [
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/sessions">{i18next.t("general:Auditing")}</Link>, "/logs", <WalletOutlined />, [
|
||||
Setting.getItem(<Link to="/sessions">{i18next.t("general:Sessions")}</Link>, "/sessions"),
|
||||
Setting.getItem(<Link to="/records">{i18next.t("general:Records")}</Link>, "/records"),
|
||||
Setting.getItem(<Link to="/tokens">{i18next.t("general:Tokens")}</Link>, "/tokens"),
|
||||
Setting.getItem(<Link to="/verifications">{i18next.t("general:Verifications")}</Link>, "/verifications"),
|
||||
]));
|
||||
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/products">{i18next.t("general:Business & Payments")}</Link>, "/business", <DollarTwoTone twoToneColor={twoToneColor} />, [
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/products">{i18next.t("general:Business")}</Link>, "/business", <DollarOutlined />, [
|
||||
Setting.getItem(<Link to="/product-store">{i18next.t("general:Product Store")}</Link>, "/product-store"),
|
||||
Setting.getItem(<Link to="/products">{i18next.t("general:Products")}</Link>, "/products"),
|
||||
Setting.getItem(<Link to="/cart">{i18next.t("general:Cart")}</Link>, "/cart"),
|
||||
@@ -383,7 +408,7 @@ function ManagementPage(props) {
|
||||
]));
|
||||
|
||||
if (Setting.isAdminUser(props.account)) {
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/sysinfo">{i18next.t("general:Admin")}</Link>, "/admin", <SettingTwoTone twoToneColor={twoToneColor} />, [
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/sysinfo">{i18next.t("general:Admin")}</Link>, "/admin", <SettingOutlined />, [
|
||||
Setting.getItem(<Link to="/sysinfo">{i18next.t("general:System Info")}</Link>, "/sysinfo"),
|
||||
Setting.getItem(<Link to="/forms">{i18next.t("general:Forms")}</Link>, "/forms"),
|
||||
Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>, "/syncers"),
|
||||
@@ -392,7 +417,7 @@ function ManagementPage(props) {
|
||||
Setting.getItem(<Link to="/tickets">{i18next.t("general:Tickets")}</Link>, "/tickets"),
|
||||
Setting.getItem(<a target="_blank" rel="noreferrer" href={Setting.isLocalhost() ? `${Setting.ServerUrl}/swagger` : "/swagger"}>{i18next.t("general:Swagger")}</a>, "/swagger")]));
|
||||
} else {
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/syncers">{i18next.t("general:Admin")}</Link>, "/admin", <SettingTwoTone twoToneColor={twoToneColor} />, [
|
||||
res.push(Setting.getItem(<Link style={{color: textColor}} to="/syncers">{i18next.t("general:Admin")}</Link>, "/admin", <SettingOutlined />, [
|
||||
Setting.getItem(<Link to="/forms">{i18next.t("general:Forms")}</Link>, "/forms"),
|
||||
Setting.getItem(<Link to="/syncers">{i18next.t("general:Syncers")}</Link>, "/syncers"),
|
||||
Setting.getItem(<Link to="/webhooks">{i18next.t("general:Webhooks")}</Link>, "/webhooks"),
|
||||
@@ -580,52 +605,113 @@ function ManagementPage(props) {
|
||||
setMenuVisible(true);
|
||||
};
|
||||
|
||||
const siderWidth = 256;
|
||||
const siderCollapsedWidth = 80;
|
||||
const showSider = !Setting.isMobile() && !props.requiredEnableMfa;
|
||||
const contentMarginLeft = showSider ? (siderCollapsed ? siderCollapsedWidth : siderWidth) : 0;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<EnableMfaNotification account={props.account} />
|
||||
<Header style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: "0", marginBottom: "4px", backgroundColor: props.themeAlgorithm.includes("dark") ? "black" : "white"}} >
|
||||
{
|
||||
props.requiredEnableMfa || (Setting.isMobile() ? (
|
||||
<React.Fragment>
|
||||
<Drawer title={i18next.t("general:Close")} placement="left" open={menuVisible} onClose={onClose}>
|
||||
<Menu
|
||||
items={getMenuItems()}
|
||||
mode={"inline"}
|
||||
selectedKeys={[props.selectedMenuKey]}
|
||||
style={{lineHeight: "64px"}}
|
||||
onClick={onClose}
|
||||
>
|
||||
</Menu>
|
||||
</Drawer>
|
||||
<Button icon={<BarsOutlined />} onClick={showMenu} type="text">
|
||||
{i18next.t("general:Menu")}
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
// Padding 1px for Menu Item Highlight border
|
||||
<div style={{flex: 1, overflow: "hidden", paddingBottom: "1px"}}>
|
||||
<Menu
|
||||
onClick={onClose}
|
||||
items={getMenuItems()}
|
||||
mode={"horizontal"}
|
||||
selectedKeys={[props.selectedMenuKey]}
|
||||
style={{backgroundColor: props.themeAlgorithm.includes("dark") ? "black" : "white"}}
|
||||
{showSider && (
|
||||
<Sider
|
||||
collapsed={siderCollapsed}
|
||||
collapsedWidth={siderCollapsedWidth}
|
||||
width={siderWidth}
|
||||
trigger={null}
|
||||
theme={isDark ? "dark" : "light"}
|
||||
style={{
|
||||
height: "100vh",
|
||||
position: "fixed",
|
||||
left: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
zIndex: 100,
|
||||
boxShadow: "2px 0 8px rgba(0,0,0,0.08)",
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
}}
|
||||
>
|
||||
<div style={{
|
||||
height: 52,
|
||||
flexShrink: 0,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: siderCollapsed ? "center" : "flex-start",
|
||||
padding: siderCollapsed ? "0" : "0 16px 0 24px",
|
||||
overflow: "hidden",
|
||||
}}>
|
||||
<Link to="/">
|
||||
<img
|
||||
src={siderCollapsed ? (organization?.favicon || siderLogo || props.logo) : (siderLogo ?? props.logo)}
|
||||
alt="logo"
|
||||
style={{
|
||||
height: siderCollapsed ? 28 : 40,
|
||||
width: siderCollapsed ? 28 : undefined,
|
||||
maxWidth: siderCollapsed ? 28 : 160,
|
||||
objectFit: "contain",
|
||||
borderRadius: siderCollapsed ? 4 : 0,
|
||||
transition: "max-width 0.2s, height 0.2s, width 0.2s",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
<div style={{flexShrink: 0}}>
|
||||
{renderAccountMenu()}
|
||||
</div>
|
||||
</Header>
|
||||
<Content style={{display: "flex", flexDirection: "column"}} >
|
||||
{isWithoutCard() ?
|
||||
renderRouter() :
|
||||
<Card className="content-warp-card">
|
||||
{renderRouter()}
|
||||
</Card>
|
||||
}
|
||||
</Content>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="sider-menu-container" style={{flex: 1, overflow: "auto"}}>
|
||||
<Menu
|
||||
mode="inline"
|
||||
items={getMenuItems()}
|
||||
selectedKeys={[selectedLeafKey]}
|
||||
openKeys={menuOpenKeys}
|
||||
onOpenChange={setMenuOpenKeys}
|
||||
theme={isDark ? "dark" : "light"}
|
||||
style={{borderRight: 0}}
|
||||
/>
|
||||
</div>
|
||||
</Sider>
|
||||
)}
|
||||
<div style={{marginLeft: contentMarginLeft, transition: "margin-left 0.2s", display: "flex", flexDirection: "column", minHeight: "100vh"}}>
|
||||
<Header style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: "0", marginBottom: "4px", backgroundColor: isDark ? "black" : "white", position: "sticky", top: 0, zIndex: 99, boxShadow: "0 1px 4px rgba(0,0,0,0.08)", height: "52px", lineHeight: "52px"}}>
|
||||
<div style={{display: "flex", alignItems: "center"}}>
|
||||
{props.requiredEnableMfa ? null : (Setting.isMobile() ? (
|
||||
<React.Fragment>
|
||||
<Drawer title={i18next.t("general:Close")} placement="left" open={menuVisible} onClose={onClose}>
|
||||
<Menu
|
||||
items={getMenuItems()}
|
||||
mode={"inline"}
|
||||
selectedKeys={[selectedLeafKey]}
|
||||
openKeys={menuOpenKeys}
|
||||
onOpenChange={setMenuOpenKeys}
|
||||
style={{lineHeight: "48px"}}
|
||||
onClick={onClose}
|
||||
/>
|
||||
</Drawer>
|
||||
<Button icon={<BarsOutlined />} onClick={showMenu} type="text">
|
||||
{i18next.t("general:Menu")}
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<Button
|
||||
icon={siderCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
onClick={toggleSider}
|
||||
type="text"
|
||||
style={{fontSize: 16, width: 40, height: 40}}
|
||||
/>
|
||||
))}
|
||||
<BreadcrumbBar uri={currentUri} />
|
||||
</div>
|
||||
<div style={{flexShrink: 0, display: "flex", alignItems: "center"}}>
|
||||
{renderAccountMenu()}
|
||||
</div>
|
||||
</Header>
|
||||
<Content style={{display: "flex", flexDirection: "column"}}>
|
||||
{isWithoutCard() ?
|
||||
renderRouter() :
|
||||
<Card className="content-warp-card" styles={{body: {padding: 0, margin: 0}}}>
|
||||
{renderRouter()}
|
||||
</Card>
|
||||
}
|
||||
</Content>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ class OrganizationListPage extends BaseListPage {
|
||||
title: i18next.t("general:Password type"),
|
||||
dataIndex: "passwordType",
|
||||
key: "passwordType",
|
||||
width: "150px",
|
||||
width: "160px",
|
||||
sorter: true,
|
||||
filterMultiple: false,
|
||||
filters: [
|
||||
@@ -267,7 +267,7 @@ class OrganizationListPage extends BaseListPage {
|
||||
title: i18next.t("organization:User balance"),
|
||||
dataIndex: "userBalance",
|
||||
key: "userBalance",
|
||||
width: "120px",
|
||||
width: "130px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return text ?? 0;
|
||||
@@ -277,7 +277,7 @@ class OrganizationListPage extends BaseListPage {
|
||||
title: i18next.t("organization:Balance credit"),
|
||||
dataIndex: "balanceCredit",
|
||||
key: "balanceCredit",
|
||||
width: "120px",
|
||||
width: "130px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return text ?? 0;
|
||||
@@ -287,7 +287,7 @@ class OrganizationListPage extends BaseListPage {
|
||||
title: i18next.t("organization:Balance currency"),
|
||||
dataIndex: "balanceCurrency",
|
||||
key: "balanceCurrency",
|
||||
width: "140px",
|
||||
width: "160px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return text || "USD";
|
||||
|
||||
@@ -415,7 +415,7 @@ class PermissionListPage extends BaseListPage {
|
||||
dataIndex: "approveTime",
|
||||
key: "approveTime",
|
||||
filterMultiple: false,
|
||||
width: "120px",
|
||||
width: "130px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return Setting.getFormattedDate(text);
|
||||
|
||||
@@ -341,7 +341,7 @@ class ProductStorePage extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div style={{padding: "16px"}}>
|
||||
<FloatingCartButton
|
||||
itemCount={this.state.cartItemCount}
|
||||
onClick={() => this.props.history.push("/cart")}
|
||||
|
||||
@@ -72,7 +72,7 @@ class RecordListPage extends BaseListPage {
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "organization",
|
||||
key: "organization",
|
||||
width: "110px",
|
||||
width: "140px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("organization"),
|
||||
render: (text, record, index) => {
|
||||
@@ -102,7 +102,7 @@ class RecordListPage extends BaseListPage {
|
||||
title: i18next.t("general:Method"),
|
||||
dataIndex: "method",
|
||||
key: "method",
|
||||
width: "100px",
|
||||
width: "110px",
|
||||
sorter: true,
|
||||
filterMultiple: false,
|
||||
filters: [
|
||||
@@ -129,7 +129,7 @@ class RecordListPage extends BaseListPage {
|
||||
title: i18next.t("user:Language"),
|
||||
dataIndex: "language",
|
||||
key: "language",
|
||||
width: "90px",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("language"),
|
||||
},
|
||||
@@ -204,13 +204,13 @@ class RecordListPage extends BaseListPage {
|
||||
sorter: true,
|
||||
fixed: "right",
|
||||
render: (text, record, index) => (
|
||||
<Button type="link" onClick={() => {
|
||||
<Button onClick={() => {
|
||||
this.setState({
|
||||
detailRecord: record,
|
||||
detailShow: true,
|
||||
});
|
||||
}}>
|
||||
{i18next.t("general:Detail")}
|
||||
{i18next.t("general:View")}
|
||||
</Button>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -215,7 +215,7 @@ class ServerStorePage extends React.Component {
|
||||
const filteredServers = this.getFilteredOnlineServers();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{padding: "16px"}}>
|
||||
<div style={{display: "flex", gap: "8px", marginBottom: "12px"}}>
|
||||
<Input
|
||||
allowClear
|
||||
|
||||
@@ -140,6 +140,7 @@ class SessionListPage extends BaseListPage {
|
||||
|
||||
const paginationProps = {
|
||||
total: this.state.pagination.total,
|
||||
pageSize: this.state.pagination.pageSize,
|
||||
showQuickJumper: true,
|
||||
showSizeChanger: true,
|
||||
showTotal: () => i18next.t("general:{total} in total").replace("{total}", this.state.pagination.total),
|
||||
@@ -148,6 +149,11 @@ class SessionListPage extends BaseListPage {
|
||||
return (
|
||||
<div>
|
||||
<Table scroll={{x: "max-content"}} columns={columns} dataSource={sessions} rowKey={(record) => `${record.owner}/${record.name}`} size="middle" bordered pagination={paginationProps}
|
||||
title={() => (
|
||||
<div>
|
||||
{i18next.t("general:Sessions")}
|
||||
</div>
|
||||
)}
|
||||
loading={this.state.loading}
|
||||
onChange={this.handleTableChange}
|
||||
/>
|
||||
|
||||
@@ -48,6 +48,7 @@ export const Countries = [
|
||||
{label: "Português", key: "pt", country: "PT", alt: "Português"},
|
||||
{label: "Türkçe", key: "tr", country: "TR", alt: "Türkçe"},
|
||||
{label: "Polski", key: "pl", country: "PL", alt: "Polski"},
|
||||
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
||||
{label: "Українська", key: "uk", country: "UA", alt: "Українська"},
|
||||
];
|
||||
|
||||
|
||||
@@ -197,7 +197,7 @@ class SiteListPage extends BaseListPage {
|
||||
title: i18next.t("site:Other domains"),
|
||||
dataIndex: "otherDomains",
|
||||
key: "otherDomains",
|
||||
width: "120px",
|
||||
width: "140px",
|
||||
sorter: (a, b) => a.otherDomains.localeCompare(b.otherDomains),
|
||||
render: (text, record, index) => {
|
||||
return record.otherDomains.map(domain => {
|
||||
|
||||
@@ -162,7 +162,7 @@ class SyncerListPage extends BaseListPage {
|
||||
title: i18next.t("syncer:Database type"),
|
||||
dataIndex: "databaseType",
|
||||
key: "databaseType",
|
||||
width: "130px",
|
||||
width: "140px",
|
||||
sorter: (a, b) => a.databaseType.localeCompare(b.databaseType),
|
||||
},
|
||||
{
|
||||
@@ -215,7 +215,7 @@ class SyncerListPage extends BaseListPage {
|
||||
title: i18next.t("syncer:Sync interval"),
|
||||
dataIndex: "syncInterval",
|
||||
key: "syncInterval",
|
||||
width: "140px",
|
||||
width: "150px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("syncInterval"),
|
||||
},
|
||||
|
||||
@@ -109,7 +109,7 @@ class TokenListPage extends BaseListPage {
|
||||
title: i18next.t("general:Application"),
|
||||
dataIndex: "application",
|
||||
key: "application",
|
||||
width: "120px",
|
||||
width: "130px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("application"),
|
||||
render: (text, record, index) => {
|
||||
@@ -124,7 +124,7 @@ class TokenListPage extends BaseListPage {
|
||||
title: i18next.t("general:Organization"),
|
||||
dataIndex: "organization",
|
||||
key: "organization",
|
||||
width: "120px",
|
||||
width: "140px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("organization"),
|
||||
render: (text, record, index) => {
|
||||
|
||||
@@ -396,7 +396,7 @@ class UserListPage extends BaseListPage {
|
||||
title: i18next.t("application:Real name"),
|
||||
dataIndex: "realName",
|
||||
key: "realName",
|
||||
width: "120px",
|
||||
width: "130px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("realName"),
|
||||
},
|
||||
@@ -464,7 +464,7 @@ class UserListPage extends BaseListPage {
|
||||
title: i18next.t("user:Register source"),
|
||||
dataIndex: "registerSource",
|
||||
key: "registerSource",
|
||||
width: "150px",
|
||||
width: "160px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("registerSource"),
|
||||
},
|
||||
@@ -482,7 +482,7 @@ class UserListPage extends BaseListPage {
|
||||
title: i18next.t("organization:Balance credit"),
|
||||
dataIndex: "balanceCredit",
|
||||
key: "balanceCredit",
|
||||
width: "120px",
|
||||
width: "130px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return text ?? 0;
|
||||
@@ -492,7 +492,7 @@ class UserListPage extends BaseListPage {
|
||||
title: i18next.t("organization:Balance currency"),
|
||||
dataIndex: "balanceCurrency",
|
||||
key: "balanceCurrency",
|
||||
width: "140px",
|
||||
width: "160px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return text || "USD";
|
||||
@@ -514,7 +514,7 @@ class UserListPage extends BaseListPage {
|
||||
title: i18next.t("user:Is forbidden"),
|
||||
dataIndex: "isForbidden",
|
||||
key: "isForbidden",
|
||||
width: "110px",
|
||||
width: "120px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
@@ -545,9 +545,9 @@ class UserListPage extends BaseListPage {
|
||||
const disabled = (record.owner === this.props.account.owner && record.name === this.props.account.name) || (record.owner === "built-in" && record.name === "admin");
|
||||
return (
|
||||
<Space>
|
||||
<Button size={isTreePage ? "small" : "middle"} type="primary" onClick={() => {
|
||||
<Button size={isTreePage ? "small" : "middle"} onClick={() => {
|
||||
this.impersonateUser(`${record.owner}/${record.name}`);
|
||||
}}>{i18next.t("general:Impersonation")}
|
||||
}}>{i18next.t("general:Impersonate")}
|
||||
</Button>
|
||||
<Button size={isTreePage ? "small" : "middle"} type="primary" onClick={() => {
|
||||
sessionStorage.setItem("userListUrl", window.location.pathname);
|
||||
|
||||
@@ -142,7 +142,7 @@ class VerificationListPage extends BaseListPage {
|
||||
title: i18next.t("login:Verification code"),
|
||||
dataIndex: "code",
|
||||
key: "code",
|
||||
width: "150px",
|
||||
width: "160px",
|
||||
sorter: true,
|
||||
...this.getColumnSearchProps("code"),
|
||||
},
|
||||
|
||||
@@ -148,7 +148,7 @@ class WebhookListPage extends BaseListPage {
|
||||
title: i18next.t("webhook:Content type"),
|
||||
dataIndex: "contentType",
|
||||
key: "contentType",
|
||||
width: "140px",
|
||||
width: "150px",
|
||||
sorter: true,
|
||||
filterMultiple: false,
|
||||
filters: [
|
||||
@@ -171,7 +171,7 @@ class WebhookListPage extends BaseListPage {
|
||||
title: i18next.t("webhook:Is user extended"),
|
||||
dataIndex: "isUserExtended",
|
||||
key: "isUserExtended",
|
||||
width: "140px",
|
||||
width: "150px",
|
||||
sorter: true,
|
||||
render: (text, record, index) => {
|
||||
return (
|
||||
|
||||
@@ -122,8 +122,8 @@ const Dashboard = (props) => {
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center"}}>
|
||||
<Spin size="large" tip={i18next.t("login:Loading")} style={{paddingTop: "10%"}} />
|
||||
<div style={{display: "flex", justifyContent: "center", alignItems: "center", height: "calc(100vh - 120px)"}}>
|
||||
<Spin size="large" tip={i18next.t("login:Loading")} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ const ShortcutsPage = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{display: "flex", justifyContent: "center", flexDirection: "column", alignItems: "center"}}>
|
||||
<div style={{display: "flex", justifyContent: "center", flexDirection: "column", alignItems: "center", padding: "16px"}}>
|
||||
<GridCards items={getItems()} />
|
||||
</div>
|
||||
);
|
||||
|
||||
109
web/src/common/BreadcrumbBar.js
Normal file
109
web/src/common/BreadcrumbBar.js
Normal file
@@ -0,0 +1,109 @@
|
||||
// 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 {Breadcrumb} from "antd";
|
||||
import {Link} from "react-router-dom";
|
||||
import i18next from "i18next";
|
||||
|
||||
const RESOURCE_LABELS = {
|
||||
"apps": "general:Apps",
|
||||
"shortcuts": "general:Shortcuts",
|
||||
"account": "account:My Account",
|
||||
"organizations": "general:Organizations",
|
||||
"users": "general:Users",
|
||||
"groups": "general:Groups",
|
||||
"trees": "general:Groups",
|
||||
"invitations": "general:Invitations",
|
||||
"applications": "general:Applications",
|
||||
"providers": "application:Providers",
|
||||
"resources": "general:Resources",
|
||||
"certs": "general:Certs",
|
||||
"keys": "general:Keys",
|
||||
"agents": "general:Agents",
|
||||
"servers": "general:MCP Servers",
|
||||
"server-store": "general:MCP Store",
|
||||
"entries": "general:Entries",
|
||||
"sites": "general:Sites",
|
||||
"rules": "general:Rules",
|
||||
"roles": "general:Roles",
|
||||
"permissions": "general:Permissions",
|
||||
"models": "general:Models",
|
||||
"adapters": "general:Adapters",
|
||||
"enforcers": "general:Enforcers",
|
||||
"sessions": "general:Sessions",
|
||||
"records": "general:Records",
|
||||
"tokens": "general:Tokens",
|
||||
"verifications": "general:Verifications",
|
||||
"product-store": "general:Product Store",
|
||||
"products": "general:Products",
|
||||
"cart": "general:Cart",
|
||||
"orders": "general:Orders",
|
||||
"payments": "general:Payments",
|
||||
"plans": "general:Plans",
|
||||
"pricings": "general:Pricings",
|
||||
"subscriptions": "general:Subscriptions",
|
||||
"transactions": "general:Transactions",
|
||||
"sysinfo": "general:System Info",
|
||||
"forms": "general:Forms",
|
||||
"syncers": "general:Syncers",
|
||||
"webhooks": "general:Webhooks",
|
||||
"webhook-events": "general:Webhook Events",
|
||||
"tickets": "general:Tickets",
|
||||
"ldap": "general:LDAP",
|
||||
"mfa": "general:MFA",
|
||||
};
|
||||
|
||||
function buildBreadcrumbItems(uri) {
|
||||
const pathSegments = (uri || "").split("/").filter(Boolean);
|
||||
|
||||
const homeItem = {title: <Link to="/">{i18next.t("general:Home")}</Link>};
|
||||
|
||||
if (pathSegments.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rootSegment = pathSegments[0];
|
||||
const listLabelKey = RESOURCE_LABELS[rootSegment];
|
||||
if (!listLabelKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (pathSegments.length === 1) {
|
||||
return [
|
||||
homeItem,
|
||||
{title: i18next.t(listLabelKey)},
|
||||
];
|
||||
}
|
||||
|
||||
const lastSegment = pathSegments[pathSegments.length - 1];
|
||||
const lastLabelKey = RESOURCE_LABELS[lastSegment];
|
||||
const lastLabel = lastLabelKey ? i18next.t(lastLabelKey) : lastSegment;
|
||||
|
||||
return [
|
||||
homeItem,
|
||||
{title: <Link to={`/${rootSegment}`}>{i18next.t(listLabelKey)}</Link>},
|
||||
{title: lastLabel},
|
||||
];
|
||||
}
|
||||
|
||||
const BreadcrumbBar = ({uri}) => {
|
||||
const items = buildBreadcrumbItems(uri);
|
||||
if (!items) {
|
||||
return null;
|
||||
}
|
||||
return <Breadcrumb items={items} style={{marginLeft: 8}} />;
|
||||
};
|
||||
|
||||
export default BreadcrumbBar;
|
||||
@@ -61,7 +61,7 @@ export const NavItemTree = ({disabled, checkedKeys, defaultExpandedKeys, onCheck
|
||||
],
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Logging & Auditing"),
|
||||
title: i18next.t("general:Auditing"),
|
||||
key: "/sessions-top",
|
||||
children: [
|
||||
{title: i18next.t("general:Sessions"), key: "/sessions"},
|
||||
@@ -71,7 +71,7 @@ export const NavItemTree = ({disabled, checkedKeys, defaultExpandedKeys, onCheck
|
||||
],
|
||||
},
|
||||
{
|
||||
title: i18next.t("general:Business & Payments"),
|
||||
title: i18next.t("general:Business"),
|
||||
key: "/business-top",
|
||||
children: [
|
||||
{title: i18next.t("general:Products"), key: "/products"},
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import React from "react";
|
||||
import {Tooltip} from "antd";
|
||||
import {QuestionCircleOutlined} from "@ant-design/icons";
|
||||
import i18next from "i18next";
|
||||
import * as TourConfig from "../TourConfig";
|
||||
import * as Setting from "../Setting";
|
||||
|
||||
@@ -34,7 +35,7 @@ class OpenTour extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
this.canTour() ?
|
||||
<Tooltip title="Click to open tour">
|
||||
<Tooltip title={i18next.t("general:Click to open tour")}>
|
||||
<div className="select-box" style={{display: Setting.isMobile() ? "none" : null, ...this.props.style}} onClick={() => TourConfig.setIsTourVisible(true)} >
|
||||
<QuestionCircleOutlined style={{fontSize: "24px"}} />
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap");
|
||||
|
||||
html {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE/Edge */
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar {
|
||||
display: none; /* Chrome/Safari/WebKit */
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family:
|
||||
"Inter",
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
@@ -32,13 +44,19 @@ code {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.ant-table.ant-table-middle .ant-table-title,
|
||||
.ant-table.ant-table-middle .ant-table-footer,
|
||||
.ant-table.ant-table-middle thead > tr > th,
|
||||
.ant-table.ant-table-middle tbody > tr > td {
|
||||
.ant-table.ant-table-medium .ant-table-title {
|
||||
padding: 5px 8px !important;
|
||||
}
|
||||
|
||||
.ant-table.ant-table-medium .ant-table-footer,
|
||||
.ant-table.ant-table-medium .ant-table-cell {
|
||||
padding: 1px 8px !important;
|
||||
}
|
||||
|
||||
.ant-table.ant-table-medium .ant-table-thead .ant-table-cell {
|
||||
padding: 10px 8px !important;
|
||||
}
|
||||
|
||||
.ant-list-sm .ant-list-item {
|
||||
padding: 2px !important;
|
||||
}
|
||||
@@ -67,3 +85,83 @@ code {
|
||||
.no-horizontal-scroll-editor [class*="CodeMirror-scroll"] {
|
||||
overflow-x: hidden !important;
|
||||
}
|
||||
|
||||
/* Bold overrides for key UI elements */
|
||||
|
||||
/* Sidebar menu: item labels */
|
||||
.ant-menu .ant-menu-item,
|
||||
.ant-menu .ant-menu-item a,
|
||||
.ant-menu .ant-menu-submenu-title {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
/* Sidebar menu: group titles */
|
||||
.ant-menu .ant-menu-item-group-title {
|
||||
font-weight: 700 !important;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* Table: column headers (thead) */
|
||||
.ant-table .ant-table-thead > tr > th,
|
||||
.ant-table .ant-table-thead > tr > td {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
/* Table: footer */
|
||||
.ant-table .ant-table-footer {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
/* Table: title bar */
|
||||
.ant-table .ant-table-title {
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
/* Card: header title */
|
||||
.ant-card .ant-card-head-title {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
/* Descriptions: item labels */
|
||||
.ant-descriptions .ant-descriptions-item-label {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
/* Tabs: tab button labels */
|
||||
.ant-tabs .ant-tabs-tab .ant-tabs-tab-btn {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
/* Layout sider: menu item and submenu labels */
|
||||
.ant-layout-sider .ant-menu-item,
|
||||
.ant-layout-sider .ant-menu-submenu-title {
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
|
||||
/* Table loading spinner - give the overlay enough height so spinner appears centered (antd v6) */
|
||||
.ant-table-wrapper .ant-spin.ant-spin-spinning {
|
||||
min-height: 420px;
|
||||
}
|
||||
|
||||
/* Sidebar menu: selected item - darker background */
|
||||
.ant-layout-sider .ant-menu-light .ant-menu-item-selected,
|
||||
.ant-layout-sider .ant-menu-light>.ant-menu .ant-menu-item-selected {
|
||||
background-color: rgba(0, 0, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
/* Sidebar menu container: hide scrollbar but keep scroll behavior */
|
||||
.sider-menu-container {
|
||||
scrollbar-width: none; /* Firefox */
|
||||
-ms-overflow-style: none; /* IE/Edge */
|
||||
}
|
||||
.sider-menu-container::-webkit-scrollbar {
|
||||
display: none; /* Chrome/Safari/WebKit */
|
||||
}
|
||||
|
||||
/* Message popups: static API renders outside ConfigProvider, override font explicitly */
|
||||
.ant-message,
|
||||
.ant-message .ant-message-notice-content,
|
||||
.ant-message .ant-message-custom-content,
|
||||
.ant-message .ant-message-custom-content span {
|
||||
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif !important;
|
||||
}
|
||||
|
||||
1515
web/src/locales/ru/data.json
Normal file
1515
web/src/locales/ru/data.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -284,8 +284,8 @@
|
||||
"Cancel": "取消",
|
||||
"Captcha": "人机验证码",
|
||||
"Cart": "购物车",
|
||||
"Category": "Category",
|
||||
"Category - Tooltip": "Category - Tooltip",
|
||||
"Category": "类别",
|
||||
"Category - Tooltip": "所属的类别",
|
||||
"Cert": "证书",
|
||||
"Cert - Tooltip": "该应用所对应的客户端SDK需要验证的公钥证书",
|
||||
"Certs": "证书",
|
||||
@@ -347,14 +347,14 @@
|
||||
"Failed to upload": "上传失败",
|
||||
"Failed to verify": "验证失败",
|
||||
"False": "假",
|
||||
"Favicon": "组织Favicon",
|
||||
"Favicon - Tooltip": "该组织所有Casdoor页面中所使用的Favicon图标URL",
|
||||
"Favicon": "图标",
|
||||
"Favicon - Tooltip": "该组织所有Casdoor页面中所使用的Favicon图标链接",
|
||||
"Filter": "筛选",
|
||||
"First name": "名字",
|
||||
"First name - Tooltip": "用户的名字",
|
||||
"Forced redirect origin - Tooltip": "强制重定向到指定的来源",
|
||||
"Forget URL": "忘记密码URL",
|
||||
"Forget URL - Tooltip": "自定义忘记密码页面的URL,不设置时采用Casdoor默认的忘记密码页面,设置后Casdoor各类页面的忘记密码链接会跳转到该URL",
|
||||
"Forget URL": "忘记密码链接",
|
||||
"Forget URL - Tooltip": "自定义忘记密码页面的链接,不设置时采用Casdoor默认的忘记密码页面,设置后Casdoor各类页面的忘记密码链接会跳转到该URL",
|
||||
"Forms": "表单",
|
||||
"Found some texts still not translated? Please help us translate at": "发现有些文字尚未翻译?请移步这里帮我们翻译:",
|
||||
"Generate": "生成",
|
||||
|
||||
168
web/src/shadcnTheme.js
Normal file
168
web/src/shadcnTheme.js
Normal file
@@ -0,0 +1,168 @@
|
||||
// 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.
|
||||
|
||||
// Shadcn-style Ant Design theme configuration.
|
||||
// Adapted from the "shadcn" preset on https://ant.design/
|
||||
|
||||
export const shadcnThemeToken = {
|
||||
fontFamily: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif",
|
||||
colorPrimary: "#262626",
|
||||
colorSuccess: "#22c55e",
|
||||
colorWarning: "#f97316",
|
||||
colorError: "#ef4444",
|
||||
colorInfo: "#262626",
|
||||
colorTextBase: "#262626",
|
||||
colorBgBase: "#ffffff",
|
||||
colorPrimaryBg: "#f5f5f5",
|
||||
colorPrimaryBgHover: "#e5e5e5",
|
||||
colorPrimaryBorder: "#d4d4d4",
|
||||
colorPrimaryBorderHover: "#a3a3a3",
|
||||
colorPrimaryHover: "#404040",
|
||||
colorPrimaryActive: "#171717",
|
||||
colorPrimaryText: "#262626",
|
||||
colorPrimaryTextHover: "#404040",
|
||||
colorPrimaryTextActive: "#171717",
|
||||
colorSuccessBg: "#f0fdf4",
|
||||
colorSuccessBgHover: "#dcfce7",
|
||||
colorSuccessBorder: "#bbf7d0",
|
||||
colorSuccessBorderHover: "#86efac",
|
||||
colorSuccessHover: "#16a34a",
|
||||
colorSuccessActive: "#15803d",
|
||||
colorSuccessText: "#16a34a",
|
||||
colorSuccessTextHover: "#16a34a",
|
||||
colorSuccessTextActive: "#15803d",
|
||||
colorWarningBg: "#fff7ed",
|
||||
colorWarningBgHover: "#fed7aa",
|
||||
colorWarningBorder: "#fdba74",
|
||||
colorWarningBorderHover: "#fb923c",
|
||||
colorWarningHover: "#ea580c",
|
||||
colorWarningActive: "#c2410c",
|
||||
colorWarningText: "#ea580c",
|
||||
colorWarningTextHover: "#ea580c",
|
||||
colorWarningTextActive: "#c2410c",
|
||||
colorErrorBg: "#fef2f2",
|
||||
colorErrorBgHover: "#fecaca",
|
||||
colorErrorBorder: "#fca5a5",
|
||||
colorErrorBorderHover: "#f87171",
|
||||
colorErrorHover: "#dc2626",
|
||||
colorErrorActive: "#b91c1c",
|
||||
colorErrorText: "#dc2626",
|
||||
colorErrorTextHover: "#dc2626",
|
||||
colorErrorTextActive: "#b91c1c",
|
||||
colorInfoBg: "#f5f5f5",
|
||||
colorInfoBgHover: "#e5e5e5",
|
||||
colorInfoBorder: "#d4d4d4",
|
||||
colorInfoBorderHover: "#a3a3a3",
|
||||
colorInfoHover: "#404040",
|
||||
colorInfoActive: "#171717",
|
||||
colorInfoText: "#262626",
|
||||
colorInfoTextHover: "#404040",
|
||||
colorInfoTextActive: "#171717",
|
||||
colorText: "#262626",
|
||||
colorTextSecondary: "#525252",
|
||||
colorTextTertiary: "#737373",
|
||||
colorTextQuaternary: "#a3a3a3",
|
||||
colorTextDisabled: "#a3a3a3",
|
||||
colorBgContainer: "#ffffff",
|
||||
colorBgElevated: "#ffffff",
|
||||
colorBgLayout: "#fafafa",
|
||||
colorBgSpotlight: "rgba(38, 38, 38, 0.85)",
|
||||
colorBgMask: "rgba(38, 38, 38, 0.45)",
|
||||
colorBorder: "#e5e5e5",
|
||||
colorBorderSecondary: "#f5f5f5",
|
||||
borderRadius: 10,
|
||||
borderRadiusXS: 2,
|
||||
borderRadiusSM: 6,
|
||||
borderRadiusLG: 14,
|
||||
padding: 16,
|
||||
paddingSM: 12,
|
||||
paddingLG: 24,
|
||||
margin: 16,
|
||||
marginSM: 12,
|
||||
marginLG: 24,
|
||||
boxShadow: "0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1)",
|
||||
boxShadowSecondary: "0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1)",
|
||||
};
|
||||
|
||||
export const shadcnThemeComponents = {
|
||||
Button: {
|
||||
primaryShadow: "none",
|
||||
defaultShadow: "none",
|
||||
dangerShadow: "none",
|
||||
defaultBorderColor: "#e4e4e7",
|
||||
defaultColor: "#18181b",
|
||||
defaultBg: "#ffffff",
|
||||
defaultHoverBg: "#f4f4f5",
|
||||
defaultHoverBorderColor: "#d4d4d8",
|
||||
defaultHoverColor: "#18181b",
|
||||
defaultActiveBg: "#e4e4e7",
|
||||
defaultActiveBorderColor: "#d4d4d8",
|
||||
borderRadius: 6,
|
||||
},
|
||||
Input: {
|
||||
activeShadow: "none",
|
||||
hoverBorderColor: "#a1a1aa",
|
||||
activeBorderColor: "#18181b",
|
||||
borderRadius: 6,
|
||||
},
|
||||
Select: {
|
||||
optionSelectedBg: "#f4f4f5",
|
||||
optionActiveBg: "#fafafa",
|
||||
optionSelectedFontWeight: 500,
|
||||
borderRadius: 6,
|
||||
},
|
||||
Alert: {
|
||||
borderRadiusLG: 8,
|
||||
},
|
||||
Modal: {
|
||||
borderRadiusLG: 12,
|
||||
},
|
||||
Progress: {
|
||||
defaultColor: "#18181b",
|
||||
remainingColor: "#f4f4f5",
|
||||
},
|
||||
Steps: {
|
||||
iconSize: 32,
|
||||
},
|
||||
Switch: {
|
||||
trackHeight: 24,
|
||||
trackMinWidth: 44,
|
||||
innerMinMargin: 4,
|
||||
innerMaxMargin: 24,
|
||||
},
|
||||
Checkbox: {
|
||||
borderRadiusSM: 4,
|
||||
},
|
||||
Slider: {
|
||||
trackBg: "#f4f4f5",
|
||||
trackHoverBg: "#e4e4e7",
|
||||
handleSize: 18,
|
||||
handleSizeHover: 20,
|
||||
railSize: 6,
|
||||
},
|
||||
ColorPicker: {
|
||||
borderRadius: 6,
|
||||
},
|
||||
Menu: {
|
||||
itemFontSize: 14,
|
||||
groupTitleFontSize: 12,
|
||||
itemHeight: 40,
|
||||
fontWeightStrong: 600,
|
||||
},
|
||||
Table: {
|
||||
headerBg: "#fafafa",
|
||||
headerSplitColor: "#e5e5e5",
|
||||
fontWeightStrong: 600,
|
||||
},
|
||||
};
|
||||
1129
web/yarn.lock
1129
web/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user