Bandwith limits optional per user, not required

This commit is contained in:
seniorswe
2025-10-04 18:00:01 -04:00
parent 6bead38ad1
commit 0fa91c14b5
8 changed files with 50 additions and 2 deletions

View File

@@ -614,7 +614,8 @@ async def metrics_middleware(request: Request, call_next):
if username:
from utils.bandwidth_util import add_usage, _get_user
u = _get_user(username)
if u and u.get('bandwidth_limit_bytes'):
# Track usage only if not explicitly disabled and a limit is configured
if u and u.get('bandwidth_limit_bytes') and u.get('bandwidth_limit_enabled') is not False:
add_usage(username, int(bytes_in) + int(clen), u.get('bandwidth_limit_window') or 'day')
except Exception:
pass

View File

@@ -26,6 +26,7 @@ class CreateUserModel(BaseModel):
custom_attributes: Optional[dict] = Field(None, description='Custom attributes for the user', example={'custom_key': 'custom_value'})
bandwidth_limit_bytes: Optional[int] = Field(None, ge=0, description='Maximum bandwidth allowed within the window (bytes)', example=1073741824)
bandwidth_limit_window: Optional[str] = Field('day', min_length=1, max_length=10, description='Bandwidth window unit (second/minute/hour/day/month)', example='day')
bandwidth_limit_enabled: Optional[bool] = Field(None, description='Whether bandwidth limit enforcement is enabled for this user', example=True)
active: Optional[bool] = Field(True, description='Active status of the user', example=True)
ui_access: Optional[bool] = Field(False, description='UI access for the user', example=False)

View File

@@ -25,6 +25,7 @@ class UpdateUserModel(BaseModel):
custom_attributes: Optional[dict] = Field(None, description='Custom attributes for the user', example={'custom_key': 'custom_value'})
bandwidth_limit_bytes: Optional[int] = Field(None, ge=0, description='Maximum bandwidth allowed within the window (bytes)', example=1073741824)
bandwidth_limit_window: Optional[str] = Field(None, min_length=1, max_length=10, description='Bandwidth window unit (second/minute/hour/day/month)', example='day')
bandwidth_limit_enabled: Optional[bool] = Field(None, description='Whether bandwidth limit enforcement is enabled for this user', example=True)
active: Optional[bool] = Field(None, description='Active status of the user', example=True)
ui_access: Optional[bool] = Field(None, description='UI access for the user', example=False)
class Config:

View File

@@ -27,6 +27,7 @@ class UserModelResponse(BaseModel):
bandwidth_limit_window: Optional[str] = Field(None, min_length=1, max_length=10, description='Bandwidth window unit (second/minute/hour/day/month)', example='day')
bandwidth_usage_bytes: Optional[int] = Field(None, ge=0, description='Current bandwidth usage in the active window (bytes)', example=123456)
bandwidth_resets_at: Optional[int] = Field(None, description='UTC epoch seconds when the current bandwidth window resets', example=1727481600)
bandwidth_limit_enabled: Optional[bool] = Field(None, description='Whether bandwidth limit enforcement is enabled for this user', example=True)
active: Optional[bool] = Field(None, description='Active status of the user', example=True)
ui_access: Optional[bool] = Field(None, description='UI access for the user', example=False)

View File

@@ -85,7 +85,8 @@ class UserService:
).dict()
try:
limit = user.get('bandwidth_limit_bytes')
if limit and int(limit) > 0:
enabled = user.get('bandwidth_limit_enabled')
if (enabled is not False) and limit and int(limit) > 0:
window = user.get('bandwidth_limit_window') or 'day'
used = int(get_current_usage(username, window))
mapping = {

View File

@@ -81,6 +81,10 @@ async def enforce_pre_request_limit(request: Request, username: Optional[str]) -
user = _get_user(username)
if not user:
return
# Only enforce if explicitly enabled or not disabled.
# Backwards compatibility: if field is absent (None), treat as enabled when limit > 0
if user.get('bandwidth_limit_enabled') is False:
return
limit = user.get('bandwidth_limit_bytes')
if not limit or int(limit) <= 0:
return

View File

@@ -25,6 +25,7 @@ interface User {
custom_attributes: Record<string, string>
bandwidth_limit_bytes?: number
bandwidth_limit_window?: string
bandwidth_limit_enabled?: boolean
active: boolean
ui_access?: boolean
}
@@ -45,6 +46,7 @@ interface UpdateUserData {
custom_attributes?: Record<string, string>
bandwidth_limit_bytes?: number
bandwidth_limit_window?: string
bandwidth_limit_enabled?: boolean
active?: boolean
ui_access?: boolean
}
@@ -90,6 +92,7 @@ const UserDetailPage = () => {
custom_attributes: { ...parsedUser.custom_attributes },
bandwidth_limit_bytes: parsedUser.bandwidth_limit_bytes,
bandwidth_limit_window: parsedUser.bandwidth_limit_window,
bandwidth_limit_enabled: (parsedUser as any).bandwidth_limit_enabled,
active: parsedUser.active,
ui_access: parsedUser.ui_access
})
@@ -103,6 +106,7 @@ const UserDetailPage = () => {
...prev,
bandwidth_limit_bytes: refreshed.bandwidth_limit_bytes,
bandwidth_limit_window: refreshed.bandwidth_limit_window,
bandwidth_limit_enabled: (refreshed as any).bandwidth_limit_enabled,
}))
} catch {}
})()
@@ -553,6 +557,26 @@ const UserDetailPage = () => {
)}
</div>
<div className="p-6 grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Enforcement</label>
{isEditing ? (
<div className="flex items-center">
<input
type="checkbox"
checked={!!editData.bandwidth_limit_enabled}
onChange={(e) => handleInputChange('bandwidth_limit_enabled', e.target.checked)}
className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
/>
<label className="ml-2 text-sm text-gray-700 dark:text-gray-300">
Enforce bandwidth limit for this user
</label>
</div>
) : (
<span className={`badge ${(user as any).bandwidth_limit_enabled === false ? 'badge-gray' : 'badge-success'}`}>
{(user as any).bandwidth_limit_enabled === false ? 'Disabled' : 'Enabled'}
</span>
)}
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Bytes (limit)</label>
{isEditing ? (

View File

@@ -25,6 +25,7 @@ interface CreateUserData {
custom_attributes: Record<string, string>
bandwidth_limit_bytes?: number
bandwidth_limit_window?: string
bandwidth_limit_enabled?: boolean
active: boolean
ui_access: boolean
}
@@ -40,6 +41,7 @@ const AddUserPage = () => {
custom_attributes: {},
bandwidth_limit_bytes: undefined,
bandwidth_limit_window: 'day',
bandwidth_limit_enabled: false,
active: true,
ui_access: false
})
@@ -468,6 +470,19 @@ const AddUserPage = () => {
<FormHelp docHref="/docs/using-fields.html#bandwidth">Limit total bytes per user over a time window.</FormHelp>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="md:col-span-2">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Enforcement</label>
<div className="flex items-center">
<input
type="checkbox"
className="h-4 w-4 text-primary-600 focus:ring-primary-500 border-gray-300 rounded"
checked={!!formData.bandwidth_limit_enabled}
onChange={(e) => handleInputChange('bandwidth_limit_enabled', e.target.checked)}
disabled={loading}
/>
<span className="ml-2 text-sm text-gray-700 dark:text-gray-300">Enforce bandwidth limit for this user</span>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Bytes (limit)</label>
<input type="number" className="input" min={0}