diff --git a/backend/capellacollab/__main__.py b/backend/capellacollab/__main__.py index 5b6b1230dd..f26c124ee0 100644 --- a/backend/capellacollab/__main__.py +++ b/backend/capellacollab/__main__.py @@ -14,7 +14,7 @@ import capellacollab.projects.toolmodels.backups.runs.interface as pipeline_runs_interface import capellacollab.sessions.metrics as sessions_metrics -import capellacollab.settings.modelsources.t4c.metrics as t4c_metrics +import capellacollab.settings.modelsources.t4c.license_server.metrics as t4c_metrics from capellacollab import core # This import statement is required and should not be removed! (Alembic will not work otherwise) diff --git a/backend/capellacollab/alembic/versions/3818a5009130_split_t4c_instances_and_license_servers.py b/backend/capellacollab/alembic/versions/3818a5009130_split_t4c_instances_and_license_servers.py new file mode 100644 index 0000000000..203d12b6c4 --- /dev/null +++ b/backend/capellacollab/alembic/versions/3818a5009130_split_t4c_instances_and_license_servers.py @@ -0,0 +1,111 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +"""Split T4C Instances and License Servers + +Revision ID: 3818a5009130 +Revises: 7cf3357ddd7b +Create Date: 2024-10-01 15:46:26.054936 + +""" +import sqlalchemy as sa +from alembic import op +from sqlalchemy.orm import Session + +# revision identifiers, used by Alembic. +revision = "3818a5009130" +down_revision = "7cf3357ddd7b" +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "t4c_license_servers", + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("name", sa.String(), nullable=False), + sa.Column("usage_api", sa.String(), nullable=False), + sa.Column("license_key", sa.String(), nullable=False), + sa.PrimaryKeyConstraint("id"), + sa.UniqueConstraint("name"), + ) + op.create_index( + op.f("ix_t4c_license_servers_id"), + "t4c_license_servers", + ["id"], + unique=True, + ) + op.add_column( + "t4c_instances", + sa.Column("license_server_id", sa.Integer(), nullable=True), + ) + op.create_foreign_key( + None, + "t4c_instances", + "t4c_license_servers", + ["license_server_id"], + ["id"], + ) + + # Fetch existing t4c_instances and group by usage_api and license + bind = op.get_bind() + session = Session(bind=bind) + t4c_instances = session.execute( + sa.text("SELECT id, usage_api, license FROM t4c_instances") + ).fetchall() + + grouped_instances = {} + for instance in t4c_instances: + key = (instance.usage_api, instance.license) + if key not in grouped_instances: + grouped_instances[key] = [] + grouped_instances[key].append(instance.id) + + # Create new license_server for each group and associate with instances + for (usage_api, license_key), instance_ids in grouped_instances.items(): + license_server_id = session.execute( + sa.text( + "INSERT INTO t4c_license_servers (name, usage_api, license_key) VALUES (:name, :usage_api, :license_key) RETURNING id" + ), + { + "name": usage_api, + "usage_api": usage_api, + "license_key": license_key, + }, + ).scalar() + + session.execute( + sa.text( + "UPDATE t4c_instances SET license_server_id = :license_server_id WHERE id = ANY(:instance_ids)" + ), + { + "license_server_id": license_server_id, + "instance_ids": instance_ids, + }, + ) + + session.commit() + + op.alter_column("t4c_instances", "license_server_id", nullable=False) + op.drop_column("t4c_instances", "license") + op.drop_column("t4c_instances", "usage_api") + # ### end Alembic commands ### + + +def downgrade(): + # TODO implement this + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "t4c_instances", sa.Column("usage_api", sa.String(), nullable=False) + ) + op.add_column( + "t4c_instances", sa.Column("license", sa.String(), nullable=False) + ) + op.drop_constraint(None, "t4c_instances", type_="foreignkey") + op.drop_column("t4c_instances", "license_server_id") + op.drop_index( + op.f("ix_t4c_license_servers_id"), table_name="t4c_license_servers" + ) + op.drop_table("t4c_license_servers") + # ### end Alembic commands ### diff --git a/backend/capellacollab/core/database/migration.py b/backend/capellacollab/core/database/migration.py index 9af7cb1ed4..d617a84647 100644 --- a/backend/capellacollab/core/database/migration.py +++ b/backend/capellacollab/core/database/migration.py @@ -28,13 +28,17 @@ from capellacollab.settings.modelsources.git import ( models as modelssources_models, ) -from capellacollab.settings.modelsources.t4c import crud as settings_t4c_crud -from capellacollab.settings.modelsources.t4c import ( - models as settings_t4c_models, +from capellacollab.settings.modelsources.t4c.instance import ( + crud as t4c_instance_crud, + models as t4c_instance_models, ) -from capellacollab.settings.modelsources.t4c.repositories import ( +from capellacollab.settings.modelsources.t4c.instance.repositories import ( crud as repositories_crud, ) +from capellacollab.settings.modelsources.t4c.license_server import ( + crud as t4c_license_server_crud, + models as t4c_license_server_models, +) from capellacollab.tools import crud as tools_crud from capellacollab.tools import models as tools_models from capellacollab.users import crud as users_crud @@ -352,20 +356,31 @@ def create_t4c_instance_and_repositories(db): db, tool.id, "5.2.0" ) assert version - default_instance = settings_t4c_models.DatabaseT4CInstance( + + default_license_server = ( + t4c_license_server_models.DatabaseT4CLicenseServer( + name="default", + usage_api="http://localhost:8086", + license_key="placeholder", + ) + ) + t4c_license_server_crud.create_t4c_license_server( + db, default_license_server + ) + + default_instance = t4c_instance_models.DatabaseT4CInstance( name="default", - license="placeholder", - protocol=settings_t4c_models.Protocol.tcp, + protocol=t4c_instance_models.Protocol.tcp, host="localhost", port=2036, cdo_port=12036, - usage_api="http://localhost:8086", + license_server=default_license_server, rest_api="http://localhost:8081/api/v1.0", username="admin", password="password", version=version, ) - settings_t4c_crud.create_t4c_instance(db, default_instance) + t4c_instance_crud.create_t4c_instance(db, default_instance) for t4c_model in t4c_crud.get_t4c_models(db): t4c_model.repository = repositories_crud.create_t4c_repository( db=db, repo_name=t4c_model.name, instance=default_instance diff --git a/backend/capellacollab/core/database/models.py b/backend/capellacollab/core/database/models.py index 1fb546e18b..2728d1c6b3 100644 --- a/backend/capellacollab/core/database/models.py +++ b/backend/capellacollab/core/database/models.py @@ -19,7 +19,8 @@ import capellacollab.settings.configuration.models import capellacollab.settings.integrations.purevariants.models import capellacollab.settings.modelsources.git.models -import capellacollab.settings.modelsources.t4c.models +import capellacollab.settings.modelsources.t4c.license_server.models +import capellacollab.settings.modelsources.t4c.instance.models import capellacollab.tools.models import capellacollab.users.models import capellacollab.users.tokens.models diff --git a/backend/capellacollab/projects/toolmodels/backups/core.py b/backend/capellacollab/projects/toolmodels/backups/core.py index d201ecea77..e60221751c 100644 --- a/backend/capellacollab/projects/toolmodels/backups/core.py +++ b/backend/capellacollab/projects/toolmodels/backups/core.py @@ -8,7 +8,7 @@ import requests from sqlalchemy import orm -import capellacollab.settings.modelsources.t4c.repositories.interface as t4c_repository_interface +import capellacollab.settings.modelsources.t4c.instance.repositories.interface as t4c_repository_interface from capellacollab.core.authentication import injectables as auth_injectables from capellacollab.projects.toolmodels import models as toolmodels_models from capellacollab.projects.toolmodels.modelsources.git import ( diff --git a/backend/capellacollab/projects/toolmodels/backups/routes.py b/backend/capellacollab/projects/toolmodels/backups/routes.py index 54f31e0461..3513e30abf 100644 --- a/backend/capellacollab/projects/toolmodels/backups/routes.py +++ b/backend/capellacollab/projects/toolmodels/backups/routes.py @@ -9,7 +9,7 @@ import requests from sqlalchemy import orm -import capellacollab.settings.modelsources.t4c.repositories.interface as t4c_repository_interface +import capellacollab.settings.modelsources.t4c.instance.repositories.interface as t4c_repository_interface from capellacollab.core import credentials, database from capellacollab.core.authentication import injectables as auth_injectables from capellacollab.projects.toolmodels import ( diff --git a/backend/capellacollab/projects/toolmodels/modelsources/t4c/crud.py b/backend/capellacollab/projects/toolmodels/modelsources/t4c/crud.py index bcb71115e5..b79a3c9059 100644 --- a/backend/capellacollab/projects/toolmodels/modelsources/t4c/crud.py +++ b/backend/capellacollab/projects/toolmodels/modelsources/t4c/crud.py @@ -8,7 +8,7 @@ from capellacollab.projects.toolmodels import models as toolmodels_models from capellacollab.projects.toolmodels.modelsources.t4c import models -from capellacollab.settings.modelsources.t4c.repositories import ( +from capellacollab.settings.modelsources.t4c.instance.repositories import ( models as repositories_models, ) diff --git a/backend/capellacollab/projects/toolmodels/modelsources/t4c/models.py b/backend/capellacollab/projects/toolmodels/modelsources/t4c/models.py index b1713da78d..619f21e593 100644 --- a/backend/capellacollab/projects/toolmodels/modelsources/t4c/models.py +++ b/backend/capellacollab/projects/toolmodels/modelsources/t4c/models.py @@ -11,13 +11,13 @@ from capellacollab.core import database from capellacollab.core import pydantic as core_pydantic -from capellacollab.settings.modelsources.t4c.repositories import ( +from capellacollab.settings.modelsources.t4c.instance.repositories import ( models as repositories_models, ) if t.TYPE_CHECKING: from capellacollab.projects.toolmodels.models import DatabaseToolModel - from capellacollab.settings.modelsources.t4c.repositories.models import ( + from capellacollab.settings.modelsources.t4c.instance.repositories.models import ( DatabaseT4CRepository, ) diff --git a/backend/capellacollab/projects/toolmodels/modelsources/t4c/routes.py b/backend/capellacollab/projects/toolmodels/modelsources/t4c/routes.py index 981f523fac..4950e4f24b 100644 --- a/backend/capellacollab/projects/toolmodels/modelsources/t4c/routes.py +++ b/backend/capellacollab/projects/toolmodels/modelsources/t4c/routes.py @@ -14,10 +14,10 @@ from capellacollab.projects.toolmodels import models as toolmodels_models from capellacollab.projects.toolmodels.backups import crud as backups_crud from capellacollab.projects.users import models as projects_users_models -from capellacollab.settings.modelsources.t4c import ( +from capellacollab.settings.modelsources.t4c.instance import ( injectables as settings_t4c_injectables, ) -from capellacollab.settings.modelsources.t4c.repositories import ( +from capellacollab.settings.modelsources.t4c.instance.repositories import ( injectables as settings_t4c_repositories_injectables, ) from capellacollab.users import models as users_models diff --git a/backend/capellacollab/projects/toolmodels/modelsources/t4c/util.py b/backend/capellacollab/projects/toolmodels/modelsources/t4c/util.py index d78af86c3a..e998030860 100644 --- a/backend/capellacollab/projects/toolmodels/modelsources/t4c/util.py +++ b/backend/capellacollab/projects/toolmodels/modelsources/t4c/util.py @@ -1,7 +1,7 @@ # SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -from capellacollab.settings.modelsources.t4c.repositories import ( +from capellacollab.settings.modelsources.t4c.instance.repositories import ( models as t4c_repository_models, ) from capellacollab.tools import models as tools_models diff --git a/backend/capellacollab/sessions/hooks/t4c.py b/backend/capellacollab/sessions/hooks/t4c.py index 6f51cb7f94..b91cab5b31 100644 --- a/backend/capellacollab/sessions/hooks/t4c.py +++ b/backend/capellacollab/sessions/hooks/t4c.py @@ -11,10 +11,8 @@ from capellacollab.core import credentials from capellacollab.core import models as core_models from capellacollab.core.authentication import injectables as auth_injectables -from capellacollab.settings.modelsources.t4c.repositories import ( +from capellacollab.settings.modelsources.t4c.instance.repositories import ( crud as repo_crud, -) -from capellacollab.settings.modelsources.t4c.repositories import ( interface as repo_interface, ) from capellacollab.tools import models as tools_models @@ -70,7 +68,9 @@ def configuration_hook( # type: ignore ) t4c_licence_secret = ( - t4c_repositories[0].instance.license if t4c_repositories else "" + t4c_repositories[0].instance.license_server.license_key + if t4c_repositories + else "" ) environment = T4CConfigEnvironment( diff --git a/backend/capellacollab/settings/modelsources/routes.py b/backend/capellacollab/settings/modelsources/routes.py index e14b6e8677..5189179947 100644 --- a/backend/capellacollab/settings/modelsources/routes.py +++ b/backend/capellacollab/settings/modelsources/routes.py @@ -17,8 +17,4 @@ prefix="/git", tags=["Settings - Modelsources - Git"], ) -router.include_router( - settings_t4c_routes.router, - prefix="/t4c", - tags=["Settings - Modelsources - T4C"], -) +router.include_router(settings_t4c_routes.router, prefix="/t4c") diff --git a/backend/capellacollab/settings/modelsources/t4c/repositories/__init__.py b/backend/capellacollab/settings/modelsources/t4c/instance/__init__.py similarity index 100% rename from backend/capellacollab/settings/modelsources/t4c/repositories/__init__.py rename to backend/capellacollab/settings/modelsources/t4c/instance/__init__.py diff --git a/backend/capellacollab/settings/modelsources/t4c/crud.py b/backend/capellacollab/settings/modelsources/t4c/instance/crud.py similarity index 100% rename from backend/capellacollab/settings/modelsources/t4c/crud.py rename to backend/capellacollab/settings/modelsources/t4c/instance/crud.py diff --git a/backend/capellacollab/settings/modelsources/t4c/instance/exceptions.py b/backend/capellacollab/settings/modelsources/t4c/instance/exceptions.py new file mode 100644 index 0000000000..e489bf9c51 --- /dev/null +++ b/backend/capellacollab/settings/modelsources/t4c/instance/exceptions.py @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from fastapi import status + +from capellacollab.core import exceptions as core_exceptions + + +class T4CInstanceIsArchivedError(core_exceptions.BaseError): + def __init__(self, t4c_instance_id: int): + self.t4c_instance_id = t4c_instance_id + super().__init__( + status_code=status.HTTP_400_BAD_REQUEST, + title="T4C instance is archived", + reason=f"The T4C instance identified by {t4c_instance_id} is archived, thus prohibiting the execution of the requested operation.", + err_code="T4C_INSTANCE_IS_ARCHIVED", + ) + + +class T4CInstanceNotFoundError(core_exceptions.BaseError): + def __init__(self, t4c_instance_id: int): + super().__init__( + status_code=status.HTTP_404_NOT_FOUND, + title="T4C server instance not found", + reason=f"The T4C instance with id {t4c_instance_id} was not found.", + err_code="T4C_INSTANCE_NOT_FOUND", + ) + + +class T4CInstanceWithNameAlreadyExistsError( + core_exceptions.ResourceAlreadyExistsError +): + def __init__(self): + super().__init__(resource_name="T4C Instance", identifier_name="name") diff --git a/backend/capellacollab/settings/modelsources/t4c/injectables.py b/backend/capellacollab/settings/modelsources/t4c/instance/injectables.py similarity index 88% rename from backend/capellacollab/settings/modelsources/t4c/injectables.py rename to backend/capellacollab/settings/modelsources/t4c/instance/injectables.py index 052a2397f2..fadc09bad2 100644 --- a/backend/capellacollab/settings/modelsources/t4c/injectables.py +++ b/backend/capellacollab/settings/modelsources/t4c/instance/injectables.py @@ -6,7 +6,11 @@ from capellacollab.core import database -from . import crud, exceptions, models +from capellacollab.settings.modelsources.t4c.instance import ( + crud, + exceptions, + models, +) def get_existing_instance( diff --git a/backend/capellacollab/settings/modelsources/t4c/models.py b/backend/capellacollab/settings/modelsources/t4c/instance/models.py similarity index 87% rename from backend/capellacollab/settings/modelsources/t4c/models.py rename to backend/capellacollab/settings/modelsources/t4c/instance/models.py index 478a8b2d91..e0e8a4bd89 100644 --- a/backend/capellacollab/settings/modelsources/t4c/models.py +++ b/backend/capellacollab/settings/modelsources/t4c/instance/models.py @@ -19,7 +19,12 @@ if t.TYPE_CHECKING: from capellacollab.tools.models import DatabaseVersion - from .repositories.models import DatabaseT4CRepository + from capellacollab.settings.modelsources.t4c.instance.repositories.models import ( + DatabaseT4CRepository, + ) + from capellacollab.settings.modelsources.t4c.license_server.models import ( + DatabaseT4CLicenseServer, + ) log = logging.getLogger(__name__) @@ -36,11 +41,6 @@ def validate_rest_api_url(value: str | None): return value -class GetSessionUsageResponse(core_pydantic.BaseModel): - free: int - total: int - - class Protocol(str, enum.Enum): tcp = "tcp" ssl = "ssl" @@ -57,10 +57,15 @@ class DatabaseT4CInstance(database.Base): name: orm.Mapped[str] = orm.mapped_column(unique=True) - license: orm.Mapped[str] + license_server_id: orm.Mapped[int] = orm.mapped_column( + sa.ForeignKey("t4c_license_servers.id"), init=False, nullable=False + ) + license_server: orm.Mapped[DatabaseT4CLicenseServer] = orm.relationship( + back_populates="instances" + ) + host: orm.Mapped[str] - usage_api: orm.Mapped[str] rest_api: orm.Mapped[str] username: orm.Mapped[str] password: orm.Mapped[str] @@ -99,12 +104,10 @@ def port_validator(value: int | None) -> int | None: class T4CInstanceBase(core_pydantic.BaseModel): - license: str host: str port: int cdo_port: int http_port: int | None = None - usage_api: str rest_api: str username: str protocol: Protocol @@ -119,12 +122,11 @@ class T4CInstanceBase(core_pydantic.BaseModel): class PatchT4CInstance(core_pydantic.BaseModel): name: str | None = None - license: str | None = None + license_server_id: int | None = None host: str | None = None port: int | None = None cdo_port: int | None = None http_port: int | None = None - usage_api: str | None = None rest_api: str | None = None username: str | None = None password: str | None = None @@ -142,6 +144,7 @@ class PatchT4CInstance(core_pydantic.BaseModel): class T4CInstanceComplete(T4CInstanceBase): name: str version_id: int + license_server_id: int class CreateT4CInstance(T4CInstanceComplete): diff --git a/backend/capellacollab/settings/modelsources/t4c/instance/repositories/__init__.py b/backend/capellacollab/settings/modelsources/t4c/instance/repositories/__init__.py new file mode 100644 index 0000000000..04412280d8 --- /dev/null +++ b/backend/capellacollab/settings/modelsources/t4c/instance/repositories/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 diff --git a/backend/capellacollab/settings/modelsources/t4c/repositories/crud.py b/backend/capellacollab/settings/modelsources/t4c/instance/repositories/crud.py similarity index 97% rename from backend/capellacollab/settings/modelsources/t4c/repositories/crud.py rename to backend/capellacollab/settings/modelsources/t4c/instance/repositories/crud.py index 5c603cce46..596991d1f4 100644 --- a/backend/capellacollab/settings/modelsources/t4c/repositories/crud.py +++ b/backend/capellacollab/settings/modelsources/t4c/instance/repositories/crud.py @@ -12,9 +12,7 @@ models as t4c_models, ) from capellacollab.projects.users import models as projects_users_models -from capellacollab.settings.modelsources.t4c import ( - models as settings_t4c_models, -) +from .. import models as settings_t4c_models from capellacollab.tools import crud as tools_crud from capellacollab.tools import models as tools_models from capellacollab.users import models as users_models diff --git a/backend/capellacollab/settings/modelsources/t4c/repositories/exceptions.py b/backend/capellacollab/settings/modelsources/t4c/instance/repositories/exceptions.py similarity index 100% rename from backend/capellacollab/settings/modelsources/t4c/repositories/exceptions.py rename to backend/capellacollab/settings/modelsources/t4c/instance/repositories/exceptions.py diff --git a/backend/capellacollab/settings/modelsources/t4c/repositories/injectables.py b/backend/capellacollab/settings/modelsources/t4c/instance/repositories/injectables.py similarity index 89% rename from backend/capellacollab/settings/modelsources/t4c/repositories/injectables.py rename to backend/capellacollab/settings/modelsources/t4c/instance/repositories/injectables.py index 421a43d91b..cbf46c351a 100644 --- a/backend/capellacollab/settings/modelsources/t4c/repositories/injectables.py +++ b/backend/capellacollab/settings/modelsources/t4c/instance/repositories/injectables.py @@ -5,10 +5,8 @@ from sqlalchemy import orm from capellacollab.core import database -from capellacollab.settings.modelsources.t4c import ( +from .. import ( injectables as settings_t4c_injectables, -) -from capellacollab.settings.modelsources.t4c import ( models as settings_t4c_models, ) diff --git a/backend/capellacollab/settings/modelsources/t4c/repositories/interface.py b/backend/capellacollab/settings/modelsources/t4c/instance/repositories/interface.py similarity index 97% rename from backend/capellacollab/settings/modelsources/t4c/repositories/interface.py rename to backend/capellacollab/settings/modelsources/t4c/instance/repositories/interface.py index e93074a62c..05a3d372b1 100644 --- a/backend/capellacollab/settings/modelsources/t4c/repositories/interface.py +++ b/backend/capellacollab/settings/modelsources/t4c/instance/repositories/interface.py @@ -8,7 +8,9 @@ from capellacollab.config import config from capellacollab.core import credentials -from capellacollab.settings.modelsources.t4c import models as t4c_models +from capellacollab.settings.modelsources.t4c.instance import ( + models as t4c_models, +) def list_repositories(instance: t4c_models.DatabaseT4CInstance): diff --git a/backend/capellacollab/settings/modelsources/t4c/repositories/models.py b/backend/capellacollab/settings/modelsources/t4c/instance/repositories/models.py similarity index 91% rename from backend/capellacollab/settings/modelsources/t4c/repositories/models.py rename to backend/capellacollab/settings/modelsources/t4c/instance/repositories/models.py index eb57f335d8..48e4468f36 100644 --- a/backend/capellacollab/settings/modelsources/t4c/repositories/models.py +++ b/backend/capellacollab/settings/modelsources/t4c/instance/repositories/models.py @@ -12,13 +12,15 @@ from capellacollab.core import database from capellacollab.core import pydantic as core_pydantic -from capellacollab.settings.modelsources.t4c import models as t4c_models +from capellacollab.settings.modelsources.t4c.instance import ( + models as t4c_models, +) if t.TYPE_CHECKING: from capellacollab.projects.toolmodels.modelsources.t4c.models import ( DatabaseT4CModel, ) - from capellacollab.settings.modelsources.t4c.models import ( + from capellacollab.settings.modelsources.t4c.instance.models import ( DatabaseT4CInstance, ) diff --git a/backend/capellacollab/settings/modelsources/t4c/repositories/routes.py b/backend/capellacollab/settings/modelsources/t4c/instance/repositories/routes.py similarity index 98% rename from backend/capellacollab/settings/modelsources/t4c/repositories/routes.py rename to backend/capellacollab/settings/modelsources/t4c/instance/repositories/routes.py index c92c8625de..964c8ef9fe 100644 --- a/backend/capellacollab/settings/modelsources/t4c/repositories/routes.py +++ b/backend/capellacollab/settings/modelsources/t4c/instance/repositories/routes.py @@ -11,10 +11,8 @@ from capellacollab.core import database from capellacollab.core import models as core_models -from capellacollab.settings.modelsources.t4c import ( +from .. import ( injectables as settings_t4c_injectables, -) -from capellacollab.settings.modelsources.t4c import ( models as settings_t4c_models, ) diff --git a/backend/capellacollab/settings/modelsources/t4c/instance/routes.py b/backend/capellacollab/settings/modelsources/t4c/instance/routes.py new file mode 100644 index 0000000000..f56ab9840f --- /dev/null +++ b/backend/capellacollab/settings/modelsources/t4c/instance/routes.py @@ -0,0 +1,131 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from collections import abc + +import fastapi +from sqlalchemy import orm + +from capellacollab.core import database +from capellacollab.core.authentication import injectables as auth_injectables +from capellacollab.settings.modelsources.t4c.instance import ( + crud, + exceptions, + injectables, + models, +) +from capellacollab.settings.modelsources.t4c.instance.repositories import ( + routes as settings_t4c_repositories_routes, +) +from capellacollab.settings.modelsources.t4c.license_server import ( + crud as t4c_license_server_crud, + exceptions as t4c_license_server_exceptions, +) +from capellacollab.tools import crud as tools_crud +from capellacollab.tools import exceptions as tools_exceptions +from capellacollab.users import models as users_models + +router = fastapi.APIRouter( + dependencies=[ + fastapi.Depends( + auth_injectables.RoleVerification( + required_role=users_models.Role.ADMIN + ) + ) + ], +) + + +@router.get("", response_model=list[models.T4CInstance]) +def get_t4c_instances( + db: orm.Session = fastapi.Depends(database.get_db), +) -> abc.Sequence[models.DatabaseT4CInstance]: + return crud.get_t4c_instances(db) + + +@router.get( + "/{t4c_instance_id}", + response_model=models.T4CInstance, +) +def get_t4c_instance( + instance: models.DatabaseT4CInstance = fastapi.Depends( + injectables.get_existing_instance + ), +) -> models.DatabaseT4CInstance: + return instance + + +@router.post( + "", + response_model=models.T4CInstance, +) +def create_t4c_instance( + body: models.CreateT4CInstance, + db: orm.Session = fastapi.Depends(database.get_db), +) -> models.DatabaseT4CInstance: + if crud.get_t4c_instance_by_name(db, body.name): + raise exceptions.T4CInstanceWithNameAlreadyExistsError() + + if not (version := tools_crud.get_version_by_id(db, body.version_id)): + raise tools_exceptions.ToolVersionNotFoundError(body.version_id) + if not ( + license_server := t4c_license_server_crud.get_t4c_license_server_by_id( + db, body.license_server_id + ) + ): + raise t4c_license_server_exceptions.T4CLicenseServerNotFoundError( + body.license_server_id + ) + + body_dump = body.model_dump() + del body_dump["version_id"] + del body_dump["license_server_id"] + + instance = models.DatabaseT4CInstance( + version=version, license_server=license_server, **body_dump + ) + instance.version = version + instance.license_server = license_server + return crud.create_t4c_instance(db, instance) + + +@router.patch( + "/{t4c_instance_id}", + response_model=models.T4CInstance, +) +def edit_t4c_instance( + body: models.PatchT4CInstance, + instance: models.DatabaseT4CInstance = fastapi.Depends( + injectables.get_existing_instance + ), + db: orm.Session = fastapi.Depends(database.get_db), +) -> models.DatabaseT4CInstance: + if instance.is_archived and (body.is_archived is None or body.is_archived): + raise exceptions.T4CInstanceIsArchivedError(instance.id) + if ( + body.name + and body.name != instance.name + and crud.get_t4c_instance_by_name(db, body.name) + ): + raise exceptions.T4CInstanceWithNameAlreadyExistsError() + + return crud.update_t4c_instance(db, instance, body) + + +@router.delete( + "/{t4c_instance_id}", + status_code=204, +) +def delete_t4c_instance( + instance: models.DatabaseT4CInstance = fastapi.Depends( + injectables.get_existing_instance + ), + db: orm.Session = fastapi.Depends(database.get_db), +): + crud.delete_t4c_instance(db, instance) + + +router.include_router( + settings_t4c_repositories_routes.router, + prefix="/{t4c_instance_id}/repositories", +) diff --git a/backend/capellacollab/settings/modelsources/t4c/interface.py b/backend/capellacollab/settings/modelsources/t4c/interface.py deleted file mode 100644 index 6ea7f7c86a..0000000000 --- a/backend/capellacollab/settings/modelsources/t4c/interface.py +++ /dev/null @@ -1,50 +0,0 @@ -# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors -# SPDX-License-Identifier: Apache-2.0 - -import requests -from requests import auth as requests_auth - -from capellacollab import config -from capellacollab.settings.modelsources.t4c import ( - models as settings_t4c_models, -) - -from . import exceptions, models - - -def get_t4c_status( - instance: settings_t4c_models.DatabaseT4CInstance, -) -> models.GetSessionUsageResponse: - try: - r = requests.get( - f"{instance.usage_api}/status/json", - auth=requests_auth.HTTPBasicAuth( - instance.username, instance.password - ), - timeout=config.config.requests.timeout, - ) - except requests.Timeout: - raise exceptions.LicenseServerTimeoutError() - except requests.ConnectionError: - raise exceptions.LicenseServerConnectionFailedError() - - # In older versions of the TeamForCapella license server, - # the API endpoint returns 404 on success - # -> We have to handle the error here manually - if r.status_code != 404 and not r.ok: - raise exceptions.LicenseServerInternalError() - - try: - cur_status = r.json()["status"] - - if cur_status.get("message", "") == "No last status available.": - raise exceptions.LicenseServerNoStatusError() - - if "used" in cur_status: - return models.GetSessionUsageResponse(**cur_status) - except KeyError: - raise exceptions.LicenseServerNoStatusInResponse() - except requests.JSONDecodeError: - raise exceptions.LicenseServerResponseDecodeError() - - raise exceptions.LicenseServerUnknownError() diff --git a/backend/capellacollab/settings/modelsources/t4c/license_server/__init__.py b/backend/capellacollab/settings/modelsources/t4c/license_server/__init__.py new file mode 100644 index 0000000000..04412280d8 --- /dev/null +++ b/backend/capellacollab/settings/modelsources/t4c/license_server/__init__.py @@ -0,0 +1,2 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 diff --git a/backend/capellacollab/settings/modelsources/t4c/license_server/crud.py b/backend/capellacollab/settings/modelsources/t4c/license_server/crud.py new file mode 100644 index 0000000000..a6da9e4f13 --- /dev/null +++ b/backend/capellacollab/settings/modelsources/t4c/license_server/crud.py @@ -0,0 +1,69 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from collections import abc + +import sqlalchemy as sa +from sqlalchemy import orm + +from capellacollab.core import database + +from . import models, exceptions + + +def get_t4c_license_servers( + db: orm.Session, +) -> abc.Sequence[models.DatabaseT4CLicenseServer]: + return ( + db.execute(sa.select(models.DatabaseT4CLicenseServer)).scalars().all() + ) + + +def get_t4c_license_server_by_id( + db: orm.Session, license_server_id: int +) -> models.DatabaseT4CLicenseServer | None: + return db.execute( + sa.select(models.DatabaseT4CLicenseServer).where( + models.DatabaseT4CLicenseServer.id == license_server_id + ) + ).scalar_one_or_none() + + +def get_t4c_license_server_by_name( + db: orm.Session, license_server_name: str +) -> models.DatabaseT4CLicenseServer | None: + return db.execute( + sa.select(models.DatabaseT4CLicenseServer).where( + models.DatabaseT4CLicenseServer.name == license_server_name + ) + ).scalar_one_or_none() + + +def create_t4c_license_server( + db: orm.Session, license_server: models.DatabaseT4CLicenseServer +) -> models.DatabaseT4CLicenseServer: + db.add(license_server) + db.commit() + return license_server + + +def update_t4c_license_server( + db: orm.Session, + license_server: models.DatabaseT4CLicenseServer, + patch_t4c_instance: models.PatchT4CLicenseServer, +): + database.patch_database_with_pydantic_object(license_server, patch_t4c_instance) + + db.commit() + + return license_server + + +def delete_t4c_license_server( + db: orm.Session, + license_server: models.DatabaseT4CLicenseServer, +): + if len(license_server.instances) > 0: + raise exceptions.T4CLicenseServerInUseError(license_server.id) + db.delete(license_server) + db.commit() diff --git a/backend/capellacollab/settings/modelsources/t4c/exceptions.py b/backend/capellacollab/settings/modelsources/t4c/license_server/exceptions.py similarity index 67% rename from backend/capellacollab/settings/modelsources/t4c/exceptions.py rename to backend/capellacollab/settings/modelsources/t4c/license_server/exceptions.py index 3915ea6a3f..3cabc1bdd8 100644 --- a/backend/capellacollab/settings/modelsources/t4c/exceptions.py +++ b/backend/capellacollab/settings/modelsources/t4c/license_server/exceptions.py @@ -6,35 +6,7 @@ from capellacollab.core import exceptions as core_exceptions -class T4CInstanceIsArchivedError(core_exceptions.BaseError): - def __init__(self, t4c_instance_id: int): - self.t4c_instance_id = t4c_instance_id - super().__init__( - status_code=status.HTTP_400_BAD_REQUEST, - title="T4C instance is archived", - reason=f"The T4C instance identified by {t4c_instance_id} is archived, thus prohibiting the execution of the requested operation.", - err_code="T4C_INSTANCE_IS_ARCHIVED", - ) - - -class T4CInstanceNotFoundError(core_exceptions.BaseError): - def __init__(self, t4c_instance_id: int): - super().__init__( - status_code=status.HTTP_404_NOT_FOUND, - title="T4C server instance not found", - reason=f"The T4C instance with id {t4c_instance_id} was not found.", - err_code="T4C_INSTANCE_NOT_FOUND", - ) - - -class T4CInstanceWithNameAlreadyExistsError( - core_exceptions.ResourceAlreadyExistsError -): - def __init__(self): - super().__init__(resource_name="T4C Instance", identifier_name="name") - - -class LicenseServerTimeoutError(core_exceptions.BaseError): +class T4CLicenseServerTimeoutError(core_exceptions.BaseError): def __init__(self): super().__init__( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -44,7 +16,7 @@ def __init__(self): ) -class LicenseServerConnectionFailedError(core_exceptions.BaseError): +class T4CLicenseServerConnectionFailedError(core_exceptions.BaseError): def __init__(self): super().__init__( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -54,7 +26,7 @@ def __init__(self): ) -class LicenseServerInternalError(core_exceptions.BaseError): +class T4CLicenseServerInternalError(core_exceptions.BaseError): def __init__(self): super().__init__( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -64,7 +36,7 @@ def __init__(self): ) -class LicenseServerNoStatusError(core_exceptions.BaseError): +class T4CLicenseServerNoStatusError(core_exceptions.BaseError): def __init__(self): super().__init__( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -77,7 +49,7 @@ def __init__(self): ) -class LicenseServerNoStatusInResponse(core_exceptions.BaseError): +class T4CLicenseServerNoStatusInResponse(core_exceptions.BaseError): def __init__(self): super().__init__( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -87,7 +59,7 @@ def __init__(self): ) -class LicenseServerResponseDecodeError(core_exceptions.BaseError): +class T4CLicenseServerResponseDecodeError(core_exceptions.BaseError): def __init__(self): super().__init__( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, @@ -97,7 +69,7 @@ def __init__(self): ) -class LicenseServerUnknownError(core_exceptions.BaseError): +class T4CLicenseServerUnknownError(core_exceptions.BaseError): def __init__(self): super().__init__( status_code=status.HTTP_502_BAD_GATEWAY, @@ -105,3 +77,31 @@ def __init__(self): reason="An unknown error occurred when communicating with the license server.", err_code="T4C_LICENSE_SERVER_UNKNOWN_ERROR", ) + + +class T4CLicenseServerWithNameAlreadyExistsError( + core_exceptions.ResourceAlreadyExistsError +): + def __init__(self): + super().__init__( + resource_name="T4C License Server", identifier_name="name" + ) + + +class T4CLicenseServerNotFoundError(core_exceptions.BaseError): + def __init__(self, t4c_license_server_id: int): + super().__init__( + status_code=status.HTTP_404_NOT_FOUND, + title="T4C license server not found", + reason=f"The T4C license server with id {t4c_license_server_id} was not found.", + err_code="T4C_LICENSE_SERVER_NOT_FOUND", + ) + +class T4CLicenseServerInUseError(core_exceptions.BaseError): + def __init__(self, t4c_license_server_id: int): + super().__init__( + status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, + title="T4C license server in use", + reason=f"The T4C license server with id {t4c_license_server_id} is in use.", + err_code="T4C_LICENSE_SERVER_IN_USE", + ) \ No newline at end of file diff --git a/backend/capellacollab/settings/modelsources/t4c/license_server/injectables.py b/backend/capellacollab/settings/modelsources/t4c/license_server/injectables.py new file mode 100644 index 0000000000..1911efd157 --- /dev/null +++ b/backend/capellacollab/settings/modelsources/t4c/license_server/injectables.py @@ -0,0 +1,21 @@ +# 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 . import crud, exceptions, models + + +def get_existing_license_server( + t4c_license_server_id: int, + db: orm.Session = fastapi.Depends(database.get_db), +) -> models.DatabaseT4CLicenseServer: + if license_server := crud.get_t4c_license_server_by_id( + db, t4c_license_server_id + ): + return license_server + + raise exceptions.T4CLicenseServerNotFoundError(t4c_license_server_id) diff --git a/backend/capellacollab/settings/modelsources/t4c/license_server/interface.py b/backend/capellacollab/settings/modelsources/t4c/license_server/interface.py new file mode 100644 index 0000000000..bbb6412c00 --- /dev/null +++ b/backend/capellacollab/settings/modelsources/t4c/license_server/interface.py @@ -0,0 +1,66 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +import requests +from capellacollab.core import pydantic as core_pydantic + +from capellacollab import config + + +from . import exceptions + + +class T4CLicenseServerUsage(core_pydantic.BaseModel): + free: int + total: int + + +def get_t4c_license_server_version(usage_api: str) -> str | None: + try: + r = requests.get( + f"{usage_api}/status/json", + timeout=config.config.requests.timeout, + ) + except requests.Timeout: + return None + except requests.ConnectionError: + return None + if r.status_code != 404 and not r.ok: + return None + + data = r.json() + license_server_version = data.get("version", None) + return license_server_version + + +def get_t4c_status(usage_api: str) -> T4CLicenseServerUsage: + try: + r = requests.get( + f"{usage_api}/status/json", + timeout=config.config.requests.timeout, + ) + except requests.Timeout: + raise exceptions.T4CLicenseServerTimeoutError() + except requests.ConnectionError: + raise exceptions.T4CLicenseServerConnectionFailedError() + + # In older versions of the TeamForCapella license server, + # the API endpoint returns 404 on success + # -> We have to handle the error here manually + if r.status_code != 404 and not r.ok: + raise exceptions.T4CLicenseServerInternalError() + + try: + cur_status = r.json()["status"] + + if cur_status.get("message", "") == "No last status available.": + raise exceptions.T4CLicenseServerNoStatusError() + + if "used" in cur_status: + return T4CLicenseServerUsage(**cur_status) + except KeyError: + raise exceptions.T4CLicenseServerNoStatusInResponse() + except requests.JSONDecodeError: + raise exceptions.T4CLicenseServerResponseDecodeError() + + raise exceptions.T4CLicenseServerUnknownError() diff --git a/backend/capellacollab/settings/modelsources/t4c/metrics.py b/backend/capellacollab/settings/modelsources/t4c/license_server/metrics.py similarity index 65% rename from backend/capellacollab/settings/modelsources/t4c/metrics.py rename to backend/capellacollab/settings/modelsources/t4c/license_server/metrics.py index f6aeb1ee58..aeb88c5917 100644 --- a/backend/capellacollab/settings/modelsources/t4c/metrics.py +++ b/backend/capellacollab/settings/modelsources/t4c/license_server/metrics.py @@ -8,7 +8,6 @@ from prometheus_client import registry as prometheus_registry from capellacollab.core import database - from . import crud, interface @@ -16,24 +15,24 @@ class UsedT4CLicensesCollector(prometheus_registry.Collector): def collect(self) -> t.Iterable[prometheus_client.core.Metric]: metric = prometheus_client.core.GaugeMetricFamily( "used_t4c_licenses", - "Currently used T4C licenses per registered TeamForCapella instance.", - labels=["instance_name"], + "Currently used T4C licenses per registered TeamForCapella license server.", + labels=["license_server_name"], ) with database.SessionLocal() as db: - instances = crud.get_t4c_instances(db) + license_servers = crud.get_t4c_license_servers(db) - if not instances: + if not license_servers: return - for instance in instances: + for license_server in license_servers: try: - t4c_status = interface.get_t4c_status(instance) + t4c_status = interface.get_t4c_status(license_server.usage_api) used_licenses = t4c_status.total - t4c_status.free except Exception: used_licenses = -1 - metric.add_metric([instance.name], used_licenses) + metric.add_metric([license_server.name], used_licenses) yield metric @@ -41,24 +40,24 @@ class TotalT4CLicensesCollector(prometheus_registry.Collector): def collect(self) -> t.Iterable[prometheus_client.core.Metric]: metric = prometheus_client.core.GaugeMetricFamily( "total_t4c_licenses", - "Available licenses per registerd TeamForCapella instance.", - labels=["instance_name"], + "Available licenses per registerd TeamForCapella license server.", + labels=["license_server_name"], ) with database.SessionLocal() as db: - instances = crud.get_t4c_instances(db) + license_servers = crud.get_t4c_license_servers(db) - if not instances: + if not license_servers: return - for instance in instances: + for license_server in license_servers: try: - t4c_status = interface.get_t4c_status(instance) + t4c_status = interface.get_t4c_status(license_server.usage_api) total_licenses = t4c_status.total except Exception: total_licenses = -1 - metric.add_metric([instance.name], total_licenses) + metric.add_metric([license_server.name], total_licenses) yield metric diff --git a/backend/capellacollab/settings/modelsources/t4c/license_server/models.py b/backend/capellacollab/settings/modelsources/t4c/license_server/models.py new file mode 100644 index 0000000000..83232d1b11 --- /dev/null +++ b/backend/capellacollab/settings/modelsources/t4c/license_server/models.py @@ -0,0 +1,93 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from __future__ import annotations + +import typing as t + +import pydantic +from sqlalchemy import orm + +from capellacollab.core import database +from capellacollab.core import pydantic as core_pydantic +from . import interface + +if t.TYPE_CHECKING: + from capellacollab.settings.modelsources.t4c.instance.models import ( + DatabaseT4CInstance, + ) + +from capellacollab.settings.modelsources.t4c.instance import ( + models as instance_models, +) + + +def validate_usage_api_url(value: str | None): + if value: + version = interface.get_t4c_license_server_version(value) + if version is None: + raise ValueError("Invalid usage API") + + return value + + +class DatabaseT4CLicenseServer(database.Base): + __tablename__ = "t4c_license_servers" + + id: orm.Mapped[int] = orm.mapped_column( + init=False, + primary_key=True, + index=True, + autoincrement=True, + unique=True, + ) + + name: orm.Mapped[str] = orm.mapped_column(unique=True) + usage_api: orm.Mapped[str] = orm.mapped_column() + license_key: orm.Mapped[str] = orm.mapped_column() + instances: orm.Mapped[list[DatabaseT4CInstance]] = orm.relationship( + default_factory=list, back_populates="license_server" + ) + + +class T4CLicenseServerBase(core_pydantic.BaseModel): + name: str + usage_api: str + license_key: str + _validate_usage_api = pydantic.field_validator("usage_api")( + validate_usage_api_url + ) + + +class PatchT4CLicenseServer(core_pydantic.BaseModel): + name: str | None = None + usage_api: str | None = None + license_key: str | None = None + _validate_usage_api = pydantic.field_validator("usage_api")( + validate_usage_api_url + ) + + +class T4CLicenseServer(T4CLicenseServerBase): + id: int + license_server_version: str | None = None + usage: interface.T4CLicenseServerUsage | None = None + instances: list[instance_models.T4CInstance] = [] + + @pydantic.model_validator(mode="after") + def add_from_api(self) -> t.Any: + self.license_server_version = interface.get_t4c_license_server_version( + self.usage_api + ) + try: + self.usage = interface.get_t4c_status(self.usage_api) + except: # TODO this is evil + pass + + return self + + +# TODO get rid of this +class GetSessionUsageResponse(core_pydantic.BaseModel): + free: int + total: int diff --git a/backend/capellacollab/settings/modelsources/t4c/license_server/routes.py b/backend/capellacollab/settings/modelsources/t4c/license_server/routes.py new file mode 100644 index 0000000000..c7ea5227ec --- /dev/null +++ b/backend/capellacollab/settings/modelsources/t4c/license_server/routes.py @@ -0,0 +1,111 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +from collections import abc + +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 crud, exceptions, injectables, models, interface + +router = fastapi.APIRouter( + dependencies=[ + fastapi.Depends( + auth_injectables.RoleVerification( + required_role=users_models.Role.ADMIN + ) + ) + ], +) + + +@router.get("", response_model=list[models.T4CLicenseServer]) +def get_t4c_license_servers( + db: orm.Session = fastapi.Depends(database.get_db), +) -> abc.Sequence[models.DatabaseT4CLicenseServer]: + return crud.get_t4c_license_servers(db) + + +@router.get( + "/{t4c_license_server_id}", + response_model=models.T4CLicenseServer, +) +def get_t4c_license_server( + license_server: models.DatabaseT4CLicenseServer = fastapi.Depends( + injectables.get_existing_license_server + ), +) -> models.DatabaseT4CLicenseServer: + return license_server + + +@router.post( + "", + response_model=models.T4CLicenseServer, +) +def create_t4c_license_server( + body: models.T4CLicenseServerBase, + db: orm.Session = fastapi.Depends(database.get_db), +) -> models.DatabaseT4CLicenseServer: + if crud.get_t4c_license_server_by_name(db, body.name): + raise exceptions.T4CLicenseServerWithNameAlreadyExistsError() + + body_dump = body.model_dump() # TODO I don't think this is necessary + + license_server = models.DatabaseT4CLicenseServer(**body_dump) + return crud.create_t4c_license_server(db, license_server) + + +@router.patch( + "/{t4c_license_server_id}", + response_model=models.T4CLicenseServer, +) +def edit_t4c_license_server( + body: models.PatchT4CLicenseServer, + license_server: models.DatabaseT4CLicenseServer = fastapi.Depends( + injectables.get_existing_license_server + ), + db: orm.Session = fastapi.Depends(database.get_db), +) -> models.DatabaseT4CLicenseServer: + if ( + body.name + and body.name != license_server.name + and crud.get_t4c_license_server_by_name(db, body.name) + ): + raise exceptions.T4CLicenseServerWithNameAlreadyExistsError() + + return crud.update_t4c_license_server(db, license_server, body) + + +@router.delete( + "/{t4c_license_server_id}", + status_code=204, +) +def delete_t4c_license_server( + license_server: models.DatabaseT4CLicenseServer = fastapi.Depends( + injectables.get_existing_license_server + ), + db: orm.Session = fastapi.Depends(database.get_db), +): + crud.delete_t4c_license_server(db, license_server) + + +@router.get( + "/{t4c_license_server_id}/licenses", + response_model=models.GetSessionUsageResponse, + dependencies=[ + fastapi.Depends( + auth_injectables.RoleVerification( + required_role=users_models.Role.ADMIN + ) + ) + ], +) +def fetch_t4c_license_server_licenses( + instance: models.DatabaseT4CLicenseServer = fastapi.Depends( + injectables.get_existing_license_server + ), +) -> models.GetSessionUsageResponse: + return interface.get_t4c_status(instance) diff --git a/backend/capellacollab/settings/modelsources/t4c/routes.py b/backend/capellacollab/settings/modelsources/t4c/routes.py index dd0c1ec17e..d3cd8f108b 100644 --- a/backend/capellacollab/settings/modelsources/t4c/routes.py +++ b/backend/capellacollab/settings/modelsources/t4c/routes.py @@ -1,129 +1,25 @@ # SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors # SPDX-License-Identifier: Apache-2.0 -from collections import abc - import fastapi -from sqlalchemy import orm - -from capellacollab.core import database -from capellacollab.core.authentication import injectables as auth_injectables -from capellacollab.settings.modelsources.t4c.repositories import ( - routes as settings_t4c_repositories_routes, -) -from capellacollab.tools import crud as tools_crud -from capellacollab.tools import exceptions as tools_exceptions -from capellacollab.users import models as users_models - -from . import crud, exceptions, injectables, interface, models - -router = fastapi.APIRouter( - dependencies=[ - fastapi.Depends( - auth_injectables.RoleVerification( - required_role=users_models.Role.ADMIN - ) - ) - ], -) - - -@router.get("", response_model=list[models.T4CInstance]) -def get_t4c_instances( - db: orm.Session = fastapi.Depends(database.get_db), -) -> abc.Sequence[models.DatabaseT4CInstance]: - return crud.get_t4c_instances(db) - - -@router.get( - "/{t4c_instance_id}", - response_model=models.T4CInstance, -) -def get_t4c_instance( - instance: models.DatabaseT4CInstance = fastapi.Depends( - injectables.get_existing_instance - ), -) -> models.DatabaseT4CInstance: - return instance - -@router.post( - "", - response_model=models.T4CInstance, +from capellacollab.settings.modelsources.t4c.instance import ( + routes as settings_t4c_instance_routes, ) -def create_t4c_instance( - body: models.CreateT4CInstance, - db: orm.Session = fastapi.Depends(database.get_db), -) -> models.DatabaseT4CInstance: - if crud.get_t4c_instance_by_name(db, body.name): - raise exceptions.T4CInstanceWithNameAlreadyExistsError() - - if not (version := tools_crud.get_version_by_id(db, body.version_id)): - raise tools_exceptions.ToolVersionNotFoundError(body.version_id) - body_dump = body.model_dump() - del body_dump["version_id"] - - instance = models.DatabaseT4CInstance(version=version, **body_dump) - instance.version = version - return crud.create_t4c_instance(db, instance) - - -@router.patch( - "/{t4c_instance_id}", - response_model=models.T4CInstance, +from capellacollab.settings.modelsources.t4c.license_server import ( + routes as settings_t4c_license_server_routes, ) -def edit_t4c_instance( - body: models.PatchT4CInstance, - instance: models.DatabaseT4CInstance = fastapi.Depends( - injectables.get_existing_instance - ), - db: orm.Session = fastapi.Depends(database.get_db), -) -> models.DatabaseT4CInstance: - if instance.is_archived and (body.is_archived is None or body.is_archived): - raise exceptions.T4CInstanceIsArchivedError(instance.id) - if ( - body.name - and body.name != instance.name - and crud.get_t4c_instance_by_name(db, body.name) - ): - raise exceptions.T4CInstanceWithNameAlreadyExistsError() - - return crud.update_t4c_instance(db, instance, body) +router = fastapi.APIRouter() -@router.delete( - "/{t4c_instance_id}", - status_code=204, -) -def delete_t4c_instance( - instance: models.DatabaseT4CInstance = fastapi.Depends( - injectables.get_existing_instance - ), - db: orm.Session = fastapi.Depends(database.get_db), -): - crud.delete_t4c_instance(db, instance) - - -@router.get( - "/{t4c_instance_id}/licenses", - response_model=models.GetSessionUsageResponse, - dependencies=[ - fastapi.Depends( - auth_injectables.RoleVerification( - required_role=users_models.Role.ADMIN - ) - ) - ], +router.include_router( + settings_t4c_instance_routes.router, + prefix="/instances", + tags=["Settings - Modelsources - T4C - Instances"], ) -def fetch_t4c_licenses( - instance: models.DatabaseT4CInstance = fastapi.Depends( - injectables.get_existing_instance - ), -) -> models.GetSessionUsageResponse: - return interface.get_t4c_status(instance) - router.include_router( - settings_t4c_repositories_routes.router, - prefix="/{t4c_instance_id}/repositories", + settings_t4c_license_server_routes.router, + prefix="/license-servers", + tags=["Settings - Modelsources - T4C - License Servers"], ) diff --git a/backend/capellacollab/tools/routes.py b/backend/capellacollab/tools/routes.py index 5b240cf15a..79f769c39e 100644 --- a/backend/capellacollab/tools/routes.py +++ b/backend/capellacollab/tools/routes.py @@ -7,7 +7,7 @@ from sqlalchemy import orm import capellacollab.projects.toolmodels.crud as projects_models_crud -import capellacollab.settings.modelsources.t4c.crud as settings_t4c_crud +import capellacollab.settings.modelsources.t4c.instance.crud as settings_t4c_crud from capellacollab.core import database from capellacollab.core import exceptions as core_exceptions from capellacollab.core.authentication import injectables as auth_injectables diff --git a/backend/tests/projects/toolmodels/conftest.py b/backend/tests/projects/toolmodels/conftest.py index 14f8433e6f..aea8671614 100644 --- a/backend/tests/projects/toolmodels/conftest.py +++ b/backend/tests/projects/toolmodels/conftest.py @@ -11,8 +11,8 @@ import capellacollab.settings.modelsources.git.crud as git_crud import capellacollab.settings.modelsources.git.models as git_models -import capellacollab.settings.modelsources.t4c.models as t4c_models -import capellacollab.settings.modelsources.t4c.repositories.interface as t4c_repositories_interface +import capellacollab.settings.modelsources.t4c.instance.models as t4c_models +import capellacollab.settings.modelsources.t4c.instance.repositories.interface as t4c_repositories_interface from capellacollab.core import credentials diff --git a/backend/tests/projects/toolmodels/pipelines/test_pipelines.py b/backend/tests/projects/toolmodels/pipelines/test_pipelines.py index 92d2fffc64..0c2d24a852 100644 --- a/backend/tests/projects/toolmodels/pipelines/test_pipelines.py +++ b/backend/tests/projects/toolmodels/pipelines/test_pipelines.py @@ -7,7 +7,6 @@ import pytest import requests.exceptions -import sqlalchemy.exc from fastapi import testclient from sqlalchemy import orm @@ -18,8 +17,8 @@ import capellacollab.projects.toolmodels.modelsources.git.models as git_models import capellacollab.projects.toolmodels.modelsources.t4c.models as models_t4c_models import capellacollab.sessions.operators -import capellacollab.settings.modelsources.t4c.models as t4c_models -import capellacollab.settings.modelsources.t4c.repositories.interface as t4c_repositories_interface +import capellacollab.settings.modelsources.t4c.instance.models as t4c_models +import capellacollab.settings.modelsources.t4c.instance.repositories.interface as t4c_repositories_interface from capellacollab.core import credentials diff --git a/backend/tests/sessions/hooks/test_t4c_hook.py b/backend/tests/sessions/hooks/test_t4c_hook.py index 8b2ab4d82a..e058e96ae2 100644 --- a/backend/tests/sessions/hooks/test_t4c_hook.py +++ b/backend/tests/sessions/hooks/test_t4c_hook.py @@ -7,7 +7,7 @@ import responses from sqlalchemy import orm -import capellacollab.settings.modelsources.t4c.repositories.models as settings_t4c_repositories_models +import capellacollab.settings.modelsources.t4c.instance.repositories.models as settings_t4c_repositories_models from capellacollab.projects import models as projects_models from capellacollab.projects.toolmodels import crud as toolmodels_crud from capellacollab.projects.toolmodels import models as toolmodels_models @@ -18,7 +18,9 @@ from capellacollab.sessions import models as sessions_models from capellacollab.sessions.hooks import interface as sessions_hooks_interface from capellacollab.sessions.hooks import t4c -from capellacollab.settings.modelsources.t4c import models as t4c_models +from capellacollab.settings.modelsources.t4c.instance import ( + models as t4c_models, +) from capellacollab.tools import crud as tools_crud from capellacollab.tools import models as tools_models from capellacollab.users import crud as users_crud diff --git a/backend/tests/settings/teamforcapella/conftest.py b/backend/tests/settings/teamforcapella/conftest.py index 423bda78c3..1114a1b05c 100644 --- a/backend/tests/settings/teamforcapella/conftest.py +++ b/backend/tests/settings/teamforcapella/conftest.py @@ -12,6 +12,9 @@ def fixture_mock_license_server(): rsps.get( "http://localhost:8086/status/json", status=status.HTTP_200_OK, - json={"status": {"used": 1, "free": 19, "total": 20}}, + json={ + "status": {"used": 1, "free": 19, "total": 20}, + "version": "1.0.0", + }, ) yield rsps diff --git a/backend/tests/settings/teamforcapella/fixtures.py b/backend/tests/settings/teamforcapella/fixtures.py index 4033c48b78..a5eac614b2 100644 --- a/backend/tests/settings/teamforcapella/fixtures.py +++ b/backend/tests/settings/teamforcapella/fixtures.py @@ -4,36 +4,57 @@ import pytest from sqlalchemy import orm -import capellacollab.settings.modelsources.t4c.repositories.crud as t4c_repositories_crud -import capellacollab.settings.modelsources.t4c.repositories.models as t4c_repositories_models -from capellacollab.settings.modelsources.t4c import crud as t4c_crud -from capellacollab.settings.modelsources.t4c import models as t4c_models +import capellacollab.settings.modelsources.t4c.instance.repositories.crud as t4c_repositories_crud +import capellacollab.settings.modelsources.t4c.instance.repositories.models as t4c_repositories_models +from capellacollab.settings.modelsources.t4c.instance import ( + crud as t4c_instance_crud, + models as t4c_instance_models, +) +from capellacollab.settings.modelsources.t4c.license_server import ( + models as t4c_license_server_models, + crud as t4c_license_server_crud, +) from capellacollab.tools import models as tools_models +@pytest.fixture(name="t4c_license_server") +def fixture_t4c_license_server( + db: orm.Session, +) -> t4c_license_server_models.DatabaseT4CLicenseServer: + license_server = t4c_license_server_models.DatabaseT4CLicenseServer( + name="test license server", + usage_api="http://localhost:8086", + license_key="test key", + ) + + return t4c_license_server_crud.create_t4c_license_server( + db, license_server + ) + + @pytest.fixture(name="t4c_instance") def fixture_t4c_instance( db: orm.Session, capella_tool_version: tools_models.DatabaseVersion, -) -> t4c_models.DatabaseT4CInstance: - server = t4c_models.DatabaseT4CInstance( + t4c_license_server: t4c_license_server_models.DatabaseT4CLicenseServer, +) -> t4c_instance_models.DatabaseT4CInstance: + server = t4c_instance_models.DatabaseT4CInstance( name="test server", - license="lic", host="localhost", - usage_api="http://localhost:8086", + license_server=t4c_license_server, rest_api="http://localhost:8080/api/v1.0", username="user", password="pass", - protocol=t4c_models.Protocol.tcp, + protocol=t4c_instance_models.Protocol.tcp, version=capella_tool_version, ) - return t4c_crud.create_t4c_instance(db, server) + return t4c_instance_crud.create_t4c_instance(db, server) @pytest.fixture(name="t4c_repository") def fixture_t4c_repository( - t4c_instance: t4c_models.DatabaseT4CInstance, + t4c_instance: t4c_instance_models.DatabaseT4CInstance, db: orm.Session, ) -> t4c_repositories_models.DatabaseT4CRepository: return t4c_repositories_crud.create_t4c_repository( diff --git a/backend/tests/settings/teamforcapella/test_t4c_instances.py b/backend/tests/settings/teamforcapella/test_t4c_instances.py index b26d8fe736..520de2ffba 100644 --- a/backend/tests/settings/teamforcapella/test_t4c_instances.py +++ b/backend/tests/settings/teamforcapella/test_t4c_instances.py @@ -2,8 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 import pytest -import responses -from fastapi import status, testclient +from fastapi import testclient from sqlalchemy import orm from capellacollab.projects.toolmodels.modelsources.t4c import ( @@ -12,14 +11,15 @@ from capellacollab.projects.toolmodels.modelsources.t4c import ( models as models_t4c_models, ) -from capellacollab.settings.modelsources.t4c import crud as t4c_crud -from capellacollab.settings.modelsources.t4c import ( - exceptions as settings_t4c_exceptions, +from capellacollab.settings.modelsources.t4c.instance import ( + crud as t4c_instance_crud, + exceptions as t4c_instance_exceptions, + injectables as t4c_instance_injectables, + models as t4c_instance_models, ) -from capellacollab.settings.modelsources.t4c import ( - injectables as settings_t4c_injectables, +from capellacollab.settings.modelsources.t4c.license_server import ( + models as t4c_license_server_models, ) -from capellacollab.settings.modelsources.t4c import models as t4c_models from capellacollab.tools import models as tools_models from capellacollab.users import crud as users_crud from capellacollab.users import models as users_models @@ -30,15 +30,15 @@ def test_create_t4c_instance( client: testclient.TestClient, db: orm.Session, tool_version: tools_models.DatabaseVersion, + t4c_license_server: t4c_license_server_models.DatabaseT4CLicenseServer, ): response = client.post( - "/api/v1/settings/modelsources/t4c", + "/api/v1/settings/modelsources/t4c/instances", json={ - "license": "test", "host": "test", "port": 2036, + "license_server_id": t4c_license_server.id, "cdo_port": 12036, - "usage_api": "http://localhost:8086", "rest_api": "http://localhost:8080", "username": "admin", "protocol": "tcp", @@ -51,7 +51,9 @@ def test_create_t4c_instance( assert response.status_code == 200 assert response.json()["name"] == "Test integration" - t4c_instance = t4c_crud.get_t4c_instance_by_id(db, response.json()["id"]) + t4c_instance = t4c_instance_crud.get_t4c_instance_by_id( + db, response.json()["id"] + ) assert t4c_instance assert t4c_instance.name == "Test integration" @@ -60,18 +62,18 @@ def test_create_t4c_instance( @pytest.mark.usefixtures("admin") def test_create_t4c_instance_already_existing_name( client: testclient.TestClient, - t4c_instance: t4c_models.DatabaseT4CInstance, + t4c_instance: t4c_instance_models.DatabaseT4CInstance, + t4c_license_server: t4c_license_server_models.DatabaseT4CLicenseServer, tool_version: tools_models.DatabaseVersion, ): response = client.post( - "/api/v1/settings/modelsources/t4c", + "/api/v1/settings/modelsources/t4c/instances", json={ "name": t4c_instance.name, - "license": "test", "host": "test", + "license_server_id": t4c_license_server.id, "port": 2036, "cdo_port": 12036, - "usage_api": "http://localhost:8086", "rest_api": "http://localhost:8080", "username": "admin", "protocol": "tcp", @@ -102,7 +104,7 @@ def test_get_t4c_instances( ) response = client.get( - "/api/v1/settings/modelsources/t4c", + "/api/v1/settings/modelsources/t4c/instances", ) assert len(response.json()) == 2 @@ -117,14 +119,14 @@ def test_get_t4c_instance( client: testclient.TestClient, db: orm.Session, executor_name: str, - t4c_instance: t4c_models.DatabaseT4CInstance, + t4c_instance: t4c_instance_models.DatabaseT4CInstance, ): users_crud.create_user( db, executor_name, executor_name, None, users_models.Role.ADMIN ) response = client.get( - f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}", + f"/api/v1/settings/modelsources/t4c/instances/{t4c_instance.id}", ) assert response.json()["name"] == "test server" @@ -137,20 +139,20 @@ def test_patch_t4c_instance( client: testclient.TestClient, db: orm.Session, executor_name: str, - t4c_instance: t4c_models.DatabaseT4CInstance, + t4c_instance: t4c_instance_models.DatabaseT4CInstance, ): users_crud.create_user( db, executor_name, executor_name, None, users_models.Role.ADMIN ) response = client.patch( - f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}", + f"/api/v1/settings/modelsources/t4c/instances/{t4c_instance.id}", json={ "name": "Patched test integration", }, ) - updated_t4c_instance = t4c_crud.get_t4c_instance_by_id( + updated_t4c_instance = t4c_instance_crud.get_t4c_instance_by_id( db, response.json()["id"] ) assert updated_t4c_instance @@ -168,18 +170,20 @@ def test_patch_archived_t4c_instance_error( client: testclient.TestClient, db: orm.Session, executor_name: str, - t4c_instance: t4c_models.DatabaseT4CInstance, + t4c_instance: t4c_instance_models.DatabaseT4CInstance, ): users_crud.create_user( db, executor_name, executor_name, None, users_models.Role.ADMIN ) - t4c_crud.update_t4c_instance( - db, t4c_instance, t4c_models.PatchT4CInstance(is_archived=True) + t4c_instance_crud.update_t4c_instance( + db, + t4c_instance, + t4c_instance_models.PatchT4CInstance(is_archived=True), ) response = client.patch( - f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}", + f"/api/v1/settings/modelsources/t4c/instances/{t4c_instance.id}", json={ "name": "Patched test integration", }, @@ -192,20 +196,22 @@ def test_unarchive_t4c_instance( client: testclient.TestClient, db: orm.Session, executor_name: str, - t4c_instance: t4c_models.DatabaseT4CInstance, + t4c_instance: t4c_instance_models.DatabaseT4CInstance, ): users_crud.create_user( db, executor_name, executor_name, None, users_models.Role.ADMIN ) - t4c_crud.update_t4c_instance( - db, t4c_instance, t4c_models.PatchT4CInstance(is_archived=True) + t4c_instance_crud.update_t4c_instance( + db, + t4c_instance, + t4c_instance_models.PatchT4CInstance(is_archived=True), ) assert t4c_instance.is_archived response = client.patch( - f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}", + f"/api/v1/settings/modelsources/t4c/instances/{t4c_instance.id}", json={ "is_archived": False, }, @@ -213,7 +219,7 @@ def test_unarchive_t4c_instance( assert response.status_code == 200 - updated_t4c_instance = t4c_crud.get_t4c_instance_by_id( + updated_t4c_instance = t4c_instance_crud.get_t4c_instance_by_id( db, response.json()["id"] ) assert updated_t4c_instance @@ -225,21 +231,21 @@ def test_unarchive_t4c_instance( @pytest.mark.usefixtures("admin") def test_patch_t4c_instance_already_existing_name( client: testclient.TestClient, - t4c_instance: t4c_models.DatabaseT4CInstance, + t4c_instance: t4c_instance_models.DatabaseT4CInstance, + t4c_license_server: t4c_license_server_models.DatabaseT4CLicenseServer, tool_version: tools_models.DatabaseVersion, ): instance_name_1 = t4c_instance.name instance_name_2 = instance_name_1 + "-2" client.post( - "/api/v1/settings/modelsources/t4c", + "/api/v1/settings/modelsources/t4c/instances", json={ "name": instance_name_2, - "license": "test", "host": "test", + "license_server_id": t4c_license_server.id, "port": 2036, "cdo_port": 12036, - "usage_api": "http://localhost:8086", "rest_api": "http://localhost:8080", "username": "admin", "protocol": "tcp", @@ -249,7 +255,7 @@ def test_patch_t4c_instance_already_existing_name( ) response = client.patch( - f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}", + f"/api/v1/settings/modelsources/t4c/instances/{t4c_instance.id}", json={ "name": instance_name_2, }, @@ -270,33 +276,37 @@ def test_patch_t4c_instance_already_existing_name( def test_delete_t4c_instance( client: testclient.TestClient, db: orm.Session, - t4c_instance: t4c_models.DatabaseT4CInstance, + t4c_instance: t4c_instance_models.DatabaseT4CInstance, t4c_model: models_t4c_models.DatabaseT4CModel, ): response = client.delete( - f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}", + f"/api/v1/settings/modelsources/t4c/instances/{t4c_instance.id}", ) assert response.status_code == 204 - assert t4c_crud.get_t4c_instance_by_id(db, t4c_instance.id) is None + assert ( + t4c_instance_crud.get_t4c_instance_by_id(db, t4c_instance.id) is None + ) assert models_t4c_crud.get_t4c_model_by_id(db, t4c_model.id) is None def test_injectables_raise_when_archived_instance( db: orm.Session, executor_name: str, - t4c_instance: t4c_models.DatabaseT4CInstance, + t4c_instance: t4c_instance_models.DatabaseT4CInstance, ): users_crud.create_user( db, executor_name, executor_name, None, users_models.Role.ADMIN ) - t4c_crud.update_t4c_instance( - db, t4c_instance, t4c_models.PatchT4CInstance(is_archived=True) + t4c_instance_crud.update_t4c_instance( + db, + t4c_instance, + t4c_instance_models.PatchT4CInstance(is_archived=True), ) - with pytest.raises(settings_t4c_exceptions.T4CInstanceIsArchivedError): - settings_t4c_injectables.get_existing_unarchived_instance( + with pytest.raises(t4c_instance_exceptions.T4CInstanceIsArchivedError): + t4c_instance_injectables.get_existing_unarchived_instance( t4c_instance.id, db ) @@ -305,7 +315,7 @@ def test_update_t4c_instance_password_empty_string( client: testclient.TestClient, db: orm.Session, executor_name: str, - t4c_instance: t4c_models.DatabaseT4CInstance, + t4c_instance: t4c_instance_models.DatabaseT4CInstance, ): users_crud.create_user( db, executor_name, executor_name, None, users_models.Role.ADMIN @@ -314,60 +324,15 @@ def test_update_t4c_instance_password_empty_string( expected_password = t4c_instance.password response = client.patch( - f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}", + f"/api/v1/settings/modelsources/t4c/instances/{t4c_instance.id}", json={ "password": "", }, ) - updated_t4c_instance = t4c_crud.get_t4c_instance_by_id( + updated_t4c_instance = t4c_instance_crud.get_t4c_instance_by_id( db, response.json()["id"] ) assert updated_t4c_instance assert updated_t4c_instance.password == expected_password - - -@pytest.mark.usefixtures("mock_license_server") -def test_get_t4c_license_usage( - client: testclient.TestClient, - db: orm.Session, - executor_name: str, - t4c_instance: t4c_models.DatabaseT4CInstance, -): - users_crud.create_user( - db, executor_name, executor_name, None, users_models.Role.ADMIN - ) - response = client.get( - f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}/licenses", - ) - - assert response.status_code == 200 - assert response.json()["free"] == 19 - assert response.json()["total"] == 20 - - -@responses.activate -def test_get_t4c_license_usage_no_status( - client: testclient.TestClient, - db: orm.Session, - executor_name: str, - t4c_instance: t4c_models.DatabaseT4CInstance, -): - users_crud.create_user( - db, executor_name, executor_name, None, users_models.Role.ADMIN - ) - responses.get( - "http://localhost:8086/status/json", - status=status.HTTP_200_OK, - json={"status": {"message": "No last status available."}}, - ) - - response = client.get( - f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}/licenses", - ) - - assert response.status_code == 422 - assert ( - response.json()["detail"]["err_code"] == "T4C_LICENSE_SERVER_NO_STATUS" - ) diff --git a/backend/tests/settings/teamforcapella/test_t4c_license_servers.py b/backend/tests/settings/teamforcapella/test_t4c_license_servers.py new file mode 100644 index 0000000000..7b719c7273 --- /dev/null +++ b/backend/tests/settings/teamforcapella/test_t4c_license_servers.py @@ -0,0 +1,234 @@ +# SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors +# SPDX-License-Identifier: Apache-2.0 + +import pytest +from fastapi import testclient +from sqlalchemy import orm + +from capellacollab.settings.modelsources.t4c.license_server import ( + crud as license_server_crud, + models as license_server_models, +) +from capellacollab.users import crud as users_crud +from capellacollab.users import models as users_models + + +@pytest.mark.usefixtures("admin", "mock_license_server") +def test_create_t4c_license_server( + client: testclient.TestClient, + db: orm.Session, +): + response = client.post( + "/api/v1/settings/modelsources/t4c/license-servers", + json={ + "name": "test", + "usage_api": "http://localhost:8086", + "license_key": "test_license_key", + }, + ) + + print(response.read()) + + assert response.status_code == 200 + assert response.json()["name"] == "test" + + t4c_instance = license_server_crud.get_t4c_license_server_by_id( + db, response.json()["id"] + ) + + assert t4c_instance + assert t4c_instance.name == "test" + + +@pytest.mark.usefixtures("admin") +def test_create_t4c_license_server_already_existing_name( + client: testclient.TestClient, + t4c_license_server: license_server_models.DatabaseT4CLicenseServer, +): + response = client.post( + "/api/v1/settings/modelsources/t4c/license-servers", + json={ + "name": t4c_license_server.name, + "usage_api": "http://localhost:8086", + "license_key": "test_license_key", + }, + ) + + assert response.status_code == 409 + + detail = response.json()["detail"] + + assert ( + "A T4C License Server with a similar name already exists." + in detail["reason"] + ) + assert "name already used" in detail["title"] + + +@pytest.mark.usefixtures("t4c_license_server") +def test_get_t4c_license_servers( + client: testclient.TestClient, + db: orm.Session, + executor_name: str, +): + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) + + response = client.get( + "/api/v1/settings/modelsources/t4c/license-servers", + ) + + assert len(response.json()) == 2 + + assert response.json()[1]["name"] == "test license server" + + +def test_get_t4c_license_server( + client: testclient.TestClient, + db: orm.Session, + executor_name: str, + t4c_license_server: license_server_models.DatabaseT4CLicenseServer, +): + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) + + response = client.get( + f"/api/v1/settings/modelsources/t4c/license-servers/{t4c_license_server.id}", + ) + + assert response.json()["name"] == "test license server" + + +def test_patch_t4c_license_server( + client: testclient.TestClient, + db: orm.Session, + executor_name: str, + t4c_license_server: license_server_models.DatabaseT4CLicenseServer, +): + users_crud.create_user( + db, executor_name, executor_name, None, users_models.Role.ADMIN + ) + + response = client.patch( + f"/api/v1/settings/modelsources/t4c/license-servers/{t4c_license_server.id}", + json={ + "name": "Patched test license server", + }, + ) + + updated_license_server = license_server_crud.get_t4c_license_server_by_id( + db, response.json()["id"] + ) + assert updated_license_server + + assert response.status_code == 200 + + assert response.json()["name"] == "Patched test license server" + assert updated_license_server.name == "Patched test license server" + + assert response.json()["usage_api"] == "http://localhost:8086" + assert updated_license_server.usage_api == "http://localhost:8086" + + +@pytest.mark.usefixtures("admin") +def test_patch_t4c_license_server_already_existing_name( + client: testclient.TestClient, + t4c_license_server: license_server_models.DatabaseT4CLicenseServer, +): + license_server_name_1 = t4c_license_server.name + license_server_name_2 = license_server_name_1 + "-2" + + client.post( + "/api/v1/settings/modelsources/t4c/license-servers", + json={ + "name": license_server_name_2, + "usage_api": "http://localhost:8086", + "license_key": "test_license_key", + }, + ) + + response = client.patch( + f"/api/v1/settings/modelsources/t4c/license-servers/{t4c_license_server.id}", + json={ + "name": license_server_name_2, + }, + ) + + assert response.status_code == 409 + + detail = response.json()["detail"] + + assert ( + "A T4C License Server with a similar name already exists." + in detail["reason"] + ) + assert "name already used" in detail["title"] + + +@pytest.mark.usefixtures("admin") +def test_delete_t4c_license_server( + client: testclient.TestClient, + db: orm.Session, + t4c_license_server: license_server_models.DatabaseT4CLicenseServer, +): + response = client.delete( + f"/api/v1/settings/modelsources/t4c/license-servers/{t4c_license_server.id}", + ) + + assert response.status_code == 204 + assert ( + license_server_crud.get_t4c_license_server_by_id( + db, t4c_license_server.id + ) + is None + ) + + +# TODO + +# +# @pytest.mark.usefixtures("mock_license_server") +# def test_get_t4c_license_usage( +# client: testclient.TestClient, +# db: orm.Session, +# executor_name: str, +# t4c_instance: t4c_models.DatabaseT4CInstance, +# ): +# users_crud.create_user( +# db, executor_name, executor_name, None, users_models.Role.ADMIN +# ) +# response = client.get( +# f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}/licenses", +# ) +# +# assert response.status_code == 200 +# assert response.json()["free"] == 19 +# assert response.json()["total"] == 20 +# +# +# @responses.activate +# def test_get_t4c_license_usage_no_status( +# client: testclient.TestClient, +# db: orm.Session, +# executor_name: str, +# t4c_instance: t4c_models.DatabaseT4CInstance, +# ): +# users_crud.create_user( +# db, executor_name, executor_name, None, users_models.Role.ADMIN +# ) +# responses.get( +# "http://localhost:8086/status/json", +# status=status.HTTP_200_OK, +# json={"status": {"message": "No last status available."}}, +# ) +# +# response = client.get( +# f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}/licenses", +# ) +# +# assert response.status_code == 422 +# assert ( +# response.json()["detail"]["err_code"] == "T4C_LICENSE_SERVER_NO_STATUS" +# ) diff --git a/backend/tests/settings/teamforcapella/test_t4c_metrics.py b/backend/tests/settings/teamforcapella/test_t4c_metrics.py index 075658db85..f054d90bad 100644 --- a/backend/tests/settings/teamforcapella/test_t4c_metrics.py +++ b/backend/tests/settings/teamforcapella/test_t4c_metrics.py @@ -3,7 +3,7 @@ import pytest -from capellacollab.settings.modelsources.t4c import metrics +from capellacollab.settings.modelsources.t4c.license_server import metrics @pytest.mark.usefixtures("mock_license_server", "t4c_instance") diff --git a/backend/tests/settings/teamforcapella/test_t4c_repositories.py b/backend/tests/settings/teamforcapella/test_t4c_repositories.py index 7f687aa913..6725866582 100644 --- a/backend/tests/settings/teamforcapella/test_t4c_repositories.py +++ b/backend/tests/settings/teamforcapella/test_t4c_repositories.py @@ -6,8 +6,10 @@ from fastapi import testclient from sqlalchemy import orm -from capellacollab.settings.modelsources.t4c import models as t4c_models -from capellacollab.settings.modelsources.t4c.repositories import ( +from capellacollab.settings.modelsources.t4c.instance import ( + models as t4c_models, +) +from capellacollab.settings.modelsources.t4c.instance.repositories import ( crud as t4c_repositories_crud, ) @@ -38,7 +40,7 @@ def test_list_t4c_repositories( t4c_repositories_crud.create_t4c_repository(db, "test4", t4c_instance) t4c_repositories_crud.create_t4c_repository(db, "test5", t4c_instance) response = client.get( - f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}/repositories", + f"/api/v1/settings/modelsources/t4c/instances/{t4c_instance.id}/repositories", ) assert response.status_code == 200 @@ -68,7 +70,7 @@ def test_list_t4c_repositories_instance_unreachable_exception( t4c_repositories_crud.create_t4c_repository(db, "test4", t4c_instance) response = client.get( - f"/api/v1/settings/modelsources/t4c/{t4c_instance.id}/repositories", + f"/api/v1/settings/modelsources/t4c/instances/{t4c_instance.id}/repositories", ) assert response.status_code == 200 diff --git a/docs/docs/admin/settings/model-sources/t4c.md b/docs/docs/admin/settings/model-sources/t4c.md index 99b9a4329e..92d622588c 100644 --- a/docs/docs/admin/settings/model-sources/t4c.md +++ b/docs/docs/admin/settings/model-sources/t4c.md @@ -5,31 +5,41 @@ # Manage T4C Instances +To use Team for Capella, you must set up a license server and at least one T4C +instance. To do this, go into the `Settings` section of the Collaboration +Manager and select `T4C` below `Model sources`. + +## Define a T4C License Server + +You can see all existing license servers (if any). To add a new license server, +click on the "Add a license server" card. You have to enter the following +information: + +- **Name**: Any name to identify the license server +- **License Server API**: The URL of the license server API +- **License Key**: License key of your license server + ## Define a T4C Instance -1. Please navigate to `Menu` > `Settings` -1. Select `T4C` below `Model sources` -1. You can see all existing instances (if any). To add a new instance, click - on the "Add an instance" card. You have to enter the following information: - - 1. **Name**: Any name to identify the instance - 1. **Capella version**: Capella version that corresponds to the instance - 1. **License configuration**: License key of your license server - 1. **Protocol**: Protocol that should be used to communicate between - capella sessions and the T4C server - 1. **Host**: Hostname of the T4C server - 1. **Port**, **CDO Port**, and **HTTP Port** Corresponding ports of your - server - 1. **License server API**: License server API url - 1. **REST API**: REST API URL of the T4C server - 1. **Username**: Username with access to the REST API, required for - communication with the REST API - 1. **Password**: Password corresponding to username +You can see all existing instances (if any). To add a new instance, click on +the "Add an instance" card. You have to enter the following information: + +- **Name**: Any name to identify the instance +- **Capella version**: Capella version that corresponds to the instance +- **License Server**: Select the license server that should be used for this + instance +- **Protocol**: Protocol that should be used to communicate between capella + sessions and the T4C server +- **Host**: Hostname of the T4C server +- **Port**, **CDO Port**, and **HTTP Port** Corresponding ports of your + server +- **REST API**: REST API URL of the T4C server +- **Username**: Username with access to the REST API, required for + communication with the REST API +- **Password**: Password corresponding to username ## Archive a T4C Instance -1. Please navigate to `Menu` > `Settings` -1. Select `T4C` bewlow `Model sources` 1. Click on the instance that you want to archive 1. Click on the `Archive` button. When everything worked you should see a messages stating "Instance updated: The instance _name_ is now archived" diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 049584761f..931b08ec78 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -4181,6 +4181,7 @@ "version": "2.2.2", "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", + "dev": true, "dependencies": { "agent-base": "^7.1.0", "http-proxy-agent": "^7.0.0", @@ -4195,12 +4196,14 @@ "node_modules/@npmcli/agent/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/@npmcli/fs": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", + "dev": true, "dependencies": { "semver": "^7.3.5" }, @@ -4212,6 +4215,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", + "dev": true, "dependencies": { "@npmcli/promise-spawn": "^7.0.0", "ini": "^4.1.3", @@ -4231,6 +4235,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, "engines": { "node": ">=16" } @@ -4238,12 +4243,14 @@ "node_modules/@npmcli/git/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/@npmcli/git/node_modules/which": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, "dependencies": { "isexe": "^3.1.1" }, @@ -4258,6 +4265,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", + "dev": true, "dependencies": { "npm-bundled": "^3.0.0", "npm-normalize-package-bin": "^3.0.0" @@ -4273,6 +4281,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", + "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -4281,6 +4290,7 @@ "version": "5.2.1", "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", + "dev": true, "dependencies": { "@npmcli/git": "^5.0.0", "glob": "^10.2.2", @@ -4298,6 +4308,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", + "dev": true, "dependencies": { "which": "^4.0.0" }, @@ -4309,6 +4320,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, "engines": { "node": ">=16" } @@ -4317,6 +4329,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, "dependencies": { "isexe": "^3.1.1" }, @@ -4331,6 +4344,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", + "dev": true, "engines": { "node": "^16.14.0 || >=18.0.0" } @@ -4339,6 +4353,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", + "dev": true, "dependencies": { "@npmcli/node-gyp": "^3.0.0", "@npmcli/package-json": "^5.0.0", @@ -4355,6 +4370,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, "engines": { "node": ">=16" } @@ -4363,6 +4379,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, "dependencies": { "isexe": "^3.1.1" }, @@ -4669,6 +4686,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", + "dev": true, "dependencies": { "@sigstore/protobuf-specs": "^0.3.2" }, @@ -4680,6 +4698,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", + "dev": true, "engines": { "node": "^16.14.0 || >=18.0.0" } @@ -4688,6 +4707,7 @@ "version": "0.3.2", "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.2.tgz", "integrity": "sha512-c6B0ehIWxMI8wiS/bj6rHMPqeFvngFV7cDU/MY+B16P9Z3Mp9k8L93eYZ7BYzSickzuqAQqAq0V956b3Ju6mLw==", + "dev": true, "engines": { "node": "^16.14.0 || >=18.0.0" } @@ -4696,6 +4716,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", + "dev": true, "dependencies": { "@sigstore/bundle": "^2.3.2", "@sigstore/core": "^1.0.0", @@ -4712,6 +4733,7 @@ "version": "2.3.4", "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", + "dev": true, "dependencies": { "@sigstore/protobuf-specs": "^0.3.2", "tuf-js": "^2.2.1" @@ -4724,6 +4746,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", + "dev": true, "dependencies": { "@sigstore/bundle": "^2.3.2", "@sigstore/core": "^1.1.0", @@ -5723,6 +5746,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", + "dev": true, "engines": { "node": "^16.14.0 || >=18.0.0" } @@ -5731,6 +5755,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", + "dev": true, "dependencies": { "@tufjs/canonical-json": "2.0.0", "minimatch": "^9.0.4" @@ -6513,6 +6538,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", + "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -6601,6 +6627,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" @@ -7523,6 +7550,7 @@ "version": "18.0.4", "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", + "dev": true, "dependencies": { "@npmcli/fs": "^3.1.0", "fs-minipass": "^3.0.0", @@ -7544,7 +7572,8 @@ "node_modules/cacache/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/cache-base": { "version": "1.0.1", @@ -7762,6 +7791,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, "engines": { "node": ">=10" } @@ -7879,6 +7909,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, "engines": { "node": ">=6" } @@ -9307,7 +9338,8 @@ "node_modules/err-code": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==" + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "dev": true }, "node_modules/errno": { "version": "0.1.8", @@ -10179,7 +10211,8 @@ "node_modules/exponential-backoff": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", - "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==" + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "dev": true }, "node_modules/express": { "version": "4.21.0", @@ -10892,6 +10925,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", + "dev": true, "dependencies": { "minipass": "^7.0.3" }, @@ -11394,6 +11428,7 @@ "version": "7.0.2", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", + "dev": true, "dependencies": { "lru-cache": "^10.0.1" }, @@ -11404,7 +11439,8 @@ "node_modules/hosted-git-info/node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/hpack.js": { "version": "2.1.6", @@ -11593,7 +11629,8 @@ "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "dev": true }, "node_modules/http-deceiver": { "version": "1.2.7", @@ -11779,6 +11816,7 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", + "dev": true, "dependencies": { "minimatch": "^9.0.0" }, @@ -11824,6 +11862,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "engines": { "node": ">=0.8.19" } @@ -11832,6 +11871,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, "engines": { "node": ">=8" } @@ -11855,6 +11895,7 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", + "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -12179,7 +12220,8 @@ "node_modules/is-lambda": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==" + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "dev": true }, "node_modules/is-negative-zero": { "version": "2.0.3", @@ -12677,6 +12719,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", + "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -12726,6 +12769,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", + "dev": true, "engines": [ "node >= 0.2.0" ] @@ -13895,6 +13939,7 @@ "version": "13.0.1", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", + "dev": true, "dependencies": { "@npmcli/agent": "^2.0.0", "cacache": "^18.0.0", @@ -14174,6 +14219,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", + "dev": true, "dependencies": { "minipass": "^7.0.3" }, @@ -14185,6 +14231,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", + "dev": true, "dependencies": { "minipass": "^7.0.3", "minipass-sized": "^1.0.3", @@ -14201,6 +14248,7 @@ "version": "1.0.5", "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -14212,6 +14260,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -14222,12 +14271,14 @@ "node_modules/minipass-flush/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/minipass-pipeline": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -14239,6 +14290,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -14249,12 +14301,14 @@ "node_modules/minipass-pipeline/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/minipass-sized": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -14266,6 +14320,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -14276,12 +14331,14 @@ "node_modules/minipass-sized/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -14294,6 +14351,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -14304,7 +14362,8 @@ "node_modules/minizlib/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/mitt": { "version": "3.0.1", @@ -14660,6 +14719,7 @@ "version": "10.2.0", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.2.0.tgz", "integrity": "sha512-sp3FonBAaFe4aYTcFdZUn2NYkbP7xroPGYvQmP4Nl5PxamznItBnNCgjrVTKrEfQynInMsJvZrdmqUnysCJ8rw==", + "dev": true, "dependencies": { "env-paths": "^2.2.0", "exponential-backoff": "^3.1.1", @@ -14709,6 +14769,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, "engines": { "node": ">=16" } @@ -14717,6 +14778,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "dev": true, "dependencies": { "isexe": "^3.1.1" }, @@ -14737,6 +14799,7 @@ "version": "7.2.1", "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", + "dev": true, "dependencies": { "abbrev": "^2.0.0" }, @@ -14751,6 +14814,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", + "dev": true, "dependencies": { "hosted-git-info": "^7.0.0", "semver": "^7.3.5", @@ -14934,6 +14998,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", + "dev": true, "dependencies": { "npm-normalize-package-bin": "^3.0.0" }, @@ -14959,6 +15024,7 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", + "dev": true, "dependencies": { "semver": "^7.1.1" }, @@ -14970,6 +15036,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -14978,6 +15045,7 @@ "version": "11.0.3", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", + "dev": true, "dependencies": { "hosted-git-info": "^7.0.0", "proc-log": "^4.0.0", @@ -14992,6 +15060,7 @@ "version": "8.0.2", "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", + "dev": true, "dependencies": { "ignore-walk": "^6.0.4" }, @@ -15003,6 +15072,7 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", + "dev": true, "dependencies": { "npm-install-checks": "^6.0.0", "npm-normalize-package-bin": "^3.0.0", @@ -15017,6 +15087,7 @@ "version": "17.1.0", "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", + "dev": true, "dependencies": { "@npmcli/redact": "^2.0.0", "jsonparse": "^1.3.1", @@ -17656,6 +17727,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, "dependencies": { "aggregate-error": "^3.0.0" }, @@ -17739,6 +17811,7 @@ "version": "18.0.6", "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", + "dev": true, "dependencies": { "@npmcli/git": "^5.0.0", "@npmcli/installed-package-contents": "^2.0.1", @@ -18636,6 +18709,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", + "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } @@ -18665,12 +18739,14 @@ "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==" + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "dev": true }, "node_modules/promise-retry": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "dev": true, "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -19400,6 +19476,7 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "dev": true, "engines": { "node": ">= 4" } @@ -20069,6 +20146,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", + "dev": true, "dependencies": { "@sigstore/bundle": "^2.3.2", "@sigstore/core": "^1.0.0", @@ -20417,6 +20495,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -20425,12 +20504,14 @@ "node_modules/spdx-exceptions": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==" + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true }, "node_modules/spdx-expression-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -20439,7 +20520,8 @@ "node_modules/spdx-license-ids": { "version": "3.0.20", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.20.tgz", - "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==" + "integrity": "sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==", + "dev": true }, "node_modules/spdy": { "version": "4.0.2", @@ -20503,6 +20585,7 @@ "version": "10.0.6", "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", + "dev": true, "dependencies": { "minipass": "^7.0.3" }, @@ -21422,6 +21505,7 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -21461,6 +21545,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, "dependencies": { "minipass": "^3.0.0" }, @@ -21472,6 +21557,7 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -21483,6 +21569,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -21491,6 +21578,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -21501,7 +21589,8 @@ "node_modules/tar/node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true }, "node_modules/telejson": { "version": "7.2.0", @@ -21630,7 +21719,8 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, "node_modules/thenify": { "version": "3.3.1", @@ -21972,6 +22062,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", + "dev": true, "dependencies": { "@tufjs/models": "2.0.1", "debug": "^4.3.4", @@ -22331,6 +22422,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", + "dev": true, "dependencies": { "unique-slug": "^4.0.0" }, @@ -22342,6 +22434,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", + "dev": true, "dependencies": { "imurmurhash": "^0.1.4" }, @@ -22615,6 +22708,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -22624,6 +22718,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", + "dev": true, "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 29277ff6fc..bd3eb3b6e2 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -44,6 +44,7 @@ import { PureVariantsComponent } from './settings/integrations/pure-variants/pur import { EditGitSettingsComponent } from './settings/modelsources/git-settings/edit-git-settings/edit-git-settings.component'; import { GitSettingsComponent } from './settings/modelsources/git-settings/git-settings.component'; import { EditT4CInstanceComponent } from './settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component'; +import { EditT4cLicenseServerComponent } from './settings/modelsources/t4c-settings/edit-t4c-license-server/edit-t4c-license-server.component'; import { T4CSettingsWrapperComponent } from './settings/modelsources/t4c-settings/t4c-settings-wrapper/t4c-settings-wrapper.component'; import { T4CSettingsComponent } from './settings/modelsources/t4c-settings/t4c-settings.component'; import { SettingsComponent } from './settings/settings.component'; @@ -434,7 +435,7 @@ export const routes: Routes = [ }, { path: 't4c', - data: { breadcrumb: 'T4C Instances' }, + data: { breadcrumb: 'T4C' }, component: T4CSettingsWrapperComponent, children: [ { @@ -443,8 +444,8 @@ export const routes: Routes = [ component: T4CSettingsComponent, }, { - path: 'create', - data: { breadcrumb: 'new' }, + path: 'create-instance', + data: { breadcrumb: 'New Instance' }, component: EditT4CInstanceComponent, }, { @@ -454,6 +455,18 @@ export const routes: Routes = [ }, component: EditT4CInstanceComponent, }, + { + path: 'create-license-server', + data: { breadcrumb: 'New License Server' }, + component: EditT4cLicenseServerComponent, + }, + { + path: 'license-server/:licenseServer', + data: { + breadcrumb: (data: Data) => data.licenseServer?.name, + }, + component: EditT4cLicenseServerComponent, + }, ], }, ], diff --git a/frontend/src/app/openapi/.openapi-generator/FILES b/frontend/src/app/openapi/.openapi-generator/FILES index 9531e2e7f1..b815508632 100644 --- a/frontend/src/app/openapi/.openapi-generator/FILES +++ b/frontend/src/app/openapi/.openapi-generator/FILES @@ -20,7 +20,8 @@ api/projects-models.service.ts api/projects.service.ts api/sessions.service.ts api/settings-modelsources-git.service.ts -api/settings-modelsources-t4-c.service.ts +api/settings-modelsources-t4-c-instances.service.ts +api/settings-modelsources-t4-c-license-servers.service.ts api/tools.service.ts api/users-sessions.service.ts api/users-token.service.ts @@ -101,6 +102,7 @@ model/page-pipeline-run.ts model/patch-project-user.ts model/patch-project.ts model/patch-t4-c-instance.ts +model/patch-t4-c-license-server.ts model/patch-t4-c-model.ts model/patch-tool-model.ts model/patch-user.ts @@ -154,6 +156,9 @@ model/simple-t4-c-model.ts model/status-response.ts model/submit-t4-c-model.ts model/t4-c-instance.ts +model/t4-c-license-server-base.ts +model/t4-c-license-server-usage.ts +model/t4-c-license-server.ts model/t4-c-model.ts model/t4-c-repository-status.ts model/t4-c-repository.ts diff --git a/frontend/src/app/openapi/api/api.ts b/frontend/src/app/openapi/api/api.ts index 0b15f2169d..9990dc29f7 100644 --- a/frontend/src/app/openapi/api/api.ts +++ b/frontend/src/app/openapi/api/api.ts @@ -49,8 +49,10 @@ export * from './sessions.service'; import { SessionsService } from './sessions.service'; export * from './settings-modelsources-git.service'; import { SettingsModelsourcesGitService } from './settings-modelsources-git.service'; -export * from './settings-modelsources-t4-c.service'; -import { SettingsModelsourcesT4CService } from './settings-modelsources-t4-c.service'; +export * from './settings-modelsources-t4-c-instances.service'; +import { SettingsModelsourcesT4CInstancesService } from './settings-modelsources-t4-c-instances.service'; +export * from './settings-modelsources-t4-c-license-servers.service'; +import { SettingsModelsourcesT4CLicenseServersService } from './settings-modelsources-t4-c-license-servers.service'; export * from './tools.service'; import { ToolsService } from './tools.service'; export * from './users.service'; @@ -61,4 +63,4 @@ export * from './users-token.service'; import { UsersTokenService } from './users-token.service'; export * from './users-workspaces.service'; import { UsersWorkspacesService } from './users-workspaces.service'; -export const APIS = [AuthenticationService, ConfigurationService, EventsService, FeedbackService, HealthService, IntegrationsPureVariantsService, MetadataService, NavbarService, NoticesService, ProjectsService, ProjectsEventsService, ProjectsModelsService, ProjectsModelsBackupsService, ProjectsModelsDiagramsService, ProjectsModelsGitService, ProjectsModelsModelComplexityBadgeService, ProjectsModelsRestrictionsService, ProjectsModelsT4CService, SessionsService, SettingsModelsourcesGitService, SettingsModelsourcesT4CService, ToolsService, UsersService, UsersSessionsService, UsersTokenService, UsersWorkspacesService]; +export const APIS = [AuthenticationService, ConfigurationService, EventsService, FeedbackService, HealthService, IntegrationsPureVariantsService, MetadataService, NavbarService, NoticesService, ProjectsService, ProjectsEventsService, ProjectsModelsService, ProjectsModelsBackupsService, ProjectsModelsDiagramsService, ProjectsModelsGitService, ProjectsModelsModelComplexityBadgeService, ProjectsModelsRestrictionsService, ProjectsModelsT4CService, SessionsService, SettingsModelsourcesGitService, SettingsModelsourcesT4CInstancesService, SettingsModelsourcesT4CLicenseServersService, ToolsService, UsersService, UsersSessionsService, UsersTokenService, UsersWorkspacesService]; diff --git a/frontend/src/app/openapi/api/settings-modelsources-t4-c.service.ts b/frontend/src/app/openapi/api/settings-modelsources-t4-c-instances.service.ts similarity index 87% rename from frontend/src/app/openapi/api/settings-modelsources-t4-c.service.ts rename to frontend/src/app/openapi/api/settings-modelsources-t4-c-instances.service.ts index da91d04022..870fe106ad 100644 --- a/frontend/src/app/openapi/api/settings-modelsources-t4-c.service.ts +++ b/frontend/src/app/openapi/api/settings-modelsources-t4-c-instances.service.ts @@ -23,8 +23,6 @@ import { CreateT4CInstance } from '../model/create-t4-c-instance'; // @ts-ignore import { CreateT4CRepository } from '../model/create-t4-c-repository'; // @ts-ignore -import { GetSessionUsageResponse } from '../model/get-session-usage-response'; -// @ts-ignore import { HTTPValidationError } from '../model/http-validation-error'; // @ts-ignore import { PatchT4CInstance } from '../model/patch-t4-c-instance'; @@ -46,7 +44,7 @@ import { Configuration } from '../configurat @Injectable({ providedIn: 'root' }) -export class SettingsModelsourcesT4CService { +export class SettingsModelsourcesT4CInstancesService { protected basePath = 'http://localhost'; public defaultHeaders = new HttpHeaders(); @@ -174,7 +172,7 @@ export class SettingsModelsourcesT4CService { } } - let localVarPath = `/api/v1/settings/modelsources/t4c`; + let localVarPath = `/api/v1/settings/modelsources/t4c/instances`; return this.httpClient.request('post', `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, @@ -259,7 +257,7 @@ export class SettingsModelsourcesT4CService { } } - let localVarPath = `/api/v1/settings/modelsources/t4c/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/repositories`; + let localVarPath = `/api/v1/settings/modelsources/t4c/instances/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/repositories`; return this.httpClient.request('post', `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, @@ -331,7 +329,7 @@ export class SettingsModelsourcesT4CService { } } - let localVarPath = `/api/v1/settings/modelsources/t4c/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}`; + let localVarPath = `/api/v1/settings/modelsources/t4c/instances/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}`; return this.httpClient.request('delete', `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, @@ -406,7 +404,7 @@ export class SettingsModelsourcesT4CService { } } - let localVarPath = `/api/v1/settings/modelsources/t4c/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/repositories/${this.configuration.encodeParam({name: "t4cRepositoryId", value: t4cRepositoryId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}`; + let localVarPath = `/api/v1/settings/modelsources/t4c/instances/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/repositories/${this.configuration.encodeParam({name: "t4cRepositoryId", value: t4cRepositoryId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}`; return this.httpClient.request('delete', `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, @@ -490,7 +488,7 @@ export class SettingsModelsourcesT4CService { } } - let localVarPath = `/api/v1/settings/modelsources/t4c/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}`; + let localVarPath = `/api/v1/settings/modelsources/t4c/instances/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}`; return this.httpClient.request('patch', `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, @@ -505,77 +503,6 @@ export class SettingsModelsourcesT4CService { ); } - /** - * Fetch T4C Licenses - * @param t4cInstanceId - * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. - * @param reportProgress flag to report request and response progress. - */ - public fetchT4cLicenses(t4cInstanceId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; - public fetchT4cLicenses(t4cInstanceId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; - public fetchT4cLicenses(t4cInstanceId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; - public fetchT4cLicenses(t4cInstanceId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { - if (t4cInstanceId === null || t4cInstanceId === undefined) { - throw new Error('Required parameter t4cInstanceId was null or undefined when calling fetchT4cLicenses.'); - } - - let localVarHeaders = this.defaultHeaders; - - let localVarCredential: string | undefined; - // authentication (PersonalAccessToken) required - localVarCredential = this.configuration.lookupCredential('PersonalAccessToken'); - if (localVarCredential) { - localVarHeaders = localVarHeaders.set('Authorization', 'Basic ' + localVarCredential); - } - - let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; - if (localVarHttpHeaderAcceptSelected === undefined) { - // to determine the Accept header - const httpHeaderAccepts: string[] = [ - 'application/json' - ]; - localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); - } - if (localVarHttpHeaderAcceptSelected !== undefined) { - localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); - } - - let localVarHttpContext: HttpContext | undefined = options && options.context; - if (localVarHttpContext === undefined) { - localVarHttpContext = new HttpContext(); - } - - let localVarTransferCache: boolean | undefined = options && options.transferCache; - if (localVarTransferCache === undefined) { - localVarTransferCache = true; - } - - - let responseType_: 'text' | 'json' | 'blob' = 'json'; - if (localVarHttpHeaderAcceptSelected) { - if (localVarHttpHeaderAcceptSelected.startsWith('text')) { - responseType_ = 'text'; - } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { - responseType_ = 'json'; - } else { - responseType_ = 'blob'; - } - } - - let localVarPath = `/api/v1/settings/modelsources/t4c/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/licenses`; - return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, - { - context: localVarHttpContext, - responseType: responseType_, - withCredentials: this.configuration.withCredentials, - headers: localVarHeaders, - observe: observe, - transferCache: localVarTransferCache, - reportProgress: reportProgress - } - ); - } - /** * Get T4C Instance * @param t4cInstanceId @@ -633,7 +560,7 @@ export class SettingsModelsourcesT4CService { } } - let localVarPath = `/api/v1/settings/modelsources/t4c/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}`; + let localVarPath = `/api/v1/settings/modelsources/t4c/instances/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}`; return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, @@ -700,7 +627,7 @@ export class SettingsModelsourcesT4CService { } } - let localVarPath = `/api/v1/settings/modelsources/t4c`; + let localVarPath = `/api/v1/settings/modelsources/t4c/instances`; return this.httpClient.request>('get', `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, @@ -771,7 +698,7 @@ export class SettingsModelsourcesT4CService { } } - let localVarPath = `/api/v1/settings/modelsources/t4c/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/repositories`; + let localVarPath = `/api/v1/settings/modelsources/t4c/instances/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/repositories`; return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, @@ -846,7 +773,7 @@ export class SettingsModelsourcesT4CService { } } - let localVarPath = `/api/v1/settings/modelsources/t4c/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/repositories/${this.configuration.encodeParam({name: "t4cRepositoryId", value: t4cRepositoryId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/recreate`; + let localVarPath = `/api/v1/settings/modelsources/t4c/instances/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/repositories/${this.configuration.encodeParam({name: "t4cRepositoryId", value: t4cRepositoryId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/recreate`; return this.httpClient.request('post', `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, @@ -921,7 +848,7 @@ export class SettingsModelsourcesT4CService { } } - let localVarPath = `/api/v1/settings/modelsources/t4c/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/repositories/${this.configuration.encodeParam({name: "t4cRepositoryId", value: t4cRepositoryId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/start`; + let localVarPath = `/api/v1/settings/modelsources/t4c/instances/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/repositories/${this.configuration.encodeParam({name: "t4cRepositoryId", value: t4cRepositoryId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/start`; return this.httpClient.request('post', `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, @@ -996,7 +923,7 @@ export class SettingsModelsourcesT4CService { } } - let localVarPath = `/api/v1/settings/modelsources/t4c/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/repositories/${this.configuration.encodeParam({name: "t4cRepositoryId", value: t4cRepositoryId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/stop`; + let localVarPath = `/api/v1/settings/modelsources/t4c/instances/${this.configuration.encodeParam({name: "t4cInstanceId", value: t4cInstanceId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/repositories/${this.configuration.encodeParam({name: "t4cRepositoryId", value: t4cRepositoryId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/stop`; return this.httpClient.request('post', `${this.configuration.basePath}${localVarPath}`, { context: localVarHttpContext, diff --git a/frontend/src/app/openapi/api/settings-modelsources-t4-c-license-servers.service.ts b/frontend/src/app/openapi/api/settings-modelsources-t4-c-license-servers.service.ts new file mode 100644 index 0000000000..a206c41a31 --- /dev/null +++ b/frontend/src/app/openapi/api/settings-modelsources-t4-c-license-servers.service.ts @@ -0,0 +1,549 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Capella Collaboration + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + + To generate a new version, run `make openapi` in the root directory of this repository. + */ + +/* tslint:disable:no-unused-variable member-ordering */ + +import { Inject, Injectable, Optional } from '@angular/core'; +import { HttpClient, HttpHeaders, HttpParams, + HttpResponse, HttpEvent, HttpParameterCodec, HttpContext + } from '@angular/common/http'; +import { CustomHttpParameterCodec } from '../encoder'; +import { Observable } from 'rxjs'; + +// @ts-ignore +import { GetSessionUsageResponse } from '../model/get-session-usage-response'; +// @ts-ignore +import { HTTPValidationError } from '../model/http-validation-error'; +// @ts-ignore +import { PatchT4CLicenseServer } from '../model/patch-t4-c-license-server'; +// @ts-ignore +import { T4CLicenseServer } from '../model/t4-c-license-server'; +// @ts-ignore +import { T4CLicenseServerBase } from '../model/t4-c-license-server-base'; + +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS } from '../variables'; +import { Configuration } from '../configuration'; + + + +@Injectable({ + providedIn: 'root' +}) +export class SettingsModelsourcesT4CLicenseServersService { + + protected basePath = 'http://localhost'; + public defaultHeaders = new HttpHeaders(); + public configuration = new Configuration(); + public encoder: HttpParameterCodec; + + constructor(protected httpClient: HttpClient, @Optional()@Inject(BASE_PATH) basePath: string|string[], @Optional() configuration: Configuration) { + if (configuration) { + this.configuration = configuration; + } + if (typeof this.configuration.basePath !== 'string') { + const firstBasePath = Array.isArray(basePath) ? basePath[0] : undefined; + if (firstBasePath != undefined) { + basePath = firstBasePath; + } + + if (typeof basePath !== 'string') { + basePath = this.basePath; + } + this.configuration.basePath = basePath; + } + this.encoder = this.configuration.encoder || new CustomHttpParameterCodec(); + } + + + // @ts-ignore + private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams { + if (typeof value === "object" && value instanceof Date === false) { + httpParams = this.addToHttpParamsRecursive(httpParams, value); + } else { + httpParams = this.addToHttpParamsRecursive(httpParams, value, key); + } + return httpParams; + } + + private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams { + if (value == null) { + return httpParams; + } + + if (typeof value === "object") { + if (Array.isArray(value)) { + (value as any[]).forEach( elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key)); + } else if (value instanceof Date) { + if (key != null) { + httpParams = httpParams.append(key, (value as Date).toISOString().substring(0, 10)); + } else { + throw Error("key may not be null if value is Date"); + } + } else { + Object.keys(value).forEach( k => httpParams = this.addToHttpParamsRecursive( + httpParams, value[k], key != null ? `${key}.${k}` : k)); + } + } else if (key != null) { + httpParams = httpParams.append(key, value); + } else { + throw Error("key may not be null if value is not object or array"); + } + return httpParams; + } + + /** + * Create T4C License Server + * @param t4CLicenseServerBase + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public createT4cLicenseServer(t4CLicenseServerBase: T4CLicenseServerBase, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public createT4cLicenseServer(t4CLicenseServerBase: T4CLicenseServerBase, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public createT4cLicenseServer(t4CLicenseServerBase: T4CLicenseServerBase, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public createT4cLicenseServer(t4CLicenseServerBase: T4CLicenseServerBase, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + if (t4CLicenseServerBase === null || t4CLicenseServerBase === undefined) { + throw new Error('Required parameter t4CLicenseServerBase was null or undefined when calling createT4cLicenseServer.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (PersonalAccessToken) required + localVarCredential = this.configuration.lookupCredential('PersonalAccessToken'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Basic ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/v1/settings/modelsources/t4c/license-servers`; + return this.httpClient.request('post', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + body: t4CLicenseServerBase, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + + /** + * Delete T4C License Server + * @param t4cLicenseServerId + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public deleteT4cLicenseServer(t4cLicenseServerId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public deleteT4cLicenseServer(t4cLicenseServerId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public deleteT4cLicenseServer(t4cLicenseServerId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public deleteT4cLicenseServer(t4cLicenseServerId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + if (t4cLicenseServerId === null || t4cLicenseServerId === undefined) { + throw new Error('Required parameter t4cLicenseServerId was null or undefined when calling deleteT4cLicenseServer.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (PersonalAccessToken) required + localVarCredential = this.configuration.lookupCredential('PersonalAccessToken'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Basic ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/v1/settings/modelsources/t4c/license-servers/${this.configuration.encodeParam({name: "t4cLicenseServerId", value: t4cLicenseServerId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}`; + return this.httpClient.request('delete', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + + /** + * Edit T4C License Server + * @param t4cLicenseServerId + * @param patchT4CLicenseServer + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public editT4cLicenseServer(t4cLicenseServerId: number, patchT4CLicenseServer: PatchT4CLicenseServer, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public editT4cLicenseServer(t4cLicenseServerId: number, patchT4CLicenseServer: PatchT4CLicenseServer, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public editT4cLicenseServer(t4cLicenseServerId: number, patchT4CLicenseServer: PatchT4CLicenseServer, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public editT4cLicenseServer(t4cLicenseServerId: number, patchT4CLicenseServer: PatchT4CLicenseServer, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + if (t4cLicenseServerId === null || t4cLicenseServerId === undefined) { + throw new Error('Required parameter t4cLicenseServerId was null or undefined when calling editT4cLicenseServer.'); + } + if (patchT4CLicenseServer === null || patchT4CLicenseServer === undefined) { + throw new Error('Required parameter patchT4CLicenseServer was null or undefined when calling editT4cLicenseServer.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (PersonalAccessToken) required + localVarCredential = this.configuration.lookupCredential('PersonalAccessToken'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Basic ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + // to determine the Content-Type header + const consumes: string[] = [ + 'application/json' + ]; + const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes); + if (httpContentTypeSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Content-Type', httpContentTypeSelected); + } + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/v1/settings/modelsources/t4c/license-servers/${this.configuration.encodeParam({name: "t4cLicenseServerId", value: t4cLicenseServerId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}`; + return this.httpClient.request('patch', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + body: patchT4CLicenseServer, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + + /** + * Fetch T4C License Server Licenses + * @param t4cLicenseServerId + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public fetchT4cLicenseServerLicenses(t4cLicenseServerId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public fetchT4cLicenseServerLicenses(t4cLicenseServerId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public fetchT4cLicenseServerLicenses(t4cLicenseServerId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public fetchT4cLicenseServerLicenses(t4cLicenseServerId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + if (t4cLicenseServerId === null || t4cLicenseServerId === undefined) { + throw new Error('Required parameter t4cLicenseServerId was null or undefined when calling fetchT4cLicenseServerLicenses.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (PersonalAccessToken) required + localVarCredential = this.configuration.lookupCredential('PersonalAccessToken'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Basic ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/v1/settings/modelsources/t4c/license-servers/${this.configuration.encodeParam({name: "t4cLicenseServerId", value: t4cLicenseServerId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}/licenses`; + return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + + /** + * Get T4C License Server + * @param t4cLicenseServerId + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public getT4cLicenseServer(t4cLicenseServerId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable; + public getT4cLicenseServer(t4cLicenseServerId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getT4cLicenseServer(t4cLicenseServerId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getT4cLicenseServer(t4cLicenseServerId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + if (t4cLicenseServerId === null || t4cLicenseServerId === undefined) { + throw new Error('Required parameter t4cLicenseServerId was null or undefined when calling getT4cLicenseServer.'); + } + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (PersonalAccessToken) required + localVarCredential = this.configuration.lookupCredential('PersonalAccessToken'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Basic ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/v1/settings/modelsources/t4c/license-servers/${this.configuration.encodeParam({name: "t4cLicenseServerId", value: t4cLicenseServerId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: undefined})}`; + return this.httpClient.request('get', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + + /** + * Get T4C License Servers + * @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body. + * @param reportProgress flag to report request and response progress. + */ + public getT4cLicenseServers(observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>; + public getT4cLicenseServers(observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public getT4cLicenseServers(observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable>>; + public getT4cLicenseServers(observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable { + + let localVarHeaders = this.defaultHeaders; + + let localVarCredential: string | undefined; + // authentication (PersonalAccessToken) required + localVarCredential = this.configuration.lookupCredential('PersonalAccessToken'); + if (localVarCredential) { + localVarHeaders = localVarHeaders.set('Authorization', 'Basic ' + localVarCredential); + } + + let localVarHttpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept; + if (localVarHttpHeaderAcceptSelected === undefined) { + // to determine the Accept header + const httpHeaderAccepts: string[] = [ + 'application/json' + ]; + localVarHttpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts); + } + if (localVarHttpHeaderAcceptSelected !== undefined) { + localVarHeaders = localVarHeaders.set('Accept', localVarHttpHeaderAcceptSelected); + } + + let localVarHttpContext: HttpContext | undefined = options && options.context; + if (localVarHttpContext === undefined) { + localVarHttpContext = new HttpContext(); + } + + let localVarTransferCache: boolean | undefined = options && options.transferCache; + if (localVarTransferCache === undefined) { + localVarTransferCache = true; + } + + + let responseType_: 'text' | 'json' | 'blob' = 'json'; + if (localVarHttpHeaderAcceptSelected) { + if (localVarHttpHeaderAcceptSelected.startsWith('text')) { + responseType_ = 'text'; + } else if (this.configuration.isJsonMime(localVarHttpHeaderAcceptSelected)) { + responseType_ = 'json'; + } else { + responseType_ = 'blob'; + } + } + + let localVarPath = `/api/v1/settings/modelsources/t4c/license-servers`; + return this.httpClient.request>('get', `${this.configuration.basePath}${localVarPath}`, + { + context: localVarHttpContext, + responseType: responseType_, + withCredentials: this.configuration.withCredentials, + headers: localVarHeaders, + observe: observe, + transferCache: localVarTransferCache, + reportProgress: reportProgress + } + ); + } + +} diff --git a/frontend/src/app/openapi/model/create-t4-c-instance.ts b/frontend/src/app/openapi/model/create-t4-c-instance.ts index 0a321fea81..68c6758c9d 100644 --- a/frontend/src/app/openapi/model/create-t4-c-instance.ts +++ b/frontend/src/app/openapi/model/create-t4-c-instance.ts @@ -13,17 +13,16 @@ import { Protocol } from './protocol'; export interface CreateT4CInstance { - license: string; host: string; port: number; cdo_port: number; http_port?: number | null; - usage_api: string; rest_api: string; username: string; protocol: Protocol; name: string; version_id: number; + license_server_id: number; is_archived?: boolean | null; password: string; } diff --git a/frontend/src/app/openapi/model/models.ts b/frontend/src/app/openapi/model/models.ts index 57d767148a..a4eaefba86 100644 --- a/frontend/src/app/openapi/model/models.ts +++ b/frontend/src/app/openapi/model/models.ts @@ -80,6 +80,7 @@ export * from './page-pipeline-run'; export * from './patch-project'; export * from './patch-project-user'; export * from './patch-t4-c-instance'; +export * from './patch-t4-c-license-server'; export * from './patch-t4-c-model'; export * from './patch-tool-model'; export * from './patch-user'; @@ -133,6 +134,9 @@ export * from './simple-t4-c-model'; export * from './status-response'; export * from './submit-t4-c-model'; export * from './t4-c-instance'; +export * from './t4-c-license-server'; +export * from './t4-c-license-server-base'; +export * from './t4-c-license-server-usage'; export * from './t4-c-model'; export * from './t4-c-repository'; export * from './t4-c-repository-status'; diff --git a/frontend/src/app/openapi/model/patch-t4-c-instance.ts b/frontend/src/app/openapi/model/patch-t4-c-instance.ts index b393f18da4..bff9569686 100644 --- a/frontend/src/app/openapi/model/patch-t4-c-instance.ts +++ b/frontend/src/app/openapi/model/patch-t4-c-instance.ts @@ -14,12 +14,11 @@ import { Protocol } from './protocol'; export interface PatchT4CInstance { name?: string | null; - license?: string | null; + license_server_id?: number | null; host?: string | null; port?: number | null; cdo_port?: number | null; http_port?: number | null; - usage_api?: string | null; rest_api?: string | null; username?: string | null; password?: string | null; diff --git a/frontend/src/app/openapi/model/patch-t4-c-license-server.ts b/frontend/src/app/openapi/model/patch-t4-c-license-server.ts new file mode 100644 index 0000000000..845c575c5f --- /dev/null +++ b/frontend/src/app/openapi/model/patch-t4-c-license-server.ts @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Capella Collaboration + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + + To generate a new version, run `make openapi` in the root directory of this repository. + */ + + + +export interface PatchT4CLicenseServer { + name?: string | null; + usage_api?: string | null; + license_key?: string | null; +} + diff --git a/frontend/src/app/openapi/model/t4-c-instance.ts b/frontend/src/app/openapi/model/t4-c-instance.ts index 6e64cae50d..ef8d71a864 100644 --- a/frontend/src/app/openapi/model/t4-c-instance.ts +++ b/frontend/src/app/openapi/model/t4-c-instance.ts @@ -14,17 +14,16 @@ import { Protocol } from './protocol'; export interface T4CInstance { - license: string; host: string; port: number; cdo_port: number; http_port: number | null; - usage_api: string; rest_api: string; username: string; protocol: Protocol; name: string; version_id: number; + license_server_id: number; id: number; version: ToolVersion; is_archived: boolean; diff --git a/frontend/src/app/openapi/model/t4-c-license-server-base.ts b/frontend/src/app/openapi/model/t4-c-license-server-base.ts new file mode 100644 index 0000000000..41c08cb8c9 --- /dev/null +++ b/frontend/src/app/openapi/model/t4-c-license-server-base.ts @@ -0,0 +1,19 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Capella Collaboration + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + + To generate a new version, run `make openapi` in the root directory of this repository. + */ + + + +export interface T4CLicenseServerBase { + name: string; + usage_api: string; + license_key: string; +} + diff --git a/frontend/src/app/openapi/model/t4-c-license-server-usage.ts b/frontend/src/app/openapi/model/t4-c-license-server-usage.ts new file mode 100644 index 0000000000..9d0ff6e4e0 --- /dev/null +++ b/frontend/src/app/openapi/model/t4-c-license-server-usage.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Capella Collaboration + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + + To generate a new version, run `make openapi` in the root directory of this repository. + */ + + + +export interface T4CLicenseServerUsage { + free: number; + total: number; +} + diff --git a/frontend/src/app/openapi/model/t4-c-license-server.ts b/frontend/src/app/openapi/model/t4-c-license-server.ts new file mode 100644 index 0000000000..64db1fbbb6 --- /dev/null +++ b/frontend/src/app/openapi/model/t4-c-license-server.ts @@ -0,0 +1,25 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + * + * Capella Collaboration + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * Do not edit the class manually. + + To generate a new version, run `make openapi` in the root directory of this repository. + */ + +import { T4CLicenseServerUsage } from './t4-c-license-server-usage'; +import { T4CInstance } from './t4-c-instance'; + + +export interface T4CLicenseServer { + name: string; + usage_api: string; + license_key: string; + id: number; + license_server_version: string | null; + usage: T4CLicenseServerUsage | null; + instances: Array; +} + diff --git a/frontend/src/app/services/settings/t4c-instance.service.ts b/frontend/src/app/services/settings/t4c-instance.service.ts index c24e6e98bf..e243d6d010 100644 --- a/frontend/src/app/services/settings/t4c-instance.service.ts +++ b/frontend/src/app/services/settings/t4c-instance.service.ts @@ -13,7 +13,7 @@ import { CreateT4CInstance, GetSessionUsageResponse, PatchT4CInstance, - SettingsModelsourcesT4CService, + SettingsModelsourcesT4CInstancesService, T4CInstance, } from 'src/app/openapi'; @@ -21,7 +21,9 @@ import { providedIn: 'root', }) export class T4CInstanceWrapperService { - constructor(private t4cInstanceService: SettingsModelsourcesT4CService) {} + constructor( + private t4cInstanceService: SettingsModelsourcesT4CInstancesService, + ) {} private _t4cInstances = new BehaviorSubject( undefined, @@ -84,10 +86,6 @@ export class T4CInstanceWrapperService { this._t4cInstances.next(undefined); } - getLicenses(instanceId: number): Observable { - return this.t4cInstanceService.fetchT4cLicenses(instanceId); - } - asyncNameValidator(ignoreInstance?: T4CInstance): AsyncValidatorFn { const ignoreInstanceId = ignoreInstance ? ignoreInstance.id : -1; return (control: AbstractControl): Observable => { diff --git a/frontend/src/app/services/settings/t4c-license-server.service.ts b/frontend/src/app/services/settings/t4c-license-server.service.ts new file mode 100644 index 0000000000..60fee766e3 --- /dev/null +++ b/frontend/src/app/services/settings/t4c-license-server.service.ts @@ -0,0 +1,96 @@ +import { Injectable } from '@angular/core'; +import { + AbstractControl, + AsyncValidatorFn, + ValidationErrors, +} from '@angular/forms'; +import { BehaviorSubject, map, Observable, take, tap } from 'rxjs'; +import { + T4CLicenseServer, + SettingsModelsourcesT4CLicenseServersService, +} from '../../openapi'; + +@Injectable({ + providedIn: 'root', +}) +export class T4CLicenseServerWrapperService { + constructor( + private licenseServerService: SettingsModelsourcesT4CLicenseServersService, + ) {} + + private _licenseServers = new BehaviorSubject( + undefined, + ); + public readonly licenseServers$ = this._licenseServers.asObservable(); + + private _licenseServer = new BehaviorSubject( + undefined, + ); + public readonly licenseServer$ = this._licenseServer.asObservable(); + + loadLicenseServers(): void { + this._licenseServers.next(undefined); + this.licenseServerService.getT4cLicenseServers().subscribe({ + next: (servers) => this._licenseServers.next(servers), + error: () => this._licenseServers.next(undefined), + }); + } + + loadLicenseServer(serverId: number): void { + this.licenseServerService.getT4cLicenseServer(serverId).subscribe({ + next: (server) => this._licenseServer.next(server), + error: () => this._licenseServer.next(undefined), + }); + } + + createLicenseServer(server: T4CLicenseServer): Observable { + return this.licenseServerService.createT4cLicenseServer(server).pipe( + tap((server) => { + this._licenseServer.next(server); + this.loadLicenseServers(); + }), + ); + } + + updateLicenseServer( + serverId: number, + server: T4CLicenseServer, + ): Observable { + return this.licenseServerService + .editT4cLicenseServer(serverId, server) + .pipe( + tap((server) => { + this._licenseServer.next(server); + this.loadLicenseServers(); + }), + ); + } + + resetLicenseServer(): void { + this._licenseServer.next(undefined); + } + + reset(): void { + this.resetLicenseServer(); + this._licenseServers.next(undefined); + } + + asyncNameValidator(ignoreInstance?: T4CLicenseServer): AsyncValidatorFn { + const ignoreLicenseServerId = ignoreInstance ? ignoreInstance.id : -1; + return (control: AbstractControl): Observable => { + const licenseServerName = control.value; + return this.licenseServers$.pipe( + take(1), + map((licenseServers) => { + return licenseServers?.find( + (licenseServer) => + licenseServer.name === licenseServerName && + licenseServer.id !== ignoreLicenseServerId, + ) + ? { uniqueName: { value: licenseServerName } } + : null; + }), + ); + }; + } +} diff --git a/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.html b/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.html index a3ee491283..de26619b56 100644 --- a/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.html +++ b/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.html @@ -58,9 +58,20 @@

- License configuration - - @if (form.controls.license.errors?.required) { + License Server + + @for ( + licenseServer of t4cLicenseServerWrapperService.licenseServers$ + | async; + track licenseServer.id + ) { + {{ + licenseServer.name + }} + } + + + @if (form.controls.license_server_id.errors?.required) { The license configuration is required. } @@ -139,26 +150,15 @@

}

-
- - License server API - - @if (form.controls.usage_api.errors?.required) { - The license server API is required. - } @else if (form.controls.usage_api.errors?.pattern) { - The URL should start with "http(s)://" - } - - - Experimental REST API - - @if (form.controls.rest_api.errors?.required) { - The REST server URL is required. - } @else if (form.controls.rest_api.errors?.pattern) { - The URL should start with "http(s)://" - } - -
+ + Experimental REST API + + @if (form.controls.rest_api.errors?.required) { + The REST server URL is required. + } @else if (form.controls.rest_api.errors?.pattern) { + The URL should start with "http(s)://" + } +
Username @@ -238,8 +238,5 @@

- } diff --git a/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.ts b/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.ts index 2a75a41b84..d76a068ba1 100644 --- a/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.ts +++ b/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-instance/edit-t4c-instance.component.ts @@ -33,12 +33,12 @@ import { CreateT4CInstance, PatchT4CInstance, Protocol, - SettingsModelsourcesT4CService, + SettingsModelsourcesT4CInstancesService, ToolVersion, } from 'src/app/openapi'; import { T4CInstanceWrapperService } from 'src/app/services/settings/t4c-instance.service'; import { ToolWrapperService } from 'src/app/settings/core/tools-settings/tool.service'; -import { LicencesComponent } from '../licences/licences.component'; +import { T4CLicenseServerWrapperService } from '../../../../services/settings/t4c-license-server.service'; import { T4CInstanceSettingsComponent } from '../t4c-instance-settings/t4c-instance-settings.component'; @UntilDestroy() @@ -58,7 +58,6 @@ import { T4CInstanceSettingsComponent } from '../t4c-instance-settings/t4c-insta MatHint, MatButton, MatIcon, - LicencesComponent, T4CInstanceSettingsComponent, AsyncPipe, ], @@ -84,7 +83,10 @@ export class EditT4CInstanceComponent implements OnInit, OnDestroy { asyncValidators: this.t4cInstanceWrapperService.asyncNameValidator(), }), version_id: new FormControl(-1, [Validators.required, Validators.min(0)]), - license: new FormControl('', Validators.required), + license_server_id: new FormControl(-1, [ + Validators.required, + Validators.min(0), + ]), protocol: new FormControl('ws', Validators.required), host: new FormControl('', Validators.required), port: new FormControl(2036, [Validators.required, ...this.portValidators]), @@ -93,10 +95,6 @@ export class EditT4CInstanceComponent implements OnInit, OnDestroy { ...this.portValidators, ]), http_port: new FormControl(8080, this.portValidators), - usage_api: new FormControl('', [ - Validators.required, - Validators.pattern(/^https?:\/\//), - ]), rest_api: new FormControl('', [ Validators.required, Validators.pattern(/^https?:\/\//), @@ -111,7 +109,8 @@ export class EditT4CInstanceComponent implements OnInit, OnDestroy { constructor( public t4cInstanceWrapperService: T4CInstanceWrapperService, - private t4cInstanceService: SettingsModelsourcesT4CService, + public t4cLicenseServerWrapperService: T4CLicenseServerWrapperService, + private t4cInstanceService: SettingsModelsourcesT4CInstancesService, private route: ActivatedRoute, private router: Router, private toastService: ToastService, diff --git a/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-license-server/edit-t4c-license-server.component.html b/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-license-server/edit-t4c-license-server.component.html new file mode 100644 index 0000000000..f99a53feb4 --- /dev/null +++ b/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-license-server/edit-t4c-license-server.component.html @@ -0,0 +1,159 @@ +
+
+
+

+ {{ existing ? "Edit License Server" : "Create License Server" }} +

+
+ +
+
+ + Name + + @if (form.controls.name.errors?.required) { + The name is required. + } + +
+
+ + License Server API + + @if (form.controls.usage_api.errors?.required) { + The license server API is required. + } @else if (form.controls.usage_api.errors?.pattern) { + The URL should start with "http(s)://" + } + +
+
+ + License Key + + @if (form.controls.license_key.errors?.required) { + The license key is required. + } + +
+
+ @if (!existing) { + + } @else if (editing) { + +
+ +
+ + + } @else { + + } +
+
+
+ + @if (existing) { +
+

License Server Status

+ + @if ( + t4cLicenseServerWrapperService.licenseServer$ | async; + as licenseServer + ) { + @if (licenseServer.license_server_version) { +
+ tag + License Server Version: + {{ licenseServer.license_server_version }} +
+ } @else { +
+ error + License Server is Unreachable +
+ } + + @if (licenseServer.usage) { +
+ 123 + Available licenses: {{ licenseServer.usage.free }} / + {{ licenseServer.usage.total }} +
+ } @else { +
+ 123 + Available licenses: Unknown +
+ } + + @if (licenseServer.instances.length > 0) { +
+ link + + Used by {{ licenseServer.instances.length }} T4C + {{ + licenseServer.instances.length > 1 ? "instances" : "instance" + }} + +
+ } + } +
+ } +
diff --git a/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-license-server/edit-t4c-license-server.component.ts b/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-license-server/edit-t4c-license-server.component.ts new file mode 100644 index 0000000000..1cd4a8c73f --- /dev/null +++ b/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-license-server/edit-t4c-license-server.component.ts @@ -0,0 +1,206 @@ +import { AsyncPipe, JsonPipe, NgIf } from '@angular/common'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { + FormBuilder, + FormGroup, + FormsModule, + ReactiveFormsModule, + Validators, +} from '@angular/forms'; +import { MatButton } from '@angular/material/button'; +import { MatRipple } from '@angular/material/core'; +import { MatDialog } from '@angular/material/dialog'; +import { MatError, MatFormField, MatLabel } from '@angular/material/form-field'; +import { MatIcon } from '@angular/material/icon'; +import { MatInput } from '@angular/material/input'; +import { MatTooltip } from '@angular/material/tooltip'; +import { ActivatedRoute, Router, RouterLink } from '@angular/router'; +import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; +import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; +import { filter, map, take } from 'rxjs'; +import { BreadcrumbsService } from '../../../../general/breadcrumbs/breadcrumbs.service'; +import { ConfirmationDialogComponent } from '../../../../helpers/confirmation-dialog/confirmation-dialog.component'; +import { MatIconComponent } from '../../../../helpers/mat-icon/mat-icon.component'; +import { ToastService } from '../../../../helpers/toast/toast.service'; +import { SettingsModelsourcesT4CLicenseServersService } from '../../../../openapi'; +import { T4CLicenseServerWrapperService } from '../../../../services/settings/t4c-license-server.service'; + +@UntilDestroy() +@Component({ + selector: 'app-edit-t4c-license-server', + templateUrl: './edit-t4c-license-server.component.html', + standalone: true, + imports: [ + MatFormField, + MatLabel, + FormsModule, + ReactiveFormsModule, + MatButton, + MatInput, + MatError, + MatIcon, + AsyncPipe, + NgIf, + JsonPipe, + NgxSkeletonLoaderModule, + MatIconComponent, + MatRipple, + RouterLink, + MatTooltip, + ], +}) +export class EditT4cLicenseServerComponent implements OnInit, OnDestroy { + form: FormGroup; + existing = false; + editing = false; + licenseServerId: number | null = null; + + constructor( + private fb: FormBuilder, + private route: ActivatedRoute, + private router: Router, + public t4cLicenseServerWrapperService: T4CLicenseServerWrapperService, + private t4cLicenseServerService: SettingsModelsourcesT4CLicenseServersService, + private toastService: ToastService, + private breadcrumbsService: BreadcrumbsService, + private dialog: MatDialog, + ) { + this.form = this.fb.group({ + name: ['', Validators.required], + usage_api: ['', [Validators.required, Validators.pattern('https?://.+')]], + license_key: ['', Validators.required], + }); + } + + ngOnInit(): void { + this.route.params + .pipe( + map((params) => params.licenseServer), + filter(Boolean), + ) + .subscribe((licenseServerId) => { + this.existing = true; + this.form.disable(); + + this.licenseServerId = licenseServerId; + this.t4cLicenseServerWrapperService.loadLicenseServer(licenseServerId); + }); + + this.route.params.subscribe((params) => { + if (params['id']) { + this.licenseServerId = +params['id']; + this.existing = true; + this.loadLicenseServer(this.licenseServerId); + } + }); + + this.t4cLicenseServerWrapperService.licenseServer$ + .pipe(untilDestroyed(this), filter(Boolean)) + .subscribe((licenseServer) => { + this.form.patchValue(licenseServer); + this.form.controls.name.setAsyncValidators( + this.t4cLicenseServerWrapperService.asyncNameValidator(licenseServer), + ); + this.breadcrumbsService.updatePlaceholder({ licenseServer }); + }); + } + + loadLicenseServer(id: number): void { + this.t4cLicenseServerWrapperService.loadLicenseServer(id); + this.t4cLicenseServerWrapperService.licenseServer$.subscribe((server) => { + if (server) { + this.form.patchValue(server); + this.form.disable(); + } + }); + } + + enableEditing(): void { + this.editing = true; + this.form.enable(); + } + + cancelEditing(): void { + this.editing = false; + this.form.disable(); + if (this.licenseServerId) { + this.loadLicenseServer(this.licenseServerId); + } + } + + submit(): void { + if (this.form.valid) { + if (this.existing) { + this.update(); + } else { + this.create(); + } + } + } + + create(): void { + this.t4cLicenseServerWrapperService + .createLicenseServer(this.form.value) + .subscribe((server) => { + this.toastService.showSuccess( + 'License Server created', + `The license server “${server.name}” was created.`, + ); + this.router.navigate(['..', 'license-server', server.id], { + relativeTo: this.route, + }); + }); + } + + update(): void { + if (this.licenseServerId) { + this.t4cLicenseServerWrapperService + .updateLicenseServer(this.licenseServerId, this.form.value) + .subscribe((server) => { + this.editing = false; + this.form.disable(); + this.toastService.showSuccess( + 'License Server updated', + `The license server “${server.name}” was updated.`, + ); + }); + } + } + + deleteLicenseServer(): void { + this.t4cLicenseServerWrapperService.licenseServer$ + .pipe(take(1), untilDestroyed(this)) + .subscribe((licenseServer) => { + if (!licenseServer) { + return; + } + const dialogRef = this.dialog.open(ConfirmationDialogComponent, { + data: { + title: 'Delete TeamForCapella License Server', + text: 'TODO Write this text', + }, + }); + + dialogRef.afterClosed().subscribe((result: boolean) => { + if (result) { + this.t4cLicenseServerService + .deleteT4cLicenseServer(licenseServer.id) + .subscribe({ + next: () => { + this.toastService.showSuccess( + `TeamForCapella License Server removed`, + `The TeamForCapella license server '${licenseServer.name}' has been removed.`, + ); + this.t4cLicenseServerWrapperService.loadLicenseServers(); + this.router.navigateByUrl('/settings/modelsources/t4c'); + }, + }); + } + }); + }); + } + + ngOnDestroy() { + this.t4cLicenseServerWrapperService.resetLicenseServer(); + } +} diff --git a/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-license-server/edit-t4c-license-server.stories.ts b/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-license-server/edit-t4c-license-server.stories.ts new file mode 100644 index 0000000000..ea6999134b --- /dev/null +++ b/frontend/src/app/settings/modelsources/t4c-settings/edit-t4c-license-server/edit-t4c-license-server.stories.ts @@ -0,0 +1,83 @@ +/* + * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import { Meta, moduleMetadata, StoryObj } from '@storybook/angular'; +import { + mockT4CLicenseServer, + mockT4CLicenseServerUnreachable, + mockT4CLicenseServerUnused, + MockT4CLicenseServerWrapperService, +} from '../../../../../storybook/t4c'; +import { T4CLicenseServerWrapperService } from '../../../../services/settings/t4c-license-server.service'; +import { EditT4cLicenseServerComponent } from './edit-t4c-license-server.component'; + +const meta: Meta = { + title: 'Settings Components/Modelsources/T4C/License Server', + component: EditT4cLicenseServerComponent, +}; + +export default meta; +type Story = StoryObj; + +export const AddLicenseServer: Story = { + args: {}, +}; + +export const ExistingLicenseServer: Story = { + args: { + existing: true, + }, + decorators: [ + moduleMetadata({ + providers: [ + { + provide: T4CLicenseServerWrapperService, + useFactory: () => + new MockT4CLicenseServerWrapperService(mockT4CLicenseServer, [ + mockT4CLicenseServer, + ]), + }, + ], + }), + ], +}; + +export const ExistingUnreachableLicenseServer: Story = { + args: { + existing: true, + }, + decorators: [ + moduleMetadata({ + providers: [ + { + provide: T4CLicenseServerWrapperService, + useFactory: () => + new MockT4CLicenseServerWrapperService( + mockT4CLicenseServerUnreachable, + [mockT4CLicenseServerUnreachable], + ), + }, + ], + }), + ], +}; + +export const ExistingUnusedLicenseServer: Story = { + args: { + existing: true, + }, + decorators: [ + moduleMetadata({ + providers: [ + { + provide: T4CLicenseServerWrapperService, + useFactory: () => + new MockT4CLicenseServerWrapperService(mockT4CLicenseServerUnused, [ + mockT4CLicenseServerUnused, + ]), + }, + ], + }), + ], +}; diff --git a/frontend/src/app/settings/modelsources/t4c-settings/licences/licences.component.css b/frontend/src/app/settings/modelsources/t4c-settings/licences/licences.component.css deleted file mode 100644 index 5923b22b52..0000000000 --- a/frontend/src/app/settings/modelsources/t4c-settings/licences/licences.component.css +++ /dev/null @@ -1,7 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors - * SPDX-License-Identifier: Apache-2.0 - */ -.error { - color: var(--error-color); -} diff --git a/frontend/src/app/settings/modelsources/t4c-settings/licences/licences.component.html b/frontend/src/app/settings/modelsources/t4c-settings/licences/licences.component.html deleted file mode 100644 index 76119bf38d..0000000000 --- a/frontend/src/app/settings/modelsources/t4c-settings/licences/licences.component.html +++ /dev/null @@ -1,16 +0,0 @@ - - -
-

License Server Status

-
- Used licences: {{ sessionUsage.total - sessionUsage.free }}/{{ - sessionUsage.total - }} -
-
- {{ errorMessage }} -
-
diff --git a/frontend/src/app/settings/modelsources/t4c-settings/licences/licences.component.ts b/frontend/src/app/settings/modelsources/t4c-settings/licences/licences.component.ts deleted file mode 100644 index b08e60dc8f..0000000000 --- a/frontend/src/app/settings/modelsources/t4c-settings/licences/licences.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright DB InfraGO AG and contributors - * SPDX-License-Identifier: Apache-2.0 - */ -import { NgIf } from '@angular/common'; -import { HttpErrorResponse } from '@angular/common/http'; -import { Component, OnInit, Input } from '@angular/core'; -import { GetSessionUsageResponse, T4CInstance } from 'src/app/openapi'; -import { T4CInstanceWrapperService } from 'src/app/services/settings/t4c-instance.service'; - -@Component({ - selector: 'app-licences', - templateUrl: './licences.component.html', - styleUrls: ['./licences.component.css'], - standalone: true, - imports: [NgIf], -}) -export class LicencesComponent implements OnInit { - @Input() instance!: T4CInstance; - - public errorMessage?: string; - - constructor(private t4cInstanceService: T4CInstanceWrapperService) {} - - ngOnInit(): void { - this.t4cInstanceService.getLicenses(this.instance.id).subscribe({ - next: (res) => { - this.sessionUsage = res; - }, - error: (err: HttpErrorResponse) => { - this.errorMessage = - err.error.detail.reason || - 'Unknown error. Please contact your system administrator.'; - }, - }); - } - - sessionUsage?: GetSessionUsageResponse; -} diff --git a/frontend/src/app/settings/modelsources/t4c-settings/service/t4c-repos/t4c-repo.service.ts b/frontend/src/app/settings/modelsources/t4c-settings/service/t4c-repos/t4c-repo.service.ts index 5e338f7a8e..f1e29a8f82 100644 --- a/frontend/src/app/settings/modelsources/t4c-settings/service/t4c-repos/t4c-repo.service.ts +++ b/frontend/src/app/settings/modelsources/t4c-settings/service/t4c-repos/t4c-repo.service.ts @@ -12,7 +12,7 @@ import { BehaviorSubject, Observable, map, take, tap } from 'rxjs'; import { CreateT4CRepository, ResponseModel, - SettingsModelsourcesT4CService, + SettingsModelsourcesT4CInstancesService, T4CInstance, T4CRepository, T4CRepositoryStatus, @@ -22,7 +22,9 @@ import { providedIn: 'root', }) export class T4CRepositoryWrapperService { - constructor(private t4cInstanceService: SettingsModelsourcesT4CService) {} + constructor( + private t4cInstanceService: SettingsModelsourcesT4CInstancesService, + ) {} private _repositories = new BehaviorSubject< ExtendedT4CRepository[] | undefined diff --git a/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings-wrapper/t4c-settings-wrapper.component.ts b/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings-wrapper/t4c-settings-wrapper.component.ts index c9ca802d51..5b307eb7e8 100644 --- a/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings-wrapper/t4c-settings-wrapper.component.ts +++ b/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings-wrapper/t4c-settings-wrapper.component.ts @@ -5,6 +5,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { RouterOutlet } from '@angular/router'; import { T4CInstanceWrapperService } from 'src/app/services/settings/t4c-instance.service'; +import { T4CLicenseServerWrapperService } from '../../../../services/settings/t4c-license-server.service'; @Component({ selector: 'app-t4c-settings-wrapper', @@ -14,10 +15,14 @@ import { T4CInstanceWrapperService } from 'src/app/services/settings/t4c-instanc imports: [RouterOutlet], }) export class T4CSettingsWrapperComponent implements OnInit, OnDestroy { - constructor(public t4cInstanceService: T4CInstanceWrapperService) {} + constructor( + public t4cInstanceService: T4CInstanceWrapperService, + public t4cLicenseServerService: T4CLicenseServerWrapperService, + ) {} ngOnInit(): void { this.t4cInstanceService.loadInstances(); + this.t4cLicenseServerService.loadLicenseServers(); } ngOnDestroy(): void { diff --git a/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings.component.html b/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings.component.html index d3dfd49efa..b9a2123f9c 100644 --- a/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings.component.html +++ b/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings.component.html @@ -4,7 +4,7 @@ --> + +
+ +
+
+ Add a license server
+
+ add_circle_outline +
+
+
+
+ + @if ((t4cLicenseServerService.licenseServers$ | async) === undefined) { + + } @else { + @for ( + licenseServer of t4cLicenseServerService.licenseServers$ | async; + track licenseServer.id + ) { + +
+
{{ licenseServer.name }}
+ +
+ @if (licenseServer.license_server_version) { + tag + License Server Version: + {{ licenseServer.license_server_version }} + } @else { + error + License Server Unreachable + } +
+ link Host: + {{ licenseServer.usage_api }} +
+
+
+ } + } +
diff --git a/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings.component.ts b/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings.component.ts index c0889dafa6..c18ef2c2b0 100644 --- a/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings.component.ts +++ b/frontend/src/app/settings/modelsources/t4c-settings/t4c-settings.component.ts @@ -10,6 +10,7 @@ import { RouterLink } from '@angular/router'; import { MatCardOverviewSkeletonLoaderComponent } from 'src/app/helpers/skeleton-loaders/mat-card-overview-skeleton-loader/mat-card-overview-skeleton-loader.component'; import { T4CInstanceWrapperService } from 'src/app/services/settings/t4c-instance.service'; import { MatIconComponent } from '../../../helpers/mat-icon/mat-icon.component'; +import { T4CLicenseServerWrapperService } from '../../../services/settings/t4c-license-server.service'; @Component({ selector: 'app-t4c-settings', @@ -27,5 +28,8 @@ import { MatIconComponent } from '../../../helpers/mat-icon/mat-icon.component'; ], }) export class T4CSettingsComponent { - constructor(public t4cInstanceService: T4CInstanceWrapperService) {} + constructor( + public t4cInstanceService: T4CInstanceWrapperService, + public t4cLicenseServerService: T4CLicenseServerWrapperService, + ) {} } diff --git a/frontend/src/storybook/t4c.ts b/frontend/src/storybook/t4c.ts index 11fd4c68ea..d02ca83001 100644 --- a/frontend/src/storybook/t4c.ts +++ b/frontend/src/storybook/t4c.ts @@ -11,6 +11,7 @@ import { BehaviorSubject, Observable, of } from 'rxjs'; import { SimpleT4CModel, T4CInstance, + T4CLicenseServer, T4CModel, T4CRepository, T4CRepositoryStatus, @@ -23,6 +24,7 @@ import { T4CRepositoryWrapperService, } from 'src/app/settings/modelsources/t4c-settings/service/t4c-repos/t4c-repo.service'; import { mockToolVersion } from 'src/storybook/tool'; +import { T4CLicenseServerWrapperService } from '../app/services/settings/t4c-license-server.service'; export const mockTeamForCapellaRepository: Readonly = { project_name: 'project', @@ -33,12 +35,11 @@ export const mockTeamForCapellaRepository: Readonly = { export const mockT4CInstance: Readonly = { id: 1, name: 'test', - license: 'license', host: 'localhost', port: 2036, cdo_port: 12036, http_port: 8080, - usage_api: 'http://localhost:8086', + license_server_id: 1, rest_api: 'http://localhost:8081/api/v1.0', username: 'admin', protocol: 'ws', @@ -94,6 +95,72 @@ export class MockT4CInstanceWrapperService resetT4CInstance(): void {} // eslint-disable-line @typescript-eslint/no-empty-function } +export const mockT4CLicenseServer: Readonly = { + id: 1, + name: 'licenseServer', + license_key: 'licenseKey', + usage_api: 'http://example.com', + usage: { + free: 1, + total: 2, + }, + license_server_version: '1.0.2324234', + instances: [mockT4CInstance], +}; + +export const mockT4CLicenseServerUnreachable: Readonly = { + id: 1, + name: 'licenseServer', + license_key: 'licenseKey', + usage_api: 'http://example.com', + usage: null, + license_server_version: null, + instances: [mockT4CInstance], +}; + +export const mockT4CLicenseServerUnused: Readonly = { + id: 1, + name: 'licenseServer', + license_key: 'licenseKey', + usage_api: 'http://example.com', + usage: { + free: 1, + total: 2, + }, + license_server_version: '1.0.2324234', + instances: [], +}; + +export class MockT4CLicenseServerWrapperService + implements Partial +{ + private _licenseServer = new BehaviorSubject( + undefined, + ); + public readonly licenseServer$ = this._licenseServer.asObservable(); + + private _licenseServers = new BehaviorSubject( + undefined, + ); + public readonly licenseServers$ = this._licenseServers.asObservable(); + + constructor( + licenseServer: T4CLicenseServer, + licenseServers: T4CLicenseServer[], + ) { + this._licenseServer.next(licenseServer); + this._licenseServers.next(licenseServers); + } + + asyncNameValidator(_ignoreServer?: T4CLicenseServer): AsyncValidatorFn { + return (_control: AbstractControl): Observable => { + return of(null); + }; + } + + resetLicenseServer(): void {} // eslint-disable-line @typescript-eslint/no-empty-function +} + export class MockT4CRepositoryWrapperService implements Partial {