feat(quotas): per-user resource limits for VM/LXC deployment
Add per-user quotas (max instances, vCPU, RAM, disk) enforced at deploy time and surfaced to users self-service. Backend: - UserQuota model (one-to-one, NULL = unlimited) + migration #28 - quota_service: usage from vm_instances cache, check_quota raises 429 - enforce in VM (templates.py) and LXC (lxc.py) deploy paths - RBAC quota:view / quota:manage, granted to admin - admin API GET/PUT /admin/api/users/{id}/quota - self-service GET /settings/api/quota - tighten VM list isolation via can_view_all_instances Frontend: - admin quota dialog in UsersPage with usage bars - quota card on Settings page (own limits) - remaining-quota hint in CreateInstanceWizard config step - useUserQuota/useSetUserQuota, useMyQuota hooks; RU/EN locales Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
from .base import *
|
||||
from .auth import Role, user_servers, User, ActiveSession, LoginAttempt, BlockedIP, UserSSHKey, PasswordResetToken
|
||||
from .auth import Role, user_servers, User, ActiveSession, LoginAttempt, BlockedIP, UserSSHKey, PasswordResetToken, UserQuota
|
||||
from .proxmox import OSTemplateGroup, OSTemplate, ProxmoxServer, VMInstance, VMSnapshotArchive
|
||||
from .ipam import IPAMNetwork, IPAMPool, IPAMAllocation, IPAMHistory
|
||||
from .tasks import TaskQueue, ProxmoxTask, DeployTask
|
||||
@@ -10,7 +10,7 @@ from .catalog import ImageMirror
|
||||
|
||||
__all__ = [
|
||||
'Base', 'utcnow',
|
||||
'Role', 'user_servers', 'User', 'ActiveSession', 'LoginAttempt', 'BlockedIP', 'UserSSHKey', 'PasswordResetToken',
|
||||
'Role', 'user_servers', 'User', 'ActiveSession', 'LoginAttempt', 'BlockedIP', 'UserSSHKey', 'PasswordResetToken', 'UserQuota',
|
||||
'OSTemplateGroup', 'OSTemplate', 'ProxmoxServer', 'VMInstance', 'VMSnapshotArchive',
|
||||
'IPAMNetwork', 'IPAMPool', 'IPAMAllocation', 'IPAMHistory',
|
||||
'TaskQueue', 'ProxmoxTask', 'DeployTask',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from .base import *
|
||||
from sqlalchemy.orm import backref
|
||||
|
||||
# Association table: User <-> ProxmoxServer (assigned servers)
|
||||
user_servers = Table(
|
||||
@@ -116,6 +117,30 @@ class User(Base):
|
||||
def __repr__(self):
|
||||
return f"<User(id={self.id}, username='{self.username}', is_admin={self.is_admin})>"
|
||||
|
||||
class UserQuota(Base):
|
||||
"""Per-user resource quota (limits).
|
||||
|
||||
A row is created only when an admin sets at least one limit. Absence of a
|
||||
row — or a ``NULL`` value in any column — means *unlimited* for that metric.
|
||||
Memory is stored in MB and disk in GB, matching the units used by the
|
||||
``vm_instances`` cache (see monitoring worker conversions).
|
||||
"""
|
||||
__tablename__ = "user_quotas"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), unique=True, nullable=False, index=True)
|
||||
max_instances = Column(Integer, nullable=True) # NULL = unlimited
|
||||
max_cores = Column(Integer, nullable=True) # sum of vCPUs
|
||||
max_memory_mb = Column(Integer, nullable=True) # sum of RAM in MB
|
||||
max_disk_gb = Column(Integer, nullable=True) # sum of disk in GB
|
||||
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
||||
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
|
||||
|
||||
user = relationship("User", backref=backref("quota", uselist=False, cascade="all, delete-orphan"))
|
||||
|
||||
def __repr__(self):
|
||||
return f"<UserQuota(user_id={self.user_id})>"
|
||||
|
||||
class PasswordResetToken(Base):
|
||||
"""One-time tokens for the self-service 'forgot password' flow.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user