diff --git a/backend/capellacollab/core/database/__init__.py b/backend/capellacollab/core/database/__init__.py
index 12fdc4be6d..294a7b73c9 100644
--- a/backend/capellacollab/core/database/__init__.py
+++ b/backend/capellacollab/core/database/__init__.py
@@ -21,6 +21,8 @@ class Base(orm.DeclarativeBase):
type_annotation_map = {
dict[str, str]: postgresql.JSONB,
dict[str, t.Any]: postgresql.JSONB,
+ dict[t.Any, t.Any]: postgresql.JSONB,
+ dict[str, bool]: postgresql.JSONB,
}
diff --git a/backend/capellacollab/core/database/migration.py b/backend/capellacollab/core/database/migration.py
index 66ddbc1491..5c6bcd8312 100644
--- a/backend/capellacollab/core/database/migration.py
+++ b/backend/capellacollab/core/database/migration.py
@@ -36,8 +36,6 @@
)
from capellacollab.tools import crud as tools_crud
from capellacollab.tools import models as tools_models
-from capellacollab.tools.integrations import crud as integrations_crud
-from capellacollab.tools.integrations import models as integrations_models
from capellacollab.users import crud as users_crud
from capellacollab.users import models as users_models
@@ -151,14 +149,9 @@ def create_tools(db):
jupyter = tools_models.DatabaseTool(
name="Jupyter",
- docker_image_template=f"{registry}/jupyter-notebook:$version",
+ integrations=tools_models.ToolIntegrations(jupyter=True),
)
tools_crud.create_tool(db, jupyter)
- integrations_crud.update_integrations(
- db,
- jupyter.integrations,
- integrations_models.PatchToolIntegrations(jupyter=True),
- )
default_version = tools_crud.create_version(db, capella.id, "6.0.0", True)
tools_crud.create_version(db, capella.id, "5.2.0")
diff --git a/backend/capellacollab/core/database/models.py b/backend/capellacollab/core/database/models.py
index 84bd3650c3..6ad2c6a72a 100644
--- a/backend/capellacollab/core/database/models.py
+++ b/backend/capellacollab/core/database/models.py
@@ -19,7 +19,6 @@
import capellacollab.settings.integrations.purevariants.models
import capellacollab.settings.modelsources.git.models
import capellacollab.settings.modelsources.t4c.models
-import capellacollab.tools.integrations.models
import capellacollab.tools.models
import capellacollab.users.models
import capellacollab.users.tokens.models
diff --git a/backend/capellacollab/projects/toolmodels/restrictions/routes.py b/backend/capellacollab/projects/toolmodels/restrictions/routes.py
index 9819b93274..60984dba4f 100644
--- a/backend/capellacollab/projects/toolmodels/restrictions/routes.py
+++ b/backend/capellacollab/projects/toolmodels/restrictions/routes.py
@@ -46,7 +46,10 @@ def update_restrictions(
),
db: orm.Session = fastapi.Depends(database.get_db),
) -> models.DatabaseToolModelRestrictions:
- if body.allow_pure_variants and not model.tool.integrations.pure_variants:
+ if (
+ body.allow_pure_variants
+ and not model.tool.integrations["pure_variants"]
+ ):
raise fastapi.HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail={
diff --git a/backend/capellacollab/projects/toolmodels/routes.py b/backend/capellacollab/projects/toolmodels/routes.py
index 9c5942c77a..2e60efa37b 100644
--- a/backend/capellacollab/projects/toolmodels/routes.py
+++ b/backend/capellacollab/projects/toolmodels/routes.py
@@ -86,7 +86,7 @@ def create_new_tool_model(
)
configuration = {}
- if tool.integrations.jupyter:
+ if tool.integrations["jupyter"]:
configuration["workspace"] = str(uuid.uuid4())
try:
@@ -102,7 +102,7 @@ def create_new_tool_model(
},
)
- if tool.integrations.jupyter:
+ if tool.integrations["jupyter"]:
workspace.create_shared_workspace(
configuration["workspace"], project, model, "2Gi"
)
@@ -230,7 +230,7 @@ def delete_tool_model(
)
if (
- model.tool.integrations.jupyter
+ model.tool.integrations["jupyter"]
and model.configuration
and "workspace" in model.configuration
):
diff --git a/backend/capellacollab/settings/modelsources/t4c/repositories/models.py b/backend/capellacollab/settings/modelsources/t4c/repositories/models.py
index 2786d71f15..061355f083 100644
--- a/backend/capellacollab/settings/modelsources/t4c/repositories/models.py
+++ b/backend/capellacollab/settings/modelsources/t4c/repositories/models.py
@@ -45,7 +45,7 @@ class DatabaseT4CRepository(database.Base):
class CreateT4CRepository(pydantic.BaseModel):
name: str = pydantic.Field(
- pattern="^[-a-zA-Z0-9_]+$", examples=["testrepo"]
+ pattern=r"^[-a-zA-Z0-9_]+$", examples=["testrepo"]
)
diff --git a/backend/capellacollab/tools/crud.py b/backend/capellacollab/tools/crud.py
index 9692e9b8ec..9fd0c6189e 100644
--- a/backend/capellacollab/tools/crud.py
+++ b/backend/capellacollab/tools/crud.py
@@ -7,7 +7,7 @@
from sqlalchemy import exc, orm
from capellacollab.core import database
-from capellacollab.tools.integrations import models as integrations_models
+from capellacollab.tools import models as tools_models
from . import exceptions, models
@@ -37,7 +37,7 @@ def get_tool_by_name(
def create_tool(
db: orm.Session, tool: models.DatabaseTool
) -> models.DatabaseTool:
- tool.integrations = integrations_models.DatabaseToolIntegrations(
+ tool.integrations = tools_models.ToolIntegrations(
pure_variants=False, t4c=False, jupyter=False
)
db.add(tool)
@@ -61,21 +61,6 @@ def update_tool_name(
return tool
-def update_tool_dockerimages(
- db: orm.Session,
- tool: models.DatabaseTool,
- patch_tool: models.PatchToolDockerimage,
-) -> models.DatabaseTool:
- if patch_tool.persistent:
- tool.docker_image_template = patch_tool.persistent
- if patch_tool.readonly:
- tool.readonly_docker_image_template = patch_tool.readonly
- if patch_tool.backup:
- tool.docker_image_backup_template = patch_tool.backup
- db.commit()
- return tool
-
-
def delete_tool(db: orm.Session, tool: models.DatabaseTool) -> None:
db.delete(tool)
db.commit()
@@ -261,7 +246,7 @@ def get_backup_image_for_tool_version(db: orm.Session, version_id: int) -> str:
if not (version := get_version_by_id(db, version_id)):
raise exceptions.ToolVersionNotFoundError(version_id)
- backup_image_template = version.tool.docker_image_backup_template
+ backup_image_template = version.config[""]
if not backup_image_template:
raise exceptions.ToolImageNotFoundError(
diff --git a/backend/capellacollab/tools/integrations/__init__.py b/backend/capellacollab/tools/integrations/__init__.py
deleted file mode 100644
index 04412280d8..0000000000
--- a/backend/capellacollab/tools/integrations/__init__.py
+++ /dev/null
@@ -1,2 +0,0 @@
-# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
-# SPDX-License-Identifier: Apache-2.0
diff --git a/backend/capellacollab/tools/integrations/crud.py b/backend/capellacollab/tools/integrations/crud.py
deleted file mode 100644
index c8ee370877..0000000000
--- a/backend/capellacollab/tools/integrations/crud.py
+++ /dev/null
@@ -1,21 +0,0 @@
-# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
-# SPDX-License-Identifier: Apache-2.0
-
-from sqlalchemy import orm
-
-from capellacollab.core import database
-
-from . import models
-
-
-def update_integrations(
- db: orm.Session,
- integrations: models.DatabaseToolIntegrations,
- patch_integrations: models.PatchToolIntegrations,
-) -> models.DatabaseToolIntegrations:
- database.patch_database_with_pydantic_object(
- integrations, patch_integrations
- )
-
- db.commit()
- return integrations
diff --git a/backend/capellacollab/tools/integrations/models.py b/backend/capellacollab/tools/integrations/models.py
deleted file mode 100644
index 9615d5663b..0000000000
--- a/backend/capellacollab/tools/integrations/models.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
-# SPDX-License-Identifier: Apache-2.0
-
-from __future__ import annotations
-
-import typing as t
-
-import pydantic
-import sqlalchemy as sa
-from sqlalchemy import orm
-
-from capellacollab.core import database
-
-if t.TYPE_CHECKING:
- from capellacollab.tools.models import DatabaseTool
-
-
-class ToolIntegrations(pydantic.BaseModel):
- model_config = pydantic.ConfigDict(from_attributes=True)
-
- t4c: bool
- pure_variants: bool
- jupyter: bool
-
-
-class PatchToolIntegrations(pydantic.BaseModel):
- t4c: bool | None = None
- pure_variants: bool | None = None
- jupyter: bool | None = None
-
-
-class DatabaseToolIntegrations(database.Base):
- __tablename__ = "tool_integrations"
-
- id: orm.Mapped[int] = orm.mapped_column(primary_key=True)
-
- tool_id: orm.Mapped[int] = orm.mapped_column(sa.ForeignKey("tools.id"))
- tool: orm.Mapped[DatabaseTool] = orm.relationship(
- back_populates="integrations"
- )
-
- t4c: orm.Mapped[bool] = orm.mapped_column(default=False)
- pure_variants: orm.Mapped[bool] = orm.mapped_column(default=False)
- jupyter: orm.Mapped[bool] = orm.mapped_column(default=False)
diff --git a/backend/capellacollab/tools/integrations/routes.py b/backend/capellacollab/tools/integrations/routes.py
deleted file mode 100644
index a5953e333e..0000000000
--- a/backend/capellacollab/tools/integrations/routes.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors
-# SPDX-License-Identifier: Apache-2.0
-
-import fastapi
-from sqlalchemy import orm
-
-from capellacollab.core import database
-from capellacollab.core.authentication import injectables as auth_injectables
-from capellacollab.users import models as users_models
-
-from .. import injectables as tools_injectables
-from .. import models as tools_models
-from . import crud, models
-
-router = fastapi.APIRouter(
- dependencies=[
- fastapi.Depends(
- auth_injectables.RoleVerification(
- required_role=users_models.Role.ADMIN
- )
- )
- ]
-)
-
-
-@router.put("", response_model=models.ToolIntegrations)
-def update_integrations(
- body: models.PatchToolIntegrations,
- tool: tools_models.DatabaseTool = fastapi.Depends(
- tools_injectables.get_existing_tool
- ),
- db: orm.Session = fastapi.Depends(database.get_db),
-) -> models.DatabaseToolIntegrations:
- return crud.update_integrations(db, tool.integrations, body)
diff --git a/backend/capellacollab/tools/models.py b/backend/capellacollab/tools/models.py
index ec682ad095..7e583e65f6 100644
--- a/backend/capellacollab/tools/models.py
+++ b/backend/capellacollab/tools/models.py
@@ -9,12 +9,19 @@
import pydantic
import sqlalchemy as sa
from sqlalchemy import orm
+from sqlalchemy.dialects import postgresql
from capellacollab.core import database
-from capellacollab.tools.integrations import models as integrations_models
-if t.TYPE_CHECKING:
- from .integrations.models import DatabaseToolIntegrations
+DOCKER_IMAGE_PATTERN = r"^[a-zA-Z0-9][a-zA-Z0-9_\-/.:$]*$"
+
+
+class ToolIntegrations(pydantic.BaseModel):
+ model_config = pydantic.ConfigDict(from_attributes=True)
+
+ t4c: bool
+ pure_variants: bool
+ jupyter: bool
class DatabaseTool(database.Base):
@@ -23,9 +30,6 @@ class DatabaseTool(database.Base):
id: orm.Mapped[int] = orm.mapped_column(primary_key=True)
name: orm.Mapped[str]
- docker_image_template: orm.Mapped[str]
- docker_image_backup_template: orm.Mapped[str | None]
- readonly_docker_image_template: orm.Mapped[str | None]
versions: orm.Mapped[list[DatabaseVersion]] = orm.relationship(
back_populates="tool"
@@ -34,8 +38,8 @@ class DatabaseTool(database.Base):
back_populates="tool"
)
- integrations: orm.Mapped[DatabaseToolIntegrations] = orm.relationship(
- back_populates="tool", uselist=False
+ integrations: orm.Mapped[ToolIntegrations] = orm.mapped_column(
+ postgresql.JSONB
)
@@ -45,9 +49,7 @@ class DatabaseVersion(database.Base):
id: orm.Mapped[int] = orm.mapped_column(primary_key=True)
- name: orm.Mapped[str]
- is_recommended: orm.Mapped[bool]
- is_deprecated: orm.Mapped[bool]
+ config: dict[t.Any, t.Any] # TODO: Add type annotation
tool_id: orm.Mapped[int | None] = orm.mapped_column(
sa.ForeignKey("tools.id")
@@ -75,27 +77,31 @@ class ToolBase(pydantic.BaseModel):
id: int
name: str
- integrations: integrations_models.ToolIntegrations
+ integrations: ToolIntegrations
-class ToolDockerimage(pydantic.BaseModel):
+class ToolConfiguration(pydantic.BaseModel):
model_config = pydantic.ConfigDict(from_attributes=True)
- persistent: str = pydantic.Field(
- ..., validation_alias="docker_image_template"
- )
- readonly: str | None = pydantic.Field(
- None, validation_alias="readonly_docker_image_template"
- )
- backup: str | None = pydantic.Field(
- None, validation_alias="docker_image_backup_template"
- )
+ name: str
+
+ versions: list[ToolVersionBase]
+ natures: list[ToolNatureBase]
+ integrations: ToolIntegrations
-class PatchToolDockerimage(pydantic.BaseModel):
- persistent: str | None = None
- readonly: str | None = None
- backup: str | None = None
+ sessions: None
+ backups: None
+
+
+class ToolBackupConfiguration(pydantic.BaseModel):
+ image: str = pydantic.Field(
+ pattern=DOCKER_IMAGE_PATTERN,
+ examples=[
+ "docker.io/hello-world:latest",
+ "ghcr.io/dsd-dbs/capella-dockerimages/capella/remote:main",
+ ],
+ )
class CreateToolVersion(pydantic.BaseModel):
diff --git a/backend/capellacollab/tools/routes.py b/backend/capellacollab/tools/routes.py
index 0b89b32072..231f6543ac 100644
--- a/backend/capellacollab/tools/routes.py
+++ b/backend/capellacollab/tools/routes.py
@@ -12,9 +12,6 @@
from capellacollab.core import database
from capellacollab.core import exceptions as core_exceptions
from capellacollab.core.authentication import injectables as auth_injectables
-from capellacollab.tools.integrations import (
- routes as tools_integrations_routes,
-)
from capellacollab.users import models as users_models
from . import crud, injectables, models
@@ -222,47 +219,6 @@ def delete_tool_nature(
crud.delete_nature(db, nature)
-@router.get(
- "/{tool_id}/dockerimages",
- response_model=models.ToolDockerimage,
- dependencies=[
- fastapi.Depends(
- auth_injectables.RoleVerification(
- required_role=users_models.Role.ADMIN
- )
- )
- ],
-)
-def get_dockerimages(
- tool: models.DatabaseTool = fastapi.Depends(injectables.get_existing_tool),
-) -> models.DatabaseTool:
- return tool
-
-
-@router.put(
- "/{tool_id}/dockerimages",
- response_model=models.ToolDockerimage,
- dependencies=[
- fastapi.Depends(
- auth_injectables.RoleVerification(
- required_role=users_models.Role.ADMIN
- )
- )
- ],
-)
-def update_dockerimages(
- body: models.PatchToolDockerimage,
- tool: models.DatabaseTool = fastapi.Depends(injectables.get_existing_tool),
- db: orm.Session = fastapi.Depends(database.get_db),
-) -> models.DatabaseTool:
- return crud.update_tool_dockerimages(db, tool, body)
-
-
-router.include_router(
- tools_integrations_routes.router, prefix="/{tool_id}/integrations"
-)
-
-
def raise_when_tool_dependency_exist(
db: orm.Session, tool: models.DatabaseTool
) -> None:
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index 31aef06f16..950e46ad0d 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -51,6 +51,7 @@ import {
HighlightPipeTransform,
ModelDiagramCodeBlockComponent,
} from 'src/app/projects/models/diagrams/model-diagram-dialog/model-diagram-code-block/model-diagram-code-block.component';
+import { CreateToolComponent } from 'src/app/settings/core/tools-settings/create-tool/create-tool.component';
import { BasicAuthTokenComponent } from 'src/app/users/basic-auth-token/basic-auth-token.component';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
@@ -175,6 +176,7 @@ import { UsersProfileComponent } from './users/users-profile/users-profile.compo
CreateReadonlySessionComponent,
CreateReadonlySessionDialogComponent,
CreateT4cModelNewRepositoryComponent,
+ CreateToolComponent,
DeleteGitSettingsDialogComponent,
DeleteSessionDialogComponent,
DisplayValueComponent,
diff --git a/frontend/src/app/settings/core/configuration-settings/configuration-settings.component.ts b/frontend/src/app/settings/core/configuration-settings/configuration-settings.component.ts
index 4d35987e1a..18e30f7434 100644
--- a/frontend/src/app/settings/core/configuration-settings/configuration-settings.component.ts
+++ b/frontend/src/app/settings/core/configuration-settings/configuration-settings.component.ts
@@ -35,7 +35,6 @@ export class ConfigurationSettingsComponent implements OnInit {
});
}
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
submitValue(value: any) {
this.configurationSettingsService
.putConfigurationSettings('global', value)
diff --git a/frontend/src/app/settings/core/tools-settings/create-tool/create-tool.component.html b/frontend/src/app/settings/core/tools-settings/create-tool/create-tool.component.html
new file mode 100644
index 0000000000..3e38063829
--- /dev/null
+++ b/frontend/src/app/settings/core/tools-settings/create-tool/create-tool.component.html
@@ -0,0 +1,6 @@
+
+
+