mirror of
https://git.mirrors.martin98.com/https://github.com/langgenius/dify.git
synced 2025-08-14 23:06:15 +08:00
feat: priced limit (#17683)
This commit is contained in:
parent
fee51ba994
commit
b82cc1c2e8
@ -21,6 +21,7 @@ from controllers.console.error import (
|
|||||||
AccountNotFound,
|
AccountNotFound,
|
||||||
EmailSendIpLimitError,
|
EmailSendIpLimitError,
|
||||||
NotAllowedCreateWorkspace,
|
NotAllowedCreateWorkspace,
|
||||||
|
WorkspacesLimitExceeded,
|
||||||
)
|
)
|
||||||
from controllers.console.wraps import email_password_login_enabled, setup_required
|
from controllers.console.wraps import email_password_login_enabled, setup_required
|
||||||
from events.tenant_event import tenant_was_created
|
from events.tenant_event import tenant_was_created
|
||||||
@ -30,7 +31,7 @@ from models.account import Account
|
|||||||
from services.account_service import AccountService, RegisterService, TenantService
|
from services.account_service import AccountService, RegisterService, TenantService
|
||||||
from services.billing_service import BillingService
|
from services.billing_service import BillingService
|
||||||
from services.errors.account import AccountRegisterError
|
from services.errors.account import AccountRegisterError
|
||||||
from services.errors.workspace import WorkSpaceNotAllowedCreateError
|
from services.errors.workspace import WorkSpaceNotAllowedCreateError, WorkspacesLimitExceededError
|
||||||
from services.feature_service import FeatureService
|
from services.feature_service import FeatureService
|
||||||
|
|
||||||
|
|
||||||
@ -88,10 +89,15 @@ class LoginApi(Resource):
|
|||||||
# SELF_HOSTED only have one workspace
|
# SELF_HOSTED only have one workspace
|
||||||
tenants = TenantService.get_join_tenants(account)
|
tenants = TenantService.get_join_tenants(account)
|
||||||
if len(tenants) == 0:
|
if len(tenants) == 0:
|
||||||
return {
|
system_features = FeatureService.get_system_features()
|
||||||
"result": "fail",
|
|
||||||
"data": "workspace not found, please contact system admin to invite you to join in a workspace",
|
if system_features.is_allow_create_workspace and not system_features.license.workspaces.is_available():
|
||||||
}
|
raise WorkspacesLimitExceeded()
|
||||||
|
else:
|
||||||
|
return {
|
||||||
|
"result": "fail",
|
||||||
|
"data": "workspace not found, please contact system admin to invite you to join in a workspace",
|
||||||
|
}
|
||||||
|
|
||||||
token_pair = AccountService.login(account=account, ip_address=extract_remote_ip(request))
|
token_pair = AccountService.login(account=account, ip_address=extract_remote_ip(request))
|
||||||
AccountService.reset_login_error_rate_limit(args["email"])
|
AccountService.reset_login_error_rate_limit(args["email"])
|
||||||
@ -198,6 +204,9 @@ class EmailCodeLoginApi(Resource):
|
|||||||
if account:
|
if account:
|
||||||
tenant = TenantService.get_join_tenants(account)
|
tenant = TenantService.get_join_tenants(account)
|
||||||
if not tenant:
|
if not tenant:
|
||||||
|
workspaces = FeatureService.get_system_features().license.workspaces
|
||||||
|
if not workspaces.is_available():
|
||||||
|
raise WorkspacesLimitExceeded()
|
||||||
if not FeatureService.get_system_features().is_allow_create_workspace:
|
if not FeatureService.get_system_features().is_allow_create_workspace:
|
||||||
raise NotAllowedCreateWorkspace()
|
raise NotAllowedCreateWorkspace()
|
||||||
else:
|
else:
|
||||||
@ -215,6 +224,8 @@ class EmailCodeLoginApi(Resource):
|
|||||||
return NotAllowedCreateWorkspace()
|
return NotAllowedCreateWorkspace()
|
||||||
except AccountRegisterError as are:
|
except AccountRegisterError as are:
|
||||||
raise AccountInFreezeError()
|
raise AccountInFreezeError()
|
||||||
|
except WorkspacesLimitExceededError:
|
||||||
|
raise WorkspacesLimitExceeded()
|
||||||
token_pair = AccountService.login(account, ip_address=extract_remote_ip(request))
|
token_pair = AccountService.login(account, ip_address=extract_remote_ip(request))
|
||||||
AccountService.reset_login_error_rate_limit(args["email"])
|
AccountService.reset_login_error_rate_limit(args["email"])
|
||||||
return {"result": "success", "data": token_pair.model_dump()}
|
return {"result": "success", "data": token_pair.model_dump()}
|
||||||
|
@ -46,6 +46,18 @@ class NotAllowedCreateWorkspace(BaseHTTPException):
|
|||||||
code = 400
|
code = 400
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspaceMembersLimitExceeded(BaseHTTPException):
|
||||||
|
error_code = "limit_exceeded"
|
||||||
|
description = "Unable to add member because the maximum workspace's member limit was exceeded"
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspacesLimitExceeded(BaseHTTPException):
|
||||||
|
error_code = "limit_exceeded"
|
||||||
|
description = "Unable to create workspace because the maximum workspace limit was exceeded"
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
|
||||||
class AccountBannedError(BaseHTTPException):
|
class AccountBannedError(BaseHTTPException):
|
||||||
error_code = "account_banned"
|
error_code = "account_banned"
|
||||||
description = "Account is banned."
|
description = "Account is banned."
|
||||||
|
@ -6,6 +6,7 @@ from flask_restful import Resource, abort, marshal_with, reqparse # type: ignor
|
|||||||
import services
|
import services
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
|
from controllers.console.error import WorkspaceMembersLimitExceeded
|
||||||
from controllers.console.wraps import (
|
from controllers.console.wraps import (
|
||||||
account_initialization_required,
|
account_initialization_required,
|
||||||
cloud_edition_billing_resource_check,
|
cloud_edition_billing_resource_check,
|
||||||
@ -17,6 +18,7 @@ from libs.login import login_required
|
|||||||
from models.account import Account, TenantAccountRole
|
from models.account import Account, TenantAccountRole
|
||||||
from services.account_service import RegisterService, TenantService
|
from services.account_service import RegisterService, TenantService
|
||||||
from services.errors.account import AccountAlreadyInTenantError
|
from services.errors.account import AccountAlreadyInTenantError
|
||||||
|
from services.feature_service import FeatureService
|
||||||
|
|
||||||
|
|
||||||
class MemberListApi(Resource):
|
class MemberListApi(Resource):
|
||||||
@ -54,6 +56,12 @@ class MemberInviteEmailApi(Resource):
|
|||||||
inviter = current_user
|
inviter = current_user
|
||||||
invitation_results = []
|
invitation_results = []
|
||||||
console_web_url = dify_config.CONSOLE_WEB_URL
|
console_web_url = dify_config.CONSOLE_WEB_URL
|
||||||
|
|
||||||
|
workspace_members = FeatureService.get_features(tenant_id=inviter.current_tenant.id).workspace_members
|
||||||
|
|
||||||
|
if not workspace_members.is_available(len(invitee_emails)):
|
||||||
|
raise WorkspaceMembersLimitExceeded()
|
||||||
|
|
||||||
for invitee_email in invitee_emails:
|
for invitee_email in invitee_emails:
|
||||||
try:
|
try:
|
||||||
token = RegisterService.invite_new_member(
|
token = RegisterService.invite_new_member(
|
||||||
|
@ -49,7 +49,7 @@ from services.errors.account import (
|
|||||||
RoleAlreadyAssignedError,
|
RoleAlreadyAssignedError,
|
||||||
TenantNotFoundError,
|
TenantNotFoundError,
|
||||||
)
|
)
|
||||||
from services.errors.workspace import WorkSpaceNotAllowedCreateError
|
from services.errors.workspace import WorkSpaceNotAllowedCreateError, WorkspacesLimitExceededError
|
||||||
from services.feature_service import FeatureService
|
from services.feature_service import FeatureService
|
||||||
from tasks.delete_account_task import delete_account_task
|
from tasks.delete_account_task import delete_account_task
|
||||||
from tasks.mail_account_deletion_task import send_account_deletion_verification_code
|
from tasks.mail_account_deletion_task import send_account_deletion_verification_code
|
||||||
@ -599,6 +599,10 @@ class TenantService:
|
|||||||
if not FeatureService.get_system_features().is_allow_create_workspace and not is_setup:
|
if not FeatureService.get_system_features().is_allow_create_workspace and not is_setup:
|
||||||
raise WorkSpaceNotAllowedCreateError()
|
raise WorkSpaceNotAllowedCreateError()
|
||||||
|
|
||||||
|
workspaces = FeatureService.get_system_features().license.workspaces
|
||||||
|
if not workspaces.is_available():
|
||||||
|
raise WorkspacesLimitExceededError()
|
||||||
|
|
||||||
if name:
|
if name:
|
||||||
tenant = TenantService.create_tenant(name=name, is_setup=is_setup)
|
tenant = TenantService.create_tenant(name=name, is_setup=is_setup)
|
||||||
else:
|
else:
|
||||||
|
@ -17,6 +17,10 @@ class EnterpriseService:
|
|||||||
def get_info(cls):
|
def get_info(cls):
|
||||||
return EnterpriseRequest.send_request("GET", "/info")
|
return EnterpriseRequest.send_request("GET", "/info")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_workspace_info(cls, tenant_id:str):
|
||||||
|
return EnterpriseRequest.send_request("GET", f"/workspace/{tenant_id}/info")
|
||||||
|
|
||||||
class WebAppAuth:
|
class WebAppAuth:
|
||||||
@classmethod
|
@classmethod
|
||||||
def is_user_allowed_to_access_webapp(cls, user_id: str, app_code: str) -> bool:
|
def is_user_allowed_to_access_webapp(cls, user_id: str, app_code: str) -> bool:
|
||||||
|
@ -7,3 +7,7 @@ class WorkSpaceNotAllowedCreateError(BaseServiceError):
|
|||||||
|
|
||||||
class WorkSpaceNotFoundError(BaseServiceError):
|
class WorkSpaceNotFoundError(BaseServiceError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WorkspacesLimitExceededError(BaseServiceError):
|
||||||
|
pass
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from enum import StrEnum
|
from enum import StrEnum
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
|
||||||
from configs import dify_config
|
from configs import dify_config
|
||||||
from services.billing_service import BillingService
|
from services.billing_service import BillingService
|
||||||
@ -22,6 +22,32 @@ class LimitationModel(BaseModel):
|
|||||||
limit: int = 0
|
limit: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
class LicenseLimitationModel(BaseModel):
|
||||||
|
"""
|
||||||
|
- enabled: whether this limit is enforced
|
||||||
|
- size: current usage count
|
||||||
|
- limit: maximum allowed count; 0 means unlimited
|
||||||
|
"""
|
||||||
|
|
||||||
|
enabled: bool = Field(False, description="Whether this limit is currently active")
|
||||||
|
size: int = Field(0, description="Number of resources already consumed")
|
||||||
|
limit: int = Field(0, description="Maximum number of resources allowed; 0 means no limit")
|
||||||
|
|
||||||
|
def is_available(self, required: int = 1) -> bool:
|
||||||
|
"""
|
||||||
|
Determine whether the requested amount can be allocated.
|
||||||
|
|
||||||
|
Returns True if:
|
||||||
|
- this limit is not active, or
|
||||||
|
- the limit is zero (unlimited), or
|
||||||
|
- there is enough remaining quota.
|
||||||
|
"""
|
||||||
|
if not self.enabled or self.limit == 0:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return (self.limit - self.size) >= required
|
||||||
|
|
||||||
|
|
||||||
class LicenseStatus(StrEnum):
|
class LicenseStatus(StrEnum):
|
||||||
NONE = "none"
|
NONE = "none"
|
||||||
INACTIVE = "inactive"
|
INACTIVE = "inactive"
|
||||||
@ -34,6 +60,7 @@ class LicenseStatus(StrEnum):
|
|||||||
class LicenseModel(BaseModel):
|
class LicenseModel(BaseModel):
|
||||||
status: LicenseStatus = LicenseStatus.NONE
|
status: LicenseStatus = LicenseStatus.NONE
|
||||||
expired_at: str = ""
|
expired_at: str = ""
|
||||||
|
workspaces: LicenseLimitationModel = LicenseLimitationModel(enabled=False, size=0, limit=0)
|
||||||
|
|
||||||
|
|
||||||
class BrandingModel(BaseModel):
|
class BrandingModel(BaseModel):
|
||||||
@ -68,6 +95,7 @@ class FeatureModel(BaseModel):
|
|||||||
model_load_balancing_enabled: bool = False
|
model_load_balancing_enabled: bool = False
|
||||||
dataset_operator_enabled: bool = False
|
dataset_operator_enabled: bool = False
|
||||||
webapp_copyright_enabled: bool = False
|
webapp_copyright_enabled: bool = False
|
||||||
|
workspace_members: LicenseLimitationModel = LicenseLimitationModel(enabled=False, size=0, limit=0)
|
||||||
|
|
||||||
# pydantic configs
|
# pydantic configs
|
||||||
model_config = ConfigDict(protected_namespaces=())
|
model_config = ConfigDict(protected_namespaces=())
|
||||||
@ -99,6 +127,7 @@ class FeatureService:
|
|||||||
|
|
||||||
if dify_config.ENTERPRISE_ENABLED:
|
if dify_config.ENTERPRISE_ENABLED:
|
||||||
features.webapp_copyright_enabled = True
|
features.webapp_copyright_enabled = True
|
||||||
|
cls._fulfill_params_from_workspace_info(features, tenant_id)
|
||||||
|
|
||||||
return features
|
return features
|
||||||
|
|
||||||
@ -130,6 +159,14 @@ class FeatureService:
|
|||||||
features.model_load_balancing_enabled = dify_config.MODEL_LB_ENABLED
|
features.model_load_balancing_enabled = dify_config.MODEL_LB_ENABLED
|
||||||
features.dataset_operator_enabled = dify_config.DATASET_OPERATOR_ENABLED
|
features.dataset_operator_enabled = dify_config.DATASET_OPERATOR_ENABLED
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _fulfill_params_from_workspace_info(cls, features: FeatureModel, tenant_id: str):
|
||||||
|
workspace_info = EnterpriseService.get_workspace_info(tenant_id)
|
||||||
|
if "WorkspaceMembers" in workspace_info:
|
||||||
|
features.workspace_members.size = workspace_info["WorkspaceMembers"]["used"]
|
||||||
|
features.workspace_members.limit = workspace_info["WorkspaceMembers"]["limit"]
|
||||||
|
features.workspace_members.enabled = workspace_info["WorkspaceMembers"]["enabled"]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _fulfill_params_from_billing_api(cls, features: FeatureModel, tenant_id: str):
|
def _fulfill_params_from_billing_api(cls, features: FeatureModel, tenant_id: str):
|
||||||
billing_info = BillingService.get_info(tenant_id)
|
billing_info = BillingService.get_info(tenant_id)
|
||||||
@ -216,3 +253,8 @@ class FeatureService:
|
|||||||
|
|
||||||
if "expiredAt" in license_info:
|
if "expiredAt" in license_info:
|
||||||
features.license.expired_at = license_info["expiredAt"]
|
features.license.expired_at = license_info["expiredAt"]
|
||||||
|
|
||||||
|
if "workspaces" in license_info:
|
||||||
|
features.license.workspaces.enabled = license_info["workspaces"]["enabled"]
|
||||||
|
features.license.workspaces.limit = license_info["workspaces"]["limit"]
|
||||||
|
features.license.workspaces.size = license_info["workspaces"]["used"]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user