mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
refactor: ruff autofixes
This commit is contained in:
455 files changed
+12322
-14762
No files matched your search
@@ -26,7 +26,6 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ACE(BaseTable):
|
||||
|
||||
__tablename__ = "acl"
|
||||
|
||||
ace_id = Column(GUID, primary_key=True, default=generate_uuid)
|
||||
@@ -34,11 +33,11 @@ class ACE(BaseTable):
|
||||
path = Column(String)
|
||||
propagate = Column(Boolean, default=True)
|
||||
allowed = Column(Boolean, default=True)
|
||||
user_id = Column(GUID, ForeignKey('users.user_id', ondelete="CASCADE"))
|
||||
user_id = Column(GUID, ForeignKey("users.user_id", ondelete="CASCADE"))
|
||||
user = relationship("User", back_populates="acl_entries")
|
||||
group_id = Column(GUID, ForeignKey('user_groups.user_group_id', ondelete="CASCADE"))
|
||||
group_id = Column(GUID, ForeignKey("user_groups.user_group_id", ondelete="CASCADE"))
|
||||
group = relationship("UserGroup", back_populates="acl_entries")
|
||||
role_id = Column(GUID, ForeignKey('roles.role_id', ondelete="CASCADE"))
|
||||
role_id = Column(GUID, ForeignKey("roles.role_id", ondelete="CASCADE"))
|
||||
role = relationship("Role", back_populates="acl_entries")
|
||||
|
||||
__table_args__ = (
|
||||
|
||||
@@ -20,7 +20,6 @@ from .base import BaseTable, GUID
|
||||
|
||||
|
||||
class ApiKey(BaseTable):
|
||||
|
||||
__tablename__ = "api_keys"
|
||||
|
||||
api_key_id = Column(GUID, primary_key=True)
|
||||
|
||||
@@ -84,7 +84,7 @@ class ListType(TypeDecorator):
|
||||
impl = VARCHAR
|
||||
cache_ok = True
|
||||
|
||||
def __init__(self, separator=',', *args, **kwargs):
|
||||
def __init__(self, separator=",", *args, **kwargs):
|
||||
|
||||
self._separator = separator
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -92,8 +92,7 @@ class ListType(TypeDecorator):
|
||||
def process_bind_param(self, value, dialect):
|
||||
if value is not None:
|
||||
if any(self._separator in str(item) for item in value):
|
||||
raise ListException(f"List values cannot contain '{self._separator}'"
|
||||
f"Please use a different separator.")
|
||||
raise ListException(f"List values cannot contain '{self._separator}'Please use a different separator.")
|
||||
return self._separator.join(map(str, value))
|
||||
|
||||
def process_result_value(self, value, dialect):
|
||||
@@ -104,7 +103,6 @@ class ListType(TypeDecorator):
|
||||
|
||||
|
||||
class BaseTable(Base):
|
||||
|
||||
__abstract__ = True
|
||||
|
||||
created_at = Column(DateTime, server_default=func.current_timestamp())
|
||||
|
||||
@@ -21,7 +21,6 @@ from .base import BaseTable, GUID
|
||||
|
||||
|
||||
class Compute(BaseTable):
|
||||
|
||||
__tablename__ = "computes"
|
||||
|
||||
compute_id = Column(GUID, primary_key=True)
|
||||
|
||||
@@ -25,12 +25,11 @@ image_template_map = Table(
|
||||
"image_template_map",
|
||||
Base.metadata,
|
||||
Column("image_id", Integer, ForeignKey("images.image_id", ondelete="CASCADE")),
|
||||
Column("template_id", GUID, ForeignKey("templates.template_id", ondelete="CASCADE"))
|
||||
Column("template_id", GUID, ForeignKey("templates.template_id", ondelete="CASCADE")),
|
||||
)
|
||||
|
||||
|
||||
class Image(BaseTable):
|
||||
|
||||
__tablename__ = "images"
|
||||
|
||||
image_id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
|
||||
@@ -50,21 +50,28 @@ class LLMModelConfig(BaseTable):
|
||||
__table_args__ = (
|
||||
# Ensure a config belongs to either a user or a group, not both
|
||||
CheckConstraint(
|
||||
"(user_id IS NOT NULL AND group_id IS NULL) OR "
|
||||
"(user_id IS NULL AND group_id IS NOT NULL)",
|
||||
name="single_owner_check"
|
||||
"(user_id IS NOT NULL AND group_id IS NULL) OR (user_id IS NULL AND group_id IS NOT NULL)",
|
||||
name="single_owner_check",
|
||||
),
|
||||
# Validate model_type values
|
||||
CheckConstraint(
|
||||
"model_type IN ('text', 'vision', 'stt', 'tts', 'multimodal', 'embedding', 'reranking', 'other')",
|
||||
name="valid_model_type_check"
|
||||
name="valid_model_type_check",
|
||||
),
|
||||
# Each user can have at most one default config (partial unique index, PostgreSQL only)
|
||||
Index("unique_user_default", "user_id", unique=True,
|
||||
postgresql_where=text("is_default = TRUE AND user_id IS NOT NULL")),
|
||||
Index(
|
||||
"unique_user_default",
|
||||
"user_id",
|
||||
unique=True,
|
||||
postgresql_where=text("is_default = TRUE AND user_id IS NOT NULL"),
|
||||
),
|
||||
# Each group can have at most one default config (partial unique index, PostgreSQL only)
|
||||
Index("unique_group_default", "group_id", unique=True,
|
||||
postgresql_where=text("is_default = TRUE AND group_id IS NOT NULL")),
|
||||
Index(
|
||||
"unique_group_default",
|
||||
"group_id",
|
||||
unique=True,
|
||||
postgresql_where=text("is_default = TRUE AND group_id IS NOT NULL"),
|
||||
),
|
||||
# Indexes for efficient queries
|
||||
Index("idx_llm_model_configs_user_id", "user_id"),
|
||||
Index("idx_llm_model_configs_group_id", "group_id"),
|
||||
|
||||
@@ -29,12 +29,11 @@ resource_pool_map = Table(
|
||||
"resource_pool_map",
|
||||
Base.metadata,
|
||||
Column("resource_id", GUID, ForeignKey("resources.resource_id", ondelete="CASCADE")),
|
||||
Column("resource_pool_id", GUID, ForeignKey("resource_pools.resource_pool_id", ondelete="CASCADE"))
|
||||
Column("resource_pool_id", GUID, ForeignKey("resource_pools.resource_pool_id", ondelete="CASCADE")),
|
||||
)
|
||||
|
||||
|
||||
class Resource(BaseTable):
|
||||
|
||||
__tablename__ = "resources"
|
||||
|
||||
resource_id = Column(GUID, primary_key=True)
|
||||
@@ -44,7 +43,6 @@ class Resource(BaseTable):
|
||||
|
||||
|
||||
class ResourcePool(BaseTable):
|
||||
|
||||
__tablename__ = "resource_pools"
|
||||
|
||||
resource_pool_id = Column(GUID, primary_key=True, default=generate_uuid)
|
||||
|
||||
@@ -29,12 +29,11 @@ privilege_role_map = Table(
|
||||
"privilege_role_map",
|
||||
Base.metadata,
|
||||
Column("privilege_id", GUID, ForeignKey("privileges.privilege_id", ondelete="CASCADE")),
|
||||
Column("role_id", GUID, ForeignKey("roles.role_id", ondelete="CASCADE"))
|
||||
Column("role_id", GUID, ForeignKey("roles.role_id", ondelete="CASCADE")),
|
||||
)
|
||||
|
||||
|
||||
class Privilege(BaseTable):
|
||||
|
||||
__tablename__ = "privileges"
|
||||
|
||||
privilege_id = Column(GUID, primary_key=True, default=generate_uuid)
|
||||
@@ -43,210 +42,60 @@ class Privilege(BaseTable):
|
||||
roles = relationship("Role", secondary=privilege_role_map, back_populates="privileges")
|
||||
|
||||
|
||||
@event.listens_for(Privilege.__table__, 'after_create')
|
||||
@event.listens_for(Privilege.__table__, "after_create")
|
||||
def create_default_roles(target, connection, **kw):
|
||||
|
||||
default_privileges = [
|
||||
{
|
||||
"description": "Create or delete a user",
|
||||
"name": "User.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "View a user",
|
||||
"name": "User.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Update a user",
|
||||
"name": "User.Modify"
|
||||
},
|
||||
{
|
||||
"description": "Create or delete a group",
|
||||
"name": "Group.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "View a group",
|
||||
"name": "Group.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Update a group",
|
||||
"name": "Group.Modify"
|
||||
},
|
||||
{
|
||||
"description": "Create or delete a role",
|
||||
"name": "Role.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "View a role",
|
||||
"name": "Role.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Update a role",
|
||||
"name": "Role.Modify"
|
||||
},
|
||||
{
|
||||
"description": "Create or delete an ACE",
|
||||
"name": "ACE.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "View an ACE",
|
||||
"name": "ACE.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Update an ACE",
|
||||
"name": "ACE.Modify"
|
||||
},
|
||||
{
|
||||
"description": "Create or delete a resource pool",
|
||||
"name": "Pool.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "View a resource pool",
|
||||
"name": "Pool.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Update a resource pool",
|
||||
"name": "Pool.Modify"
|
||||
},
|
||||
{
|
||||
"description": "Create or delete a template",
|
||||
"name": "Template.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "View a template",
|
||||
"name": "Template.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Update a template",
|
||||
"name": "Template.Modify"
|
||||
},
|
||||
{
|
||||
"description": "Create or delete a project",
|
||||
"name": "Project.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "View a project",
|
||||
"name": "Project.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Update a project",
|
||||
"name": "Project.Modify"
|
||||
},
|
||||
{
|
||||
"description": "Create or delete project snapshots",
|
||||
"name": "Snapshot.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "Restore a snapshot",
|
||||
"name": "Snapshot.Restore"
|
||||
},
|
||||
{
|
||||
"description": "View a snapshot",
|
||||
"name": "Snapshot.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Create or delete a node",
|
||||
"name": "Node.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "View a node",
|
||||
"name": "Node.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Update a node",
|
||||
"name": "Node.Modify"
|
||||
},
|
||||
{
|
||||
"description": "Console access to a node",
|
||||
"name": "Node.Console"
|
||||
},
|
||||
{
|
||||
"description": "Power management for a node",
|
||||
"name": "Node.PowerMgmt"
|
||||
},
|
||||
{
|
||||
"description": "Create or delete a link",
|
||||
"name": "Link.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "View a link",
|
||||
"name": "Link.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Update a link",
|
||||
"name": "Link.Modify"
|
||||
},
|
||||
{
|
||||
"description": "Capture packets on a link",
|
||||
"name": "Link.Capture"
|
||||
},
|
||||
{
|
||||
"description": "Create or delete a drawing",
|
||||
"name": "Drawing.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "View a drawing",
|
||||
"name": "Drawing.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Update a drawing",
|
||||
"name": "Drawing.Modify"
|
||||
},
|
||||
{
|
||||
"description": "Create or delete a symbol",
|
||||
"name": "Symbol.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "View a symbol",
|
||||
"name": "Symbol.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Create or delete an image",
|
||||
"name": "Image.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "View an image",
|
||||
"name": "Image.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Create or delete a compute",
|
||||
"name": "Compute.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "Update a compute",
|
||||
"name": "Compute.Modify"
|
||||
},
|
||||
{
|
||||
"description": "View a compute",
|
||||
"name": "Compute.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Install an appliance",
|
||||
"name": "Appliance.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "View an appliance",
|
||||
"name": "Appliance.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Create or delete an LLM model configuration",
|
||||
"name": "LLMConfig.Allocate"
|
||||
},
|
||||
{
|
||||
"description": "View an LLM model configuration",
|
||||
"name": "LLMConfig.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Update an LLM model configuration",
|
||||
"name": "LLMConfig.Modify"
|
||||
},
|
||||
{
|
||||
"description": "View server settings",
|
||||
"name": "Server.Audit"
|
||||
},
|
||||
{
|
||||
"description": "Update server settings",
|
||||
"name": "Server.Modify"
|
||||
}
|
||||
{"description": "Create or delete a user", "name": "User.Allocate"},
|
||||
{"description": "View a user", "name": "User.Audit"},
|
||||
{"description": "Update a user", "name": "User.Modify"},
|
||||
{"description": "Create or delete a group", "name": "Group.Allocate"},
|
||||
{"description": "View a group", "name": "Group.Audit"},
|
||||
{"description": "Update a group", "name": "Group.Modify"},
|
||||
{"description": "Create or delete a role", "name": "Role.Allocate"},
|
||||
{"description": "View a role", "name": "Role.Audit"},
|
||||
{"description": "Update a role", "name": "Role.Modify"},
|
||||
{"description": "Create or delete an ACE", "name": "ACE.Allocate"},
|
||||
{"description": "View an ACE", "name": "ACE.Audit"},
|
||||
{"description": "Update an ACE", "name": "ACE.Modify"},
|
||||
{"description": "Create or delete a resource pool", "name": "Pool.Allocate"},
|
||||
{"description": "View a resource pool", "name": "Pool.Audit"},
|
||||
{"description": "Update a resource pool", "name": "Pool.Modify"},
|
||||
{"description": "Create or delete a template", "name": "Template.Allocate"},
|
||||
{"description": "View a template", "name": "Template.Audit"},
|
||||
{"description": "Update a template", "name": "Template.Modify"},
|
||||
{"description": "Create or delete a project", "name": "Project.Allocate"},
|
||||
{"description": "View a project", "name": "Project.Audit"},
|
||||
{"description": "Update a project", "name": "Project.Modify"},
|
||||
{"description": "Create or delete project snapshots", "name": "Snapshot.Allocate"},
|
||||
{"description": "Restore a snapshot", "name": "Snapshot.Restore"},
|
||||
{"description": "View a snapshot", "name": "Snapshot.Audit"},
|
||||
{"description": "Create or delete a node", "name": "Node.Allocate"},
|
||||
{"description": "View a node", "name": "Node.Audit"},
|
||||
{"description": "Update a node", "name": "Node.Modify"},
|
||||
{"description": "Console access to a node", "name": "Node.Console"},
|
||||
{"description": "Power management for a node", "name": "Node.PowerMgmt"},
|
||||
{"description": "Create or delete a link", "name": "Link.Allocate"},
|
||||
{"description": "View a link", "name": "Link.Audit"},
|
||||
{"description": "Update a link", "name": "Link.Modify"},
|
||||
{"description": "Capture packets on a link", "name": "Link.Capture"},
|
||||
{"description": "Create or delete a drawing", "name": "Drawing.Allocate"},
|
||||
{"description": "View a drawing", "name": "Drawing.Audit"},
|
||||
{"description": "Update a drawing", "name": "Drawing.Modify"},
|
||||
{"description": "Create or delete a symbol", "name": "Symbol.Allocate"},
|
||||
{"description": "View a symbol", "name": "Symbol.Audit"},
|
||||
{"description": "Create or delete an image", "name": "Image.Allocate"},
|
||||
{"description": "View an image", "name": "Image.Audit"},
|
||||
{"description": "Create or delete a compute", "name": "Compute.Allocate"},
|
||||
{"description": "Update a compute", "name": "Compute.Modify"},
|
||||
{"description": "View a compute", "name": "Compute.Audit"},
|
||||
{"description": "Install an appliance", "name": "Appliance.Allocate"},
|
||||
{"description": "View an appliance", "name": "Appliance.Audit"},
|
||||
{"description": "Create or delete an LLM model configuration", "name": "LLMConfig.Allocate"},
|
||||
{"description": "View an LLM model configuration", "name": "LLMConfig.Audit"},
|
||||
{"description": "Update an LLM model configuration", "name": "LLMConfig.Modify"},
|
||||
{"description": "View server settings", "name": "Server.Audit"},
|
||||
{"description": "Update server settings", "name": "Server.Modify"},
|
||||
]
|
||||
|
||||
stmt = target.insert().values(default_privileges)
|
||||
@@ -258,6 +107,7 @@ def create_default_roles(target, connection, **kw):
|
||||
def add_privileges_to_role(target, connection, role, privileges):
|
||||
|
||||
from .roles import Role
|
||||
|
||||
roles_table = Role.__table__
|
||||
privileges_table = Privilege.__table__
|
||||
|
||||
@@ -272,10 +122,11 @@ def add_privileges_to_role(target, connection, role, privileges):
|
||||
connection.execute(stmt)
|
||||
|
||||
|
||||
@event.listens_for(privilege_role_map, 'after_create')
|
||||
@event.listens_for(privilege_role_map, "after_create")
|
||||
def add_privileges_to_default_roles(target, connection, **kw):
|
||||
|
||||
from .roles import Role
|
||||
|
||||
roles_table = Role.__table__
|
||||
stmt = roles_table.select().where(roles_table.c.name == "Administrator")
|
||||
result = connection.execute(stmt)
|
||||
@@ -317,7 +168,7 @@ def add_privileges_to_default_roles(target, connection, **kw):
|
||||
"Appliance.Allocate",
|
||||
"Appliance.Audit",
|
||||
"LLMConfig.Audit",
|
||||
"LLMConfig.Modify"
|
||||
"LLMConfig.Modify",
|
||||
)
|
||||
|
||||
add_privileges_to_role(target, connection, "User", user_privileges)
|
||||
@@ -333,7 +184,7 @@ def add_privileges_to_default_roles(target, connection, **kw):
|
||||
"Symbol.Audit",
|
||||
"Image.Audit",
|
||||
"Compute.Audit",
|
||||
"Appliance.Audit"
|
||||
"Appliance.Audit",
|
||||
)
|
||||
|
||||
add_privileges_to_role(target, connection, "Auditor", auditor_privileges)
|
||||
@@ -348,7 +199,7 @@ def add_privileges_to_default_roles(target, connection, **kw):
|
||||
"Image.Allocate",
|
||||
"Image.Audit",
|
||||
"Appliance.Allocate",
|
||||
"Appliance.Audit"
|
||||
"Appliance.Audit",
|
||||
)
|
||||
|
||||
add_privileges_to_role(target, connection, "Template manager", template_manager_privileges)
|
||||
@@ -360,20 +211,13 @@ def add_privileges_to_default_roles(target, connection, **kw):
|
||||
"User.Modify",
|
||||
"Group.Allocate",
|
||||
"Group.Audit",
|
||||
"Group.Modify"
|
||||
"Group.Modify",
|
||||
)
|
||||
|
||||
add_privileges_to_role(target, connection, "User manager", user_manager_privileges)
|
||||
|
||||
# add required privileges to the "ACL manager" role
|
||||
acl_manager_privileges = (
|
||||
"Role.Allocate",
|
||||
"Role.Audit",
|
||||
"Role.Modify",
|
||||
"ACE.Allocate",
|
||||
"ACE.Audit",
|
||||
"ACE.Modify"
|
||||
)
|
||||
acl_manager_privileges = ("Role.Allocate", "Role.Audit", "Role.Modify", "ACE.Allocate", "ACE.Audit", "ACE.Modify")
|
||||
|
||||
add_privileges_to_role(target, connection, "ACL manager", acl_manager_privileges)
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Role(BaseTable):
|
||||
|
||||
__tablename__ = "roles"
|
||||
|
||||
role_id = Column(GUID, primary_key=True, default=generate_uuid)
|
||||
@@ -38,7 +37,7 @@ class Role(BaseTable):
|
||||
acl_entries = relationship("ACE")
|
||||
|
||||
|
||||
@event.listens_for(Role.__table__, 'after_create')
|
||||
@event.listens_for(Role.__table__, "after_create")
|
||||
def create_default_roles(target, connection, **kw):
|
||||
|
||||
default_roles = [
|
||||
@@ -48,7 +47,7 @@ def create_default_roles(target, connection, **kw):
|
||||
{"name": "Template manager", "description": "Role to manage templates", "is_builtin": True},
|
||||
{"name": "User manager", "description": "Role to manage users and groups", "is_builtin": True},
|
||||
{"name": "ACL manager", "description": "Role to manage other roles and the ACL", "is_builtin": True},
|
||||
{"name": "No Access", "description": "Role with no privileges (used to forbid access)", "is_builtin": True}
|
||||
{"name": "No Access", "description": "Role with no privileges (used to forbid access)", "is_builtin": True},
|
||||
]
|
||||
|
||||
stmt = target.insert().values(default_roles)
|
||||
|
||||
@@ -24,7 +24,6 @@ from .images import image_template_map
|
||||
|
||||
|
||||
class Template(BaseTable):
|
||||
|
||||
__tablename__ = "templates"
|
||||
|
||||
template_id = Column(GUID, primary_key=True, default=generate_uuid)
|
||||
@@ -49,7 +48,6 @@ class Template(BaseTable):
|
||||
|
||||
|
||||
class CloudTemplate(Template):
|
||||
|
||||
__tablename__ = "cloud_templates"
|
||||
|
||||
template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True)
|
||||
@@ -63,7 +61,6 @@ class CloudTemplate(Template):
|
||||
|
||||
|
||||
class DockerTemplate(Template):
|
||||
|
||||
__tablename__ = "docker_templates"
|
||||
|
||||
template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True)
|
||||
@@ -89,7 +86,6 @@ class DockerTemplate(Template):
|
||||
|
||||
|
||||
class DynamipsTemplate(Template):
|
||||
|
||||
__tablename__ = "dynamips_templates"
|
||||
|
||||
template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True)
|
||||
@@ -132,7 +128,6 @@ class DynamipsTemplate(Template):
|
||||
|
||||
|
||||
class EthernetHubTemplate(Template):
|
||||
|
||||
__tablename__ = "ethernet_hub_templates"
|
||||
|
||||
template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True)
|
||||
@@ -142,7 +137,6 @@ class EthernetHubTemplate(Template):
|
||||
|
||||
|
||||
class EthernetSwitchTemplate(Template):
|
||||
|
||||
__tablename__ = "ethernet_switch_templates"
|
||||
|
||||
template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True)
|
||||
@@ -153,7 +147,6 @@ class EthernetSwitchTemplate(Template):
|
||||
|
||||
|
||||
class IOUTemplate(Template):
|
||||
|
||||
__tablename__ = "iou_templates"
|
||||
|
||||
template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True)
|
||||
@@ -173,7 +166,6 @@ class IOUTemplate(Template):
|
||||
|
||||
|
||||
class QemuTemplate(Template):
|
||||
|
||||
__tablename__ = "qemu_templates"
|
||||
|
||||
template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True)
|
||||
@@ -220,7 +212,6 @@ class QemuTemplate(Template):
|
||||
|
||||
|
||||
class VirtualBoxTemplate(Template):
|
||||
|
||||
__tablename__ = "virtualbox_templates"
|
||||
|
||||
template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True)
|
||||
@@ -243,7 +234,6 @@ class VirtualBoxTemplate(Template):
|
||||
|
||||
|
||||
class VMwareTemplate(Template):
|
||||
|
||||
__tablename__ = "vmware_templates"
|
||||
|
||||
template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True)
|
||||
@@ -265,7 +255,6 @@ class VMwareTemplate(Template):
|
||||
|
||||
|
||||
class VPCSTemplate(Template):
|
||||
|
||||
__tablename__ = "vpcs_templates"
|
||||
|
||||
template_id = Column(GUID, ForeignKey("templates.template_id", ondelete="CASCADE"), primary_key=True)
|
||||
|
||||
@@ -31,12 +31,11 @@ user_group_map = Table(
|
||||
"user_group_map",
|
||||
Base.metadata,
|
||||
Column("user_id", GUID, ForeignKey("users.user_id", ondelete="CASCADE")),
|
||||
Column("user_group_id", GUID, ForeignKey("user_groups.user_group_id", ondelete="CASCADE"))
|
||||
Column("user_group_id", GUID, ForeignKey("user_groups.user_group_id", ondelete="CASCADE")),
|
||||
)
|
||||
|
||||
|
||||
class User(BaseTable):
|
||||
|
||||
__tablename__ = "users"
|
||||
|
||||
user_id = Column(GUID, primary_key=True, default=generate_uuid)
|
||||
@@ -52,7 +51,7 @@ class User(BaseTable):
|
||||
acl_entries = relationship("ACE")
|
||||
|
||||
|
||||
@event.listens_for(User.__table__, 'after_create')
|
||||
@event.listens_for(User.__table__, "after_create")
|
||||
def create_default_super_admin(target, connection, **kw):
|
||||
|
||||
config = Config.instance().settings
|
||||
@@ -63,7 +62,7 @@ def create_default_super_admin(target, connection, **kw):
|
||||
username=default_admin_username,
|
||||
full_name="Super Administrator",
|
||||
hashed_password=hashed_password,
|
||||
is_superadmin=True
|
||||
is_superadmin=True,
|
||||
)
|
||||
connection.execute(stmt)
|
||||
connection.commit()
|
||||
@@ -71,7 +70,6 @@ def create_default_super_admin(target, connection, **kw):
|
||||
|
||||
|
||||
class UserGroup(BaseTable):
|
||||
|
||||
__tablename__ = "user_groups"
|
||||
|
||||
user_group_id = Column(GUID, primary_key=True, default=generate_uuid)
|
||||
@@ -81,13 +79,10 @@ class UserGroup(BaseTable):
|
||||
acl_entries = relationship("ACE")
|
||||
|
||||
|
||||
@event.listens_for(UserGroup.__table__, 'after_create')
|
||||
@event.listens_for(UserGroup.__table__, "after_create")
|
||||
def create_default_user_groups(target, connection, **kw):
|
||||
|
||||
default_groups = [
|
||||
{"name": "Administrators", "is_builtin": True},
|
||||
{"name": "Users", "is_builtin": True}
|
||||
]
|
||||
default_groups = [{"name": "Administrators", "is_builtin": True}, {"name": "Users", "is_builtin": True}]
|
||||
|
||||
stmt = target.insert().values(default_groups)
|
||||
connection.execute(stmt)
|
||||
|
||||
@@ -30,7 +30,6 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ApiKeysRepository(BaseRepository):
|
||||
|
||||
def __init__(self, db_session: AsyncSession) -> None:
|
||||
super().__init__(db_session)
|
||||
|
||||
@@ -55,11 +54,7 @@ class ApiKeysRepository(BaseRepository):
|
||||
return result.scalars().first()
|
||||
|
||||
async def get_api_keys_by_user(self, user_id: UUID) -> List[models.ApiKey]:
|
||||
query = (
|
||||
select(models.ApiKey)
|
||||
.where(models.ApiKey.user_id == user_id)
|
||||
.order_by(models.ApiKey.created_at.desc())
|
||||
)
|
||||
query = select(models.ApiKey).where(models.ApiKey.user_id == user_id).order_by(models.ApiKey.created_at.desc())
|
||||
result = await self._db_session.execute(query)
|
||||
return list(result.scalars().all())
|
||||
|
||||
@@ -69,31 +64,19 @@ class ApiKeysRepository(BaseRepository):
|
||||
return result.scalars().first()
|
||||
|
||||
async def revoke_api_key(self, api_key_id: UUID) -> bool:
|
||||
query = (
|
||||
update(models.ApiKey)
|
||||
.where(models.ApiKey.api_key_id == api_key_id)
|
||||
.values(revoked=True)
|
||||
)
|
||||
query = update(models.ApiKey).where(models.ApiKey.api_key_id == api_key_id).values(revoked=True)
|
||||
result = await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
return result.rowcount > 0
|
||||
|
||||
async def restore_api_key(self, api_key_id: UUID) -> bool:
|
||||
query = (
|
||||
update(models.ApiKey)
|
||||
.where(models.ApiKey.api_key_id == api_key_id)
|
||||
.values(revoked=False)
|
||||
)
|
||||
query = update(models.ApiKey).where(models.ApiKey.api_key_id == api_key_id).values(revoked=False)
|
||||
result = await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
return result.rowcount > 0
|
||||
|
||||
async def update_last_used(self, api_key_id: UUID) -> None:
|
||||
query = (
|
||||
update(models.ApiKey)
|
||||
.where(models.ApiKey.api_key_id == api_key_id)
|
||||
.values(last_used_at=func.now())
|
||||
)
|
||||
query = update(models.ApiKey).where(models.ApiKey.api_key_id == api_key_id).values(last_used_at=func.now())
|
||||
await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
|
||||
|
||||
@@ -27,7 +27,6 @@ from gns3server import schemas
|
||||
|
||||
|
||||
class ComputesRepository(BaseRepository):
|
||||
|
||||
def __init__(self, db_session: AsyncSession) -> None:
|
||||
|
||||
super().__init__(db_session)
|
||||
@@ -72,9 +71,7 @@ class ComputesRepository(BaseRepository):
|
||||
if compute_update.password is not None:
|
||||
update_values["password"] = compute_update.password.get_secret_value()
|
||||
|
||||
query = update(models.Compute).\
|
||||
where(models.Compute.compute_id == compute_id).\
|
||||
values(update_values)
|
||||
query = update(models.Compute).where(models.Compute.compute_id == compute_id).values(update_values)
|
||||
|
||||
await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
|
||||
@@ -31,7 +31,6 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ImagesRepository(BaseRepository):
|
||||
|
||||
def __init__(self, db_session: AsyncSession) -> None:
|
||||
|
||||
super().__init__(db_session)
|
||||
@@ -43,8 +42,9 @@ class ImagesRepository(BaseRepository):
|
||||
|
||||
image_dir, image_name = os.path.split(image_path)
|
||||
if image_dir:
|
||||
query = select(models.Image).\
|
||||
where(models.Image.filename == image_name, models.Image.path.endswith(image_path))
|
||||
query = select(models.Image).where(
|
||||
models.Image.filename == image_name, models.Image.path.endswith(image_path)
|
||||
)
|
||||
else:
|
||||
query = select(models.Image).where(models.Image.filename == image_name)
|
||||
result = await self._db_session.execute(query)
|
||||
@@ -56,8 +56,9 @@ class ImagesRepository(BaseRepository):
|
||||
"""
|
||||
|
||||
if image_dir:
|
||||
query = select(models.Image).\
|
||||
where(models.Image.checksum == checksum, models.Image.path.startswith(image_dir))
|
||||
query = select(models.Image).where(
|
||||
models.Image.checksum == checksum, models.Image.path.startswith(image_dir)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().one_or_none()
|
||||
else:
|
||||
@@ -82,9 +83,7 @@ class ImagesRepository(BaseRepository):
|
||||
Get all templates that an image belongs to.
|
||||
"""
|
||||
|
||||
query = select(models.Template).\
|
||||
join(models.Template.images).\
|
||||
filter(models.Image.image_id == image_id)
|
||||
query = select(models.Template).join(models.Template.images).filter(models.Image.image_id == image_id)
|
||||
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().all()
|
||||
@@ -101,7 +100,7 @@ class ImagesRepository(BaseRepository):
|
||||
image_size=image_size,
|
||||
path=path,
|
||||
checksum=checksum,
|
||||
checksum_algorithm=checksum_algorithm
|
||||
checksum_algorithm=checksum_algorithm,
|
||||
)
|
||||
|
||||
self._db_session.add(db_image)
|
||||
@@ -114,9 +113,11 @@ class ImagesRepository(BaseRepository):
|
||||
Update an image.
|
||||
"""
|
||||
|
||||
query = update(models.Image).\
|
||||
where(models.Image.path == image_path).\
|
||||
values(checksum=checksum, checksum_algorithm=checksum_algorithm)
|
||||
query = (
|
||||
update(models.Image)
|
||||
.where(models.Image.path == image_path)
|
||||
.values(checksum=checksum, checksum_algorithm=checksum_algorithm)
|
||||
)
|
||||
|
||||
await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
@@ -132,9 +133,11 @@ class ImagesRepository(BaseRepository):
|
||||
|
||||
image_dir, image_name = os.path.split(image_path)
|
||||
if image_dir:
|
||||
query = delete(models.Image).\
|
||||
where(models.Image.filename == image_name, models.Image.path.endswith(image_path)).\
|
||||
execution_options(synchronize_session=False)
|
||||
query = (
|
||||
delete(models.Image)
|
||||
.where(models.Image.filename == image_name, models.Image.path.endswith(image_path))
|
||||
.execution_options(synchronize_session=False)
|
||||
)
|
||||
else:
|
||||
query = delete(models.Image).where(models.Image.filename == image_name)
|
||||
result = await self._db_session.execute(query)
|
||||
@@ -146,8 +149,7 @@ class ImagesRepository(BaseRepository):
|
||||
Prune images not attached to any template.
|
||||
"""
|
||||
|
||||
query = select(models.Image).\
|
||||
filter(~models.Image.templates.any())
|
||||
query = select(models.Image).filter(~models.Image.templates.any())
|
||||
result = await self._db_session.execute(query)
|
||||
images = result.scalars().all()
|
||||
images_deleted = 0
|
||||
|
||||
@@ -50,44 +50,36 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
async def get_user_config(self, config_id: UUID) -> Optional[models.LLMModelConfig]:
|
||||
"""Get a user's LLM model configuration by ID."""
|
||||
query = select(models.LLMModelConfig).where(
|
||||
and_(
|
||||
models.LLMModelConfig.config_id == config_id,
|
||||
models.LLMModelConfig.user_id.isnot(None)
|
||||
)
|
||||
and_(models.LLMModelConfig.config_id == config_id, models.LLMModelConfig.user_id.isnot(None))
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().first()
|
||||
|
||||
async def get_user_configs(self, user_id: UUID) -> List[models.LLMModelConfig]:
|
||||
"""Get all LLM model configurations for a user."""
|
||||
query = select(models.LLMModelConfig).where(
|
||||
models.LLMModelConfig.user_id == user_id
|
||||
).order_by(models.LLMModelConfig.created_at)
|
||||
query = (
|
||||
select(models.LLMModelConfig)
|
||||
.where(models.LLMModelConfig.user_id == user_id)
|
||||
.order_by(models.LLMModelConfig.created_at)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
async def get_user_default_config(self, user_id: UUID) -> Optional[models.LLMModelConfig]:
|
||||
"""Get a user's default LLM model configuration."""
|
||||
query = select(models.LLMModelConfig).where(
|
||||
and_(
|
||||
models.LLMModelConfig.user_id == user_id,
|
||||
models.LLMModelConfig.is_default
|
||||
)
|
||||
and_(models.LLMModelConfig.user_id == user_id, models.LLMModelConfig.is_default)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().first()
|
||||
|
||||
async def create_user_config(
|
||||
self,
|
||||
user_id: UUID,
|
||||
name: str,
|
||||
model_type: str,
|
||||
config_data: Dict[str, Any],
|
||||
is_default: bool = False
|
||||
self, user_id: UUID, name: str, model_type: str, config_data: Dict[str, Any], is_default: bool = False
|
||||
) -> models.LLMModelConfig:
|
||||
"""Create a new LLM model configuration for a user."""
|
||||
# Encrypt API key if present
|
||||
from gns3server.utils.encryption import encrypt
|
||||
|
||||
config_to_store = config_data.copy()
|
||||
if "api_key" in config_to_store and config_to_store["api_key"]:
|
||||
try:
|
||||
@@ -105,7 +97,7 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
user_id=user_id,
|
||||
is_default=is_default,
|
||||
created_at=now,
|
||||
updated_at=now
|
||||
updated_at=now,
|
||||
)
|
||||
self._db_session.add(db_config)
|
||||
await self._db_session.commit()
|
||||
@@ -113,11 +105,7 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
return db_config
|
||||
|
||||
async def update_user_config(
|
||||
self,
|
||||
config_id: UUID,
|
||||
user_id: UUID,
|
||||
updates: Dict[str, Any],
|
||||
expected_version: Optional[int] = None
|
||||
self, config_id: UUID, user_id: UUID, updates: Dict[str, Any], expected_version: Optional[int] = None
|
||||
) -> Optional[models.LLMModelConfig]:
|
||||
"""
|
||||
Update a user's LLM model configuration.
|
||||
@@ -130,10 +118,7 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
:raises ValueError: If version mismatch (concurrent modification)
|
||||
"""
|
||||
query = select(models.LLMModelConfig).where(
|
||||
and_(
|
||||
models.LLMModelConfig.config_id == config_id,
|
||||
models.LLMModelConfig.user_id == user_id
|
||||
)
|
||||
and_(models.LLMModelConfig.config_id == config_id, models.LLMModelConfig.user_id == user_id)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
db_config = result.scalars().first()
|
||||
@@ -193,10 +178,7 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
async def delete_user_config(self, config_id: UUID, user_id: UUID) -> bool:
|
||||
"""Delete a user's LLM model configuration."""
|
||||
query = delete(models.LLMModelConfig).where(
|
||||
and_(
|
||||
models.LLMModelConfig.config_id == config_id,
|
||||
models.LLMModelConfig.user_id == user_id
|
||||
)
|
||||
and_(models.LLMModelConfig.config_id == config_id, models.LLMModelConfig.user_id == user_id)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
@@ -208,22 +190,16 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
# First, unset current default
|
||||
await self._db_session.execute(
|
||||
update(models.LLMModelConfig)
|
||||
.where(
|
||||
and_(
|
||||
models.LLMModelConfig.user_id == user_id,
|
||||
models.LLMModelConfig.is_default
|
||||
)
|
||||
)
|
||||
.where(and_(models.LLMModelConfig.user_id == user_id, models.LLMModelConfig.is_default))
|
||||
.values(is_default=False, updated_at=now)
|
||||
)
|
||||
|
||||
# Set new default
|
||||
query = update(models.LLMModelConfig).where(
|
||||
and_(
|
||||
models.LLMModelConfig.config_id == config_id,
|
||||
models.LLMModelConfig.user_id == user_id
|
||||
)
|
||||
).values(is_default=True, updated_at=now)
|
||||
query = (
|
||||
update(models.LLMModelConfig)
|
||||
.where(and_(models.LLMModelConfig.config_id == config_id, models.LLMModelConfig.user_id == user_id))
|
||||
.values(is_default=True, updated_at=now)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
return result.rowcount > 0
|
||||
@@ -233,44 +209,36 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
async def get_group_config(self, config_id: UUID) -> Optional[models.LLMModelConfig]:
|
||||
"""Get a group's LLM model configuration by ID."""
|
||||
query = select(models.LLMModelConfig).where(
|
||||
and_(
|
||||
models.LLMModelConfig.config_id == config_id,
|
||||
models.LLMModelConfig.group_id.isnot(None)
|
||||
)
|
||||
and_(models.LLMModelConfig.config_id == config_id, models.LLMModelConfig.group_id.isnot(None))
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().first()
|
||||
|
||||
async def get_group_configs(self, group_id: UUID) -> List[models.LLMModelConfig]:
|
||||
"""Get all LLM model configurations for a group."""
|
||||
query = select(models.LLMModelConfig).where(
|
||||
models.LLMModelConfig.group_id == group_id
|
||||
).order_by(models.LLMModelConfig.created_at)
|
||||
query = (
|
||||
select(models.LLMModelConfig)
|
||||
.where(models.LLMModelConfig.group_id == group_id)
|
||||
.order_by(models.LLMModelConfig.created_at)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
async def get_group_default_config(self, group_id: UUID) -> Optional[models.LLMModelConfig]:
|
||||
"""Get a group's default LLM model configuration."""
|
||||
query = select(models.LLMModelConfig).where(
|
||||
and_(
|
||||
models.LLMModelConfig.group_id == group_id,
|
||||
models.LLMModelConfig.is_default
|
||||
)
|
||||
and_(models.LLMModelConfig.group_id == group_id, models.LLMModelConfig.is_default)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().first()
|
||||
|
||||
async def create_group_config(
|
||||
self,
|
||||
group_id: UUID,
|
||||
name: str,
|
||||
model_type: str,
|
||||
config_data: Dict[str, Any],
|
||||
is_default: bool = False
|
||||
self, group_id: UUID, name: str, model_type: str, config_data: Dict[str, Any], is_default: bool = False
|
||||
) -> models.LLMModelConfig:
|
||||
"""Create a new LLM model configuration for a group."""
|
||||
# Encrypt API key if present
|
||||
from gns3server.utils.encryption import encrypt
|
||||
|
||||
config_to_store = config_data.copy()
|
||||
if "api_key" in config_to_store and config_to_store["api_key"]:
|
||||
try:
|
||||
@@ -288,7 +256,7 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
group_id=group_id,
|
||||
is_default=is_default,
|
||||
created_at=now,
|
||||
updated_at=now
|
||||
updated_at=now,
|
||||
)
|
||||
self._db_session.add(db_config)
|
||||
await self._db_session.commit()
|
||||
@@ -296,11 +264,7 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
return db_config
|
||||
|
||||
async def update_group_config(
|
||||
self,
|
||||
config_id: UUID,
|
||||
group_id: UUID,
|
||||
updates: Dict[str, Any],
|
||||
expected_version: Optional[int] = None
|
||||
self, config_id: UUID, group_id: UUID, updates: Dict[str, Any], expected_version: Optional[int] = None
|
||||
) -> Optional[models.LLMModelConfig]:
|
||||
"""
|
||||
Update a group's LLM model configuration.
|
||||
@@ -313,10 +277,7 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
:raises ValueError: If version mismatch (concurrent modification)
|
||||
"""
|
||||
query = select(models.LLMModelConfig).where(
|
||||
and_(
|
||||
models.LLMModelConfig.config_id == config_id,
|
||||
models.LLMModelConfig.group_id == group_id
|
||||
)
|
||||
and_(models.LLMModelConfig.config_id == config_id, models.LLMModelConfig.group_id == group_id)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
db_config = result.scalars().first()
|
||||
@@ -376,10 +337,7 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
async def delete_group_config(self, config_id: UUID, group_id: UUID) -> bool:
|
||||
"""Delete a group's LLM model configuration."""
|
||||
query = delete(models.LLMModelConfig).where(
|
||||
and_(
|
||||
models.LLMModelConfig.config_id == config_id,
|
||||
models.LLMModelConfig.group_id == group_id
|
||||
)
|
||||
and_(models.LLMModelConfig.config_id == config_id, models.LLMModelConfig.group_id == group_id)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
@@ -391,22 +349,16 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
# First, unset current default
|
||||
await self._db_session.execute(
|
||||
update(models.LLMModelConfig)
|
||||
.where(
|
||||
and_(
|
||||
models.LLMModelConfig.group_id == group_id,
|
||||
models.LLMModelConfig.is_default
|
||||
)
|
||||
)
|
||||
.where(and_(models.LLMModelConfig.group_id == group_id, models.LLMModelConfig.is_default))
|
||||
.values(is_default=False, updated_at=now)
|
||||
)
|
||||
|
||||
# Set new default
|
||||
query = update(models.LLMModelConfig).where(
|
||||
and_(
|
||||
models.LLMModelConfig.config_id == config_id,
|
||||
models.LLMModelConfig.group_id == group_id
|
||||
)
|
||||
).values(is_default=True, updated_at=now)
|
||||
query = (
|
||||
update(models.LLMModelConfig)
|
||||
.where(and_(models.LLMModelConfig.config_id == config_id, models.LLMModelConfig.group_id == group_id))
|
||||
.values(is_default=True, updated_at=now)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
return result.rowcount > 0
|
||||
@@ -414,10 +366,7 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
# Inheritance methods
|
||||
|
||||
async def get_user_effective_configs(
|
||||
self,
|
||||
user_id: UUID,
|
||||
current_user_id: Optional[UUID] = None,
|
||||
current_user_is_superadmin: bool = False
|
||||
self, user_id: UUID, current_user_id: Optional[UUID] = None, current_user_is_superadmin: bool = False
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Get user's effective configurations (own + inherited from groups).
|
||||
@@ -429,9 +378,7 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
user_configs = await self.get_user_configs(user_id)
|
||||
|
||||
# Get user's groups
|
||||
query = select(models.UserGroup).\
|
||||
join(models.UserGroup.users).\
|
||||
filter(models.User.user_id == user_id)
|
||||
query = select(models.UserGroup).join(models.UserGroup.users).filter(models.User.user_id == user_id)
|
||||
result = await self._db_session.execute(query)
|
||||
user_groups = result.scalars().all()
|
||||
|
||||
@@ -453,28 +400,8 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
# Always hide API key for security
|
||||
config_dict = self._hide_api_key(config.config)
|
||||
|
||||
configs_with_source.append({
|
||||
"config_id": config.config_id,
|
||||
"name": config.name,
|
||||
"model_type": config.model_type,
|
||||
"config": config_dict,
|
||||
"user_id": config.user_id,
|
||||
"group_id": config.group_id,
|
||||
"is_default": config.is_default,
|
||||
"version": config.version,
|
||||
"created_at": config.created_at,
|
||||
"updated_at": config.updated_at,
|
||||
"source": "user",
|
||||
"group_name": None
|
||||
})
|
||||
|
||||
# Add inherited group configs (always shown, regardless of user configs)
|
||||
for group_id, configs in group_configs_map.items():
|
||||
for config in configs:
|
||||
# Always hide API key for security
|
||||
config_dict = self._hide_api_key(config.config)
|
||||
|
||||
configs_with_source.append({
|
||||
configs_with_source.append(
|
||||
{
|
||||
"config_id": config.config_id,
|
||||
"name": config.name,
|
||||
"model_type": config.model_type,
|
||||
@@ -485,9 +412,33 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
"version": config.version,
|
||||
"created_at": config.created_at,
|
||||
"updated_at": config.updated_at,
|
||||
"source": "group",
|
||||
"group_name": group_names_map[group_id]
|
||||
})
|
||||
"source": "user",
|
||||
"group_name": None,
|
||||
}
|
||||
)
|
||||
|
||||
# Add inherited group configs (always shown, regardless of user configs)
|
||||
for group_id, configs in group_configs_map.items():
|
||||
for config in configs:
|
||||
# Always hide API key for security
|
||||
config_dict = self._hide_api_key(config.config)
|
||||
|
||||
configs_with_source.append(
|
||||
{
|
||||
"config_id": config.config_id,
|
||||
"name": config.name,
|
||||
"model_type": config.model_type,
|
||||
"config": config_dict,
|
||||
"user_id": config.user_id,
|
||||
"group_id": config.group_id,
|
||||
"is_default": config.is_default,
|
||||
"version": config.version,
|
||||
"created_at": config.created_at,
|
||||
"updated_at": config.updated_at,
|
||||
"source": "group",
|
||||
"group_name": group_names_map[group_id],
|
||||
}
|
||||
)
|
||||
|
||||
# Select default_config with proper priority:
|
||||
# 1. User's config marked with is_default: true
|
||||
@@ -508,7 +459,4 @@ class LLMModelConfigsRepository(BaseRepository):
|
||||
if default_config is None and configs_with_source:
|
||||
default_config = configs_with_source[0]
|
||||
|
||||
return {
|
||||
"configs": configs_with_source,
|
||||
"default_config": default_config
|
||||
}
|
||||
return {"configs": configs_with_source, "default_config": default_config}
|
||||
@@ -32,7 +32,6 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ResourcePoolsRepository(BaseRepository):
|
||||
|
||||
def __init__(self, db_session: AsyncSession) -> None:
|
||||
|
||||
super().__init__(db_session)
|
||||
@@ -61,9 +60,7 @@ class ResourcePoolsRepository(BaseRepository):
|
||||
"""
|
||||
|
||||
db_resource = models.Resource(
|
||||
resource_id=resource.resource_id,
|
||||
resource_type=resource.resource_type,
|
||||
name=resource.name
|
||||
resource_id=resource.resource_id, resource_type=resource.resource_type, name=resource.name
|
||||
)
|
||||
self._db_session.add(db_resource)
|
||||
await self._db_session.commit()
|
||||
@@ -85,9 +82,11 @@ class ResourcePoolsRepository(BaseRepository):
|
||||
Get all resource memberships in resource pools.
|
||||
"""
|
||||
|
||||
query = select(models.ResourcePool).\
|
||||
join(models.ResourcePool.resources).\
|
||||
filter(models.Resource.resource_id == resource_id)
|
||||
query = (
|
||||
select(models.ResourcePool)
|
||||
.join(models.ResourcePool.resources)
|
||||
.filter(models.Resource.resource_id == resource_id)
|
||||
)
|
||||
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().all()
|
||||
@@ -131,18 +130,18 @@ class ResourcePoolsRepository(BaseRepository):
|
||||
return db_resource_pool
|
||||
|
||||
async def update_resource_pool(
|
||||
self,
|
||||
resource_pool_id: UUID,
|
||||
resource_pool_update: schemas.ResourcePoolUpdate
|
||||
self, resource_pool_id: UUID, resource_pool_update: schemas.ResourcePoolUpdate
|
||||
) -> Optional[models.ResourcePool]:
|
||||
"""
|
||||
Update a resource pool.
|
||||
"""
|
||||
|
||||
update_values = resource_pool_update.model_dump(exclude_unset=True)
|
||||
query = update(models.ResourcePool).\
|
||||
where(models.ResourcePool.resource_pool_id == resource_pool_id).\
|
||||
values(update_values)
|
||||
query = (
|
||||
update(models.ResourcePool)
|
||||
.where(models.ResourcePool.resource_pool_id == resource_pool_id)
|
||||
.values(update_values)
|
||||
)
|
||||
|
||||
await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
@@ -170,17 +169,17 @@ class ResourcePoolsRepository(BaseRepository):
|
||||
return result.rowcount > 0
|
||||
|
||||
async def add_resource_to_pool(
|
||||
self,
|
||||
resource_pool_id: UUID,
|
||||
resource: models.Resource
|
||||
self, resource_pool_id: UUID, resource: models.Resource
|
||||
) -> Union[None, models.ResourcePool]:
|
||||
"""
|
||||
Add a resource to a resource pool.
|
||||
"""
|
||||
|
||||
query = select(models.ResourcePool).\
|
||||
options(selectinload(models.ResourcePool.resources)).\
|
||||
where(models.ResourcePool.resource_pool_id == resource_pool_id)
|
||||
query = (
|
||||
select(models.ResourcePool)
|
||||
.options(selectinload(models.ResourcePool.resources))
|
||||
.where(models.ResourcePool.resource_pool_id == resource_pool_id)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
resource_pool_db = result.scalars().first()
|
||||
if not resource_pool_db:
|
||||
@@ -192,17 +191,17 @@ class ResourcePoolsRepository(BaseRepository):
|
||||
return resource_pool_db
|
||||
|
||||
async def remove_resource_from_pool(
|
||||
self,
|
||||
resource_pool_id: UUID,
|
||||
resource: models.Resource
|
||||
self, resource_pool_id: UUID, resource: models.Resource
|
||||
) -> Union[None, models.ResourcePool]:
|
||||
"""
|
||||
Remove a resource from a resource pool.
|
||||
"""
|
||||
|
||||
query = select(models.ResourcePool).\
|
||||
options(selectinload(models.ResourcePool.resources)).\
|
||||
where(models.ResourcePool.resource_pool_id == resource_pool_id)
|
||||
query = (
|
||||
select(models.ResourcePool)
|
||||
.options(selectinload(models.ResourcePool.resources))
|
||||
.where(models.ResourcePool.resource_pool_id == resource_pool_id)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
resource_pool_db = result.scalars().first()
|
||||
if not resource_pool_db:
|
||||
@@ -219,9 +218,11 @@ class ResourcePoolsRepository(BaseRepository):
|
||||
Get all resources from a resource pool.
|
||||
"""
|
||||
|
||||
query = select(models.Resource).\
|
||||
join(models.Resource.resource_pools).\
|
||||
filter(models.ResourcePool.resource_pool_id == resource_pool_id)
|
||||
query = (
|
||||
select(models.Resource)
|
||||
.join(models.Resource.resource_pools)
|
||||
.filter(models.ResourcePool.resource_pool_id == resource_pool_id)
|
||||
)
|
||||
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().all()
|
||||
@@ -33,7 +33,6 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RbacRepository(BaseRepository):
|
||||
|
||||
def __init__(self, db_session: AsyncSession) -> None:
|
||||
|
||||
super().__init__(db_session)
|
||||
@@ -43,9 +42,7 @@ class RbacRepository(BaseRepository):
|
||||
Get a role by its ID.
|
||||
"""
|
||||
|
||||
query = select(models.Role).\
|
||||
options(selectinload(models.Role.privileges)).\
|
||||
where(models.Role.role_id == role_id)
|
||||
query = select(models.Role).options(selectinload(models.Role.privileges)).where(models.Role.role_id == role_id)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().first()
|
||||
|
||||
@@ -54,9 +51,7 @@ class RbacRepository(BaseRepository):
|
||||
Get a role by its name.
|
||||
"""
|
||||
|
||||
query = select(models.Role).\
|
||||
options(selectinload(models.Role.privileges)).\
|
||||
where(models.Role.name == name)
|
||||
query = select(models.Role).options(selectinload(models.Role.privileges)).where(models.Role.name == name)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().first()
|
||||
|
||||
@@ -82,19 +77,13 @@ class RbacRepository(BaseRepository):
|
||||
await self._db_session.commit()
|
||||
return await self.get_role(db_role.role_id)
|
||||
|
||||
async def update_role(
|
||||
self,
|
||||
role_id: UUID,
|
||||
role_update: schemas.RoleUpdate
|
||||
) -> Optional[models.Role]:
|
||||
async def update_role(self, role_id: UUID, role_update: schemas.RoleUpdate) -> Optional[models.Role]:
|
||||
"""
|
||||
Update a role.
|
||||
"""
|
||||
|
||||
update_values = role_update.model_dump(exclude_unset=True)
|
||||
query = update(models.Role).\
|
||||
where(models.Role.role_id == role_id).\
|
||||
values(update_values)
|
||||
query = update(models.Role).where(models.Role.role_id == role_id).values(update_values)
|
||||
|
||||
await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
@@ -113,18 +102,12 @@ class RbacRepository(BaseRepository):
|
||||
await self._db_session.commit()
|
||||
return result.rowcount > 0
|
||||
|
||||
async def add_privilege_to_role(
|
||||
self,
|
||||
role_id: UUID,
|
||||
privilege: models.Privilege
|
||||
) -> Union[None, models.Role]:
|
||||
async def add_privilege_to_role(self, role_id: UUID, privilege: models.Privilege) -> Union[None, models.Role]:
|
||||
"""
|
||||
Add a privilege to a role.
|
||||
"""
|
||||
|
||||
query = select(models.Role).\
|
||||
options(selectinload(models.Role.privileges)).\
|
||||
where(models.Role.role_id == role_id)
|
||||
query = select(models.Role).options(selectinload(models.Role.privileges)).where(models.Role.role_id == role_id)
|
||||
result = await self._db_session.execute(query)
|
||||
role_db = result.scalars().first()
|
||||
if not role_db:
|
||||
@@ -142,18 +125,12 @@ class RbacRepository(BaseRepository):
|
||||
await self._db_session.refresh(role_db)
|
||||
return role_db
|
||||
|
||||
async def remove_privilege_from_role(
|
||||
self,
|
||||
role_id: UUID,
|
||||
privilege: models.Privilege
|
||||
) -> Union[None, models.Role]:
|
||||
async def remove_privilege_from_role(self, role_id: UUID, privilege: models.Privilege) -> Union[None, models.Role]:
|
||||
"""
|
||||
Remove a privilege from a role.
|
||||
"""
|
||||
|
||||
query = select(models.Role).\
|
||||
options(selectinload(models.Role.privileges)).\
|
||||
where(models.Role.role_id == role_id)
|
||||
query = select(models.Role).options(selectinload(models.Role.privileges)).where(models.Role.role_id == role_id)
|
||||
result = await self._db_session.execute(query)
|
||||
role_db = result.scalars().first()
|
||||
if not role_db:
|
||||
@@ -169,9 +146,7 @@ class RbacRepository(BaseRepository):
|
||||
Get all the role privileges.
|
||||
"""
|
||||
|
||||
query = select(models.Privilege).\
|
||||
join(models.Privilege.roles).\
|
||||
filter(models.Role.role_id == role_id)
|
||||
query = select(models.Privilege).join(models.Privilege.roles).filter(models.Role.role_id == role_id)
|
||||
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().all()
|
||||
@@ -237,15 +212,11 @@ class RbacRepository(BaseRepository):
|
||||
This method includes related user, group, and role information.
|
||||
"""
|
||||
|
||||
query = select(models.ACE).\
|
||||
where(
|
||||
(models.ACE.path == path) | (models.ACE.path.startswith(path + "/"))
|
||||
).\
|
||||
options(
|
||||
selectinload(models.ACE.user),
|
||||
selectinload(models.ACE.group),
|
||||
selectinload(models.ACE.role)
|
||||
)
|
||||
query = (
|
||||
select(models.ACE)
|
||||
.where((models.ACE.path == path) | (models.ACE.path.startswith(path + "/")))
|
||||
.options(selectinload(models.ACE.user), selectinload(models.ACE.group), selectinload(models.ACE.role))
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().all()
|
||||
|
||||
@@ -254,8 +225,7 @@ class RbacRepository(BaseRepository):
|
||||
Check if an ACE exists.
|
||||
"""
|
||||
|
||||
query = select(models.ACE).\
|
||||
where(models.ACE.path == path)
|
||||
query = select(models.ACE).where(models.ACE.path == path)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().first() is not None
|
||||
|
||||
@@ -271,19 +241,13 @@ class RbacRepository(BaseRepository):
|
||||
await self._db_session.refresh(db_ace)
|
||||
return db_ace
|
||||
|
||||
async def update_ace(
|
||||
self,
|
||||
ace_id: UUID,
|
||||
ace_update: schemas.ACEUpdate
|
||||
) -> Optional[models.ACE]:
|
||||
async def update_ace(self, ace_id: UUID, ace_update: schemas.ACEUpdate) -> Optional[models.ACE]:
|
||||
"""
|
||||
Update an ACE
|
||||
"""
|
||||
|
||||
update_values = ace_update.model_dump(exclude_unset=True)
|
||||
query = update(models.ACE).\
|
||||
where(models.ACE.ace_id == ace_id).\
|
||||
values(update_values)
|
||||
query = update(models.ACE).where(models.ACE.ace_id == ace_id).values(update_values)
|
||||
|
||||
await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
@@ -307,9 +271,7 @@ class RbacRepository(BaseRepository):
|
||||
Delete all ACEs starting with path.
|
||||
"""
|
||||
|
||||
query = delete(models.ACE).\
|
||||
where(models.ACE.path.startswith(path)).\
|
||||
execution_options(synchronize_session=False)
|
||||
query = delete(models.ACE).where(models.ACE.path.startswith(path)).execution_options(synchronize_session=False)
|
||||
result = await self._db_session.execute(query)
|
||||
log.debug(f"{result.rowcount} ACE(s) have been deleted")
|
||||
|
||||
@@ -344,9 +306,11 @@ class RbacRepository(BaseRepository):
|
||||
for ace_path, ace_propagate, ace_allowed, ace_privilege in aces:
|
||||
if ace_path.startswith("/pool"):
|
||||
resource_pool_id = ace_path.split("/")[2]
|
||||
query = select(models.Resource). \
|
||||
join(models.Resource.resource_pools). \
|
||||
filter(models.ResourcePool.resource_pool_id == resource_pool_id)
|
||||
query = (
|
||||
select(models.Resource)
|
||||
.join(models.Resource.resource_pools)
|
||||
.filter(models.ResourcePool.resource_pool_id == resource_pool_id)
|
||||
)
|
||||
|
||||
result = await self._db_session.execute(query)
|
||||
resources = result.scalars().all()
|
||||
@@ -366,13 +330,15 @@ class RbacRepository(BaseRepository):
|
||||
Retrieve all user ACEs matching the user_id and privilege name.
|
||||
"""
|
||||
|
||||
query = select(models.ACE.path, models.ACE.propagate, models.ACE.allowed, models.Privilege.name).\
|
||||
join(models.Privilege.roles).\
|
||||
join(models.Role.acl_entries).\
|
||||
join(models.ACE.user). \
|
||||
filter(models.User.user_id == user_id).\
|
||||
filter(models.Privilege.name == privilege_name).\
|
||||
order_by(models.ACE.path.desc())
|
||||
query = (
|
||||
select(models.ACE.path, models.ACE.propagate, models.ACE.allowed, models.Privilege.name)
|
||||
.join(models.Privilege.roles)
|
||||
.join(models.Role.acl_entries)
|
||||
.join(models.ACE.user)
|
||||
.filter(models.User.user_id == user_id)
|
||||
.filter(models.Privilege.name == privilege_name)
|
||||
.order_by(models.ACE.path.desc())
|
||||
)
|
||||
|
||||
result = await self._db_session.execute(query)
|
||||
return result.all()
|
||||
@@ -382,13 +348,15 @@ class RbacRepository(BaseRepository):
|
||||
Retrieve all group ACEs matching the user_id and privilege name.
|
||||
"""
|
||||
|
||||
query = select(models.ACE.path, models.ACE.propagate, models.ACE.allowed, models.Privilege.name). \
|
||||
join(models.Privilege.roles). \
|
||||
join(models.Role.acl_entries). \
|
||||
join(models.ACE.group). \
|
||||
join(models.UserGroup.users).\
|
||||
filter(models.User.user_id == user_id). \
|
||||
filter(models.Privilege.name == privilege_name)
|
||||
query = (
|
||||
select(models.ACE.path, models.ACE.propagate, models.ACE.allowed, models.Privilege.name)
|
||||
.join(models.Privilege.roles)
|
||||
.join(models.Role.acl_entries)
|
||||
.join(models.ACE.group)
|
||||
.join(models.UserGroup.users)
|
||||
.filter(models.User.user_id == user_id)
|
||||
.filter(models.Privilege.name == privilege_name)
|
||||
)
|
||||
|
||||
result = await self._db_session.execute(query)
|
||||
return result.all()
|
||||
@@ -404,12 +372,7 @@ class RbacRepository(BaseRepository):
|
||||
pool_resources.extend(await self._get_resources_in_pools(group_aces))
|
||||
return list(set(pool_resources))
|
||||
|
||||
async def get_accessible_project_ids(
|
||||
self,
|
||||
user_id: UUID,
|
||||
privilege_name: str,
|
||||
all_project_ids: List[str]
|
||||
):
|
||||
async def get_accessible_project_ids(self, user_id: UUID, privilege_name: str, all_project_ids: List[str]):
|
||||
"""
|
||||
Batch check which projects a user can access via direct ACE or resource pools.
|
||||
Performs 3 fixed DB queries regardless of project count.
|
||||
@@ -448,8 +411,11 @@ class RbacRepository(BaseRepository):
|
||||
user_denied.add(pid)
|
||||
|
||||
# --- User ACE: pool check ---
|
||||
user_pool_ids = {ace_path.split("/")[2] for ace_path, _, ace_allowed, _ in user_aces
|
||||
if ace_path.startswith("/pools/") and ace_allowed}
|
||||
user_pool_ids = {
|
||||
ace_path.split("/")[2]
|
||||
for ace_path, _, ace_allowed, _ in user_aces
|
||||
if ace_path.startswith("/pools/") and ace_allowed
|
||||
}
|
||||
|
||||
pool_accessible_ids = set()
|
||||
for pool_id, project_ids in pool_to_projects.items():
|
||||
@@ -470,8 +436,11 @@ class RbacRepository(BaseRepository):
|
||||
pass
|
||||
|
||||
# --- Group ACE: pool check ---
|
||||
group_pool_ids = {ace_path.split("/")[2] for ace_path, _, ace_allowed, _ in group_aces
|
||||
if ace_path.startswith("/pools/") and ace_allowed}
|
||||
group_pool_ids = {
|
||||
ace_path.split("/")[2]
|
||||
for ace_path, _, ace_allowed, _ in group_aces
|
||||
if ace_path.startswith("/pools/") and ace_allowed
|
||||
}
|
||||
|
||||
for pool_id, project_ids in pool_to_projects.items():
|
||||
if pool_id in group_pool_ids:
|
||||
|
||||
@@ -47,7 +47,6 @@ TEMPLATE_TYPE_TO_MODEL = {
|
||||
|
||||
|
||||
class TemplatesRepository(BaseRepository):
|
||||
|
||||
def __init__(self, db_session: AsyncSession) -> None:
|
||||
|
||||
super().__init__(db_session)
|
||||
@@ -57,17 +56,21 @@ class TemplatesRepository(BaseRepository):
|
||||
|
||||
async def get_template(self, template_id: UUID) -> Union[None, models.Template]:
|
||||
|
||||
query = select(models.Template).\
|
||||
options(selectinload(models.Template.images)).\
|
||||
where(models.Template.template_id == template_id)
|
||||
query = (
|
||||
select(models.Template)
|
||||
.options(selectinload(models.Template.images))
|
||||
.where(models.Template.template_id == template_id)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().first()
|
||||
|
||||
async def get_template_by_name_and_version(self, name: str, version: str) -> Union[None, models.Template]:
|
||||
|
||||
query = select(models.Template).\
|
||||
options(selectinload(models.Template.images)).\
|
||||
where(models.Template.name == name, models.Template.version == version)
|
||||
query = (
|
||||
select(models.Template)
|
||||
.options(selectinload(models.Template.images))
|
||||
.where(models.Template.name == name, models.Template.version == version)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().first()
|
||||
|
||||
@@ -76,9 +79,9 @@ class TemplatesRepository(BaseRepository):
|
||||
Return the first template with this name, regardless of version.
|
||||
"""
|
||||
|
||||
query = select(models.Template).\
|
||||
options(selectinload(models.Template.images)).\
|
||||
where(models.Template.name == name)
|
||||
query = (
|
||||
select(models.Template).options(selectinload(models.Template.images)).where(models.Template.name == name)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().first()
|
||||
|
||||
@@ -115,9 +118,11 @@ class TemplatesRepository(BaseRepository):
|
||||
|
||||
async def duplicate_template(self, template_id: UUID) -> Optional[schemas.Template]:
|
||||
|
||||
query = select(models.Template).\
|
||||
options(selectinload(models.Template.images)).\
|
||||
where(models.Template.template_id == template_id)
|
||||
query = (
|
||||
select(models.Template)
|
||||
.options(selectinload(models.Template.images))
|
||||
.where(models.Template.template_id == template_id)
|
||||
)
|
||||
db_template = (await self._db_session.execute(query)).scalars().first()
|
||||
if db_template:
|
||||
# duplicate db object with new primary key (template_id)
|
||||
@@ -136,8 +141,9 @@ class TemplatesRepository(BaseRepository):
|
||||
|
||||
image_dir, image_name = os.path.split(image_path)
|
||||
if image_dir:
|
||||
query = select(models.Image).\
|
||||
where(models.Image.filename == image_name, models.Image.path.endswith(image_path))
|
||||
query = select(models.Image).where(
|
||||
models.Image.filename == image_name, models.Image.path.endswith(image_path)
|
||||
)
|
||||
else:
|
||||
query = select(models.Image).where(models.Image.filename == image_name)
|
||||
query = query.order_by(models.Image.image_id)
|
||||
@@ -151,18 +157,16 @@ class TemplatesRepository(BaseRepository):
|
||||
)
|
||||
return images[0] if images else None
|
||||
|
||||
async def add_image_to_template(
|
||||
self,
|
||||
template_id: UUID,
|
||||
image: models.Image
|
||||
) -> Union[None, models.Template]:
|
||||
async def add_image_to_template(self, template_id: UUID, image: models.Image) -> Union[None, models.Template]:
|
||||
"""
|
||||
Add an image to template.
|
||||
"""
|
||||
|
||||
query = select(models.Template).\
|
||||
options(selectinload(models.Template.images)).\
|
||||
where(models.Template.template_id == template_id)
|
||||
query = (
|
||||
select(models.Template)
|
||||
.options(selectinload(models.Template.images))
|
||||
.where(models.Template.template_id == template_id)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
template_in_db = result.scalars().first()
|
||||
if not template_in_db:
|
||||
@@ -173,18 +177,16 @@ class TemplatesRepository(BaseRepository):
|
||||
await self._db_session.refresh(template_in_db)
|
||||
return template_in_db
|
||||
|
||||
async def remove_image_from_template(
|
||||
self,
|
||||
template_id: UUID,
|
||||
image: models.Image
|
||||
) -> Union[None, models.Template]:
|
||||
async def remove_image_from_template(self, template_id: UUID, image: models.Image) -> Union[None, models.Template]:
|
||||
"""
|
||||
Remove an image from a template.
|
||||
"""
|
||||
|
||||
query = select(models.Template).\
|
||||
options(selectinload(models.Template.images)).\
|
||||
where(models.Template.template_id == template_id)
|
||||
query = (
|
||||
select(models.Template)
|
||||
.options(selectinload(models.Template.images))
|
||||
.where(models.Template.template_id == template_id)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
template_in_db = result.scalars().first()
|
||||
if not template_in_db:
|
||||
@@ -201,8 +203,6 @@ class TemplatesRepository(BaseRepository):
|
||||
Return all images attached to a template.
|
||||
"""
|
||||
|
||||
query = select(models.Image).\
|
||||
join(models.Image.templates).\
|
||||
filter(models.Template.template_id == template_id)
|
||||
query = select(models.Image).join(models.Image.templates).filter(models.Template.template_id == template_id)
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().all()
|
||||
@@ -33,7 +33,6 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UsersRepository(BaseRepository):
|
||||
|
||||
def __init__(self, db_session: AsyncSession) -> None:
|
||||
|
||||
super().__init__(db_session)
|
||||
@@ -82,10 +81,7 @@ class UsersRepository(BaseRepository):
|
||||
|
||||
hashed_password = self._auth_service.hash_password(user.password.get_secret_value())
|
||||
db_user = models.User(
|
||||
username=user.username,
|
||||
email=user.email,
|
||||
full_name=user.full_name,
|
||||
hashed_password=hashed_password
|
||||
username=user.username, email=user.email, full_name=user.full_name, hashed_password=hashed_password
|
||||
)
|
||||
self._db_session.add(db_user)
|
||||
await self._db_session.commit()
|
||||
@@ -102,9 +98,7 @@ class UsersRepository(BaseRepository):
|
||||
if password:
|
||||
update_values["hashed_password"] = self._auth_service.hash_password(password=password.get_secret_value())
|
||||
|
||||
query = update(models.User).\
|
||||
where(models.User.user_id == user_id).\
|
||||
values(update_values)
|
||||
query = update(models.User).where(models.User.user_id == user_id).values(update_values)
|
||||
|
||||
await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
@@ -118,9 +112,11 @@ class UsersRepository(BaseRepository):
|
||||
Increment token_version to invalidate all existing tokens for the user.
|
||||
"""
|
||||
|
||||
query = update(models.User).\
|
||||
where(models.User.user_id == user_id).\
|
||||
values(token_version=models.User.token_version + 1)
|
||||
query = (
|
||||
update(models.User)
|
||||
.where(models.User.user_id == user_id)
|
||||
.values(token_version=models.User.token_version + 1)
|
||||
)
|
||||
|
||||
await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
@@ -147,8 +143,9 @@ class UsersRepository(BaseRepository):
|
||||
# this is useful for manual password recovery like:
|
||||
# sqlite3 gns3_controller.db "UPDATE users SET hashed_password = null WHERE username = 'admin';"
|
||||
if user.hashed_password is None:
|
||||
log.warning(f"User '{username}' has been authenticated without a password "
|
||||
f"configured. Please set a new password.")
|
||||
log.warning(
|
||||
f"User '{username}' has been authenticated without a password configured. Please set a new password."
|
||||
)
|
||||
return user
|
||||
if not self._auth_service.verify_password(password, user.hashed_password):
|
||||
return None
|
||||
@@ -168,9 +165,7 @@ class UsersRepository(BaseRepository):
|
||||
Get all user memberships (user groups).
|
||||
"""
|
||||
|
||||
query = select(models.UserGroup).\
|
||||
join(models.UserGroup.users).\
|
||||
filter(models.User.user_id == user_id)
|
||||
query = select(models.UserGroup).join(models.UserGroup.users).filter(models.User.user_id == user_id)
|
||||
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().all()
|
||||
@@ -214,18 +209,14 @@ class UsersRepository(BaseRepository):
|
||||
return db_user_group
|
||||
|
||||
async def update_user_group(
|
||||
self,
|
||||
user_group_id: UUID,
|
||||
user_group_update: schemas.UserGroupUpdate
|
||||
self, user_group_id: UUID, user_group_update: schemas.UserGroupUpdate
|
||||
) -> Optional[models.UserGroup]:
|
||||
"""
|
||||
Update a user group.
|
||||
"""
|
||||
|
||||
update_values = user_group_update.model_dump(exclude_unset=True)
|
||||
query = update(models.UserGroup).\
|
||||
where(models.UserGroup.user_group_id == user_group_id).\
|
||||
values(update_values)
|
||||
query = update(models.UserGroup).where(models.UserGroup.user_group_id == user_group_id).values(update_values)
|
||||
|
||||
await self._db_session.execute(query)
|
||||
await self._db_session.commit()
|
||||
@@ -244,18 +235,16 @@ class UsersRepository(BaseRepository):
|
||||
await self._db_session.commit()
|
||||
return result.rowcount > 0
|
||||
|
||||
async def add_member_to_user_group(
|
||||
self,
|
||||
user_group_id: UUID,
|
||||
user: models.User
|
||||
) -> Union[None, models.UserGroup]:
|
||||
async def add_member_to_user_group(self, user_group_id: UUID, user: models.User) -> Union[None, models.UserGroup]:
|
||||
"""
|
||||
Add a member to a user group.
|
||||
"""
|
||||
|
||||
query = select(models.UserGroup).\
|
||||
options(selectinload(models.UserGroup.users)).\
|
||||
where(models.UserGroup.user_group_id == user_group_id)
|
||||
query = (
|
||||
select(models.UserGroup)
|
||||
.options(selectinload(models.UserGroup.users))
|
||||
.where(models.UserGroup.user_group_id == user_group_id)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
user_group_db = result.scalars().first()
|
||||
if not user_group_db:
|
||||
@@ -267,17 +256,17 @@ class UsersRepository(BaseRepository):
|
||||
return user_group_db
|
||||
|
||||
async def remove_member_from_user_group(
|
||||
self,
|
||||
user_group_id: UUID,
|
||||
user: models.User
|
||||
self, user_group_id: UUID, user: models.User
|
||||
) -> Union[None, models.UserGroup]:
|
||||
"""
|
||||
Remove a member from a user group.
|
||||
"""
|
||||
|
||||
query = select(models.UserGroup).\
|
||||
options(selectinload(models.UserGroup.users)).\
|
||||
where(models.UserGroup.user_group_id == user_group_id)
|
||||
query = (
|
||||
select(models.UserGroup)
|
||||
.options(selectinload(models.UserGroup.users))
|
||||
.where(models.UserGroup.user_group_id == user_group_id)
|
||||
)
|
||||
result = await self._db_session.execute(query)
|
||||
user_group_db = result.scalars().first()
|
||||
if not user_group_db:
|
||||
@@ -293,9 +282,7 @@ class UsersRepository(BaseRepository):
|
||||
Get all members from a user group.
|
||||
"""
|
||||
|
||||
query = select(models.User).\
|
||||
join(models.User.groups).\
|
||||
filter(models.UserGroup.user_group_id == user_group_id)
|
||||
query = select(models.User).join(models.User.groups).filter(models.UserGroup.user_group_id == user_group_id)
|
||||
|
||||
result = await self._db_session.execute(query)
|
||||
return result.scalars().all()
|
||||
+23
-19
@@ -79,7 +79,9 @@ async def connect_to_db(app: FastAPI) -> None:
|
||||
|
||||
db_path = os.path.join(Config.instance().config_dir, "gns3_controller.db")
|
||||
db_url = os.environ.get("GNS3_DATABASE_URI", f"sqlite+aiosqlite:///{db_path}")
|
||||
engine = create_async_engine(db_url, connect_args={"check_same_thread": False, "timeout": 20}, future=True, pool_size=512, max_overflow=1024)
|
||||
engine = create_async_engine(
|
||||
db_url, connect_args={"check_same_thread": False, "timeout": 20}, future=True, pool_size=512, max_overflow=1024
|
||||
)
|
||||
|
||||
# Register PRAGMA on the sync engine to ensure it fires for async connections
|
||||
@event.listens_for(engine.sync_engine, "connect")
|
||||
@@ -91,19 +93,21 @@ async def connect_to_db(app: FastAPI) -> None:
|
||||
|
||||
# Verify WAL mode is active
|
||||
async with engine.connect() as _verify_conn:
|
||||
|
||||
def _check_wal(conn):
|
||||
cursor = conn.connection.cursor()
|
||||
cursor.execute("PRAGMA journal_mode")
|
||||
row = cursor.fetchone()
|
||||
cursor.close()
|
||||
return row[0] if row else "unknown"
|
||||
|
||||
wal_mode = await _verify_conn.run_sync(_check_wal)
|
||||
log.info(f"SQLite journal mode: {wal_mode}")
|
||||
if wal_mode and wal_mode.upper() != "WAL":
|
||||
log.warning("WAL mode not active - concurrent writes may cause 'database is locked' errors")
|
||||
alembic_cfg = config.Config()
|
||||
alembic_cfg.set_main_option("script_location", "gns3server:db_migrations")
|
||||
#alembic_cfg.set_main_option('sqlalchemy.url', db_url)
|
||||
# alembic_cfg.set_main_option('sqlalchemy.url', db_url)
|
||||
try:
|
||||
async with engine.connect() as conn:
|
||||
current_rev, head_rev = await conn.run_sync(check_revision, alembic_cfg)
|
||||
@@ -116,27 +120,27 @@ async def connect_to_db(app: FastAPI) -> None:
|
||||
inspector = sa.inspect(connection)
|
||||
tables = inspector.get_table_names()
|
||||
|
||||
if 'users' not in tables:
|
||||
return 'new' # Truly new database
|
||||
if "users" not in tables:
|
||||
return "new" # Truly new database
|
||||
|
||||
# Check for new feature columns that indicate this is already migrated
|
||||
columns = [col['name'] for col in inspector.get_columns('users')]
|
||||
if 'llm_model_configs' in tables:
|
||||
columns = [col["name"] for col in inspector.get_columns("users")]
|
||||
if "llm_model_configs" in tables:
|
||||
# The llm_model_configs table already exists (created from code)
|
||||
return 'new_with_llm_configs'
|
||||
return "new_with_llm_configs"
|
||||
else:
|
||||
# Old database without llm_model_configs table, needs migration
|
||||
return 'old_needs_migration'
|
||||
return "old_needs_migration"
|
||||
|
||||
db_state = await conn.run_sync(check_db_state)
|
||||
|
||||
if db_state == 'new':
|
||||
if db_state == "new":
|
||||
# Truly new database: create all tables and stamp
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
await conn.run_sync(run_stamp, alembic_cfg)
|
||||
await conn.commit()
|
||||
log.info("Created new database and stamped to head revision")
|
||||
elif db_state == 'new_with_llm_configs':
|
||||
elif db_state == "new_with_llm_configs":
|
||||
# Database already has llm_model_configs table (from Base.metadata.create_all)
|
||||
# Just stamp the version
|
||||
await conn.run_sync(run_stamp, alembic_cfg)
|
||||
@@ -216,6 +220,7 @@ async def update_disk_checksums(updated_disks: List[str]) -> None:
|
||||
"""
|
||||
|
||||
from gns3server.api.server import app
|
||||
|
||||
async with AsyncSession(app.state._db_engine) as db_session:
|
||||
images_repository = ImagesRepository(db_session)
|
||||
for path in updated_disks:
|
||||
@@ -226,6 +231,7 @@ async def update_disk_checksums(updated_disks: List[str]) -> None:
|
||||
if image.checksum != checksum:
|
||||
await images_repository.update_image(path, checksum, "md5")
|
||||
|
||||
|
||||
class EventHandler(PatternMatchingEventHandler):
|
||||
"""
|
||||
Watchdog event handler.
|
||||
@@ -237,7 +243,7 @@ class EventHandler(PatternMatchingEventHandler):
|
||||
self._queue = queue
|
||||
|
||||
# ignore temporary files, md5sum files, hidden files and directories
|
||||
super().__init__(ignore_patterns=["*.tmp", "*.md5sum", ".*"], ignore_directories = True, **kwargs)
|
||||
super().__init__(ignore_patterns=["*.tmp", "*.md5sum", ".*"], ignore_directories=True, **kwargs)
|
||||
|
||||
def on_closed(self, event: FileSystemEvent) -> None:
|
||||
# monitor for closed files (e.g. when a file has finished to be copied)
|
||||
@@ -245,6 +251,7 @@ class EventHandler(PatternMatchingEventHandler):
|
||||
return # ignore custom IOU libraries
|
||||
self._loop.call_soon_threadsafe(self._queue.put_nowait, event)
|
||||
|
||||
|
||||
class EventIterator(object):
|
||||
"""
|
||||
Watchdog Event iterator.
|
||||
@@ -263,13 +270,11 @@ class EventIterator(object):
|
||||
raise StopAsyncIteration
|
||||
return item
|
||||
|
||||
|
||||
async def monitor_images_on_filesystem(app: FastAPI):
|
||||
|
||||
def watchdog(
|
||||
path: str,
|
||||
queue: asyncio.Queue,
|
||||
loop: asyncio.BaseEventLoop,
|
||||
app: FastAPI, recursive: bool = False
|
||||
path: str, queue: asyncio.Queue, loop: asyncio.BaseEventLoop, app: FastAPI, recursive: bool = False
|
||||
) -> None:
|
||||
"""
|
||||
Thread to monitor a directory for new images.
|
||||
@@ -294,7 +299,7 @@ async def monitor_images_on_filesystem(app: FastAPI):
|
||||
loop = asyncio.get_event_loop()
|
||||
server_config = Config.instance().settings.Server
|
||||
image_dir = os.path.expanduser(server_config.images_path)
|
||||
asyncio.get_event_loop().run_in_executor(None, watchdog,image_dir, queue, loop, app, True)
|
||||
asyncio.get_event_loop().run_in_executor(None, watchdog, image_dir, queue, loop, app, True)
|
||||
|
||||
async for filesystem_event in EventIterator(queue):
|
||||
# read the file system event from the queue
|
||||
@@ -352,7 +357,7 @@ async def get_user_llm_config_full(user_id: str, app: FastAPI) -> Optional[dict]
|
||||
result = await repo.get_user_effective_configs(
|
||||
user_uuid,
|
||||
current_user_id=user_uuid, # Viewing own config
|
||||
current_user_is_superadmin=False
|
||||
current_user_is_superadmin=False,
|
||||
)
|
||||
|
||||
if not result or not result.get("default_config"):
|
||||
@@ -393,7 +398,7 @@ async def get_user_llm_config_full(user_id: str, app: FastAPI) -> Optional[dict]
|
||||
"group_name": default_config.get("group_name"),
|
||||
"user_id": str(full_config.user_id) if full_config.user_id else None,
|
||||
"group_id": str(full_config.group_id) if full_config.group_id else None,
|
||||
**config_data # provider, api_key, model, temperature, etc.
|
||||
**config_data, # provider, api_key, model, temperature, etc.
|
||||
}
|
||||
|
||||
# Validate required fields
|
||||
@@ -415,4 +420,3 @@ async def get_user_llm_config_full(user_id: str, app: FastAPI) -> Optional[dict]
|
||||
except Exception as e:
|
||||
log.error(f"Failed to retrieve LLM config for user {user_id}: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
Reference in new issue
Block a user