Skip to content

Commit

Permalink
feat: Add support for global configuration
Browse files Browse the repository at this point in the history
The global configuration is defined as yml.
We use the `monaco` editor in the frontend.
  • Loading branch information
MoritzWeber0 committed Oct 27, 2023
1 parent ca2d78a commit 294053d
Show file tree
Hide file tree
Showing 26 changed files with 668 additions and 14 deletions.
1 change: 0 additions & 1 deletion backend/capellacollab/alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# SPDX-License-Identifier: Apache-2.0

import os
from logging.config import fileConfig

os.environ["ALEMBIC_CONTEXT"] = "1"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
# SPDX-License-Identifier: Apache-2.0

"""Add configuration table
Revision ID: 86ab7d4d1684
Revises: ac0e6e0f77ee
Create Date: 2023-10-27 14:54:40.452599
"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = "86ab7d4d1684"
down_revision = "ac0e6e0f77ee"
branch_labels = None
depends_on = None


def upgrade():
op.create_table(
"configuration",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("name", sa.String(), nullable=False),
sa.Column(
"configuration",
postgresql.JSONB(astext_type=sa.Text()),
nullable=False,
),
sa.PrimaryKeyConstraint("id"),
)
op.create_index(
op.f("ix_configuration_id"), "configuration", ["id"], unique=True
)
op.create_index(
op.f("ix_configuration_name"), "configuration", ["name"], unique=True
)
5 changes: 4 additions & 1 deletion backend/capellacollab/core/database/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@


class Base(orm.DeclarativeBase):
type_annotation_map = {dict[str, str]: postgresql.JSONB}
type_annotation_map = {
dict[str, str]: postgresql.JSONB,
dict[str, t.Any]: postgresql.JSONB,
}


### SQL MODELS ARE IMPORTED HERE ###
Expand Down
2 changes: 2 additions & 0 deletions backend/capellacollab/core/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import capellacollab.projects.toolmodels.restrictions.models
import capellacollab.projects.users.models
import capellacollab.sessions.models
import capellacollab.settings.configuration.models
import capellacollab.settings.integrations.purevariants.models
import capellacollab.settings.modelsources.git.models
import capellacollab.settings.modelsources.t4c.models
Expand All @@ -22,3 +23,4 @@
import capellacollab.users.events.models
import capellacollab.users.models
import capellacollab.users.tokens
import capellacollab.users.tokens.models
1 change: 0 additions & 1 deletion backend/capellacollab/projects/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@

from . import crud, models

logger = logging.getLogger(__name__)
router = fastapi.APIRouter(
dependencies=[
fastapi.Depends(
Expand Down
3 changes: 3 additions & 0 deletions backend/capellacollab/settings/configuration/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
# SPDX-License-Identifier: Apache-2.0

55 changes: 55 additions & 0 deletions backend/capellacollab/settings/configuration/crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
# SPDX-License-Identifier: Apache-2.0

import sqlalchemy as sa
from sqlalchemy import orm

from . import models


def get_configuration_by_name(
db: orm.Session, name: str
) -> None | models.DatabaseConfiguration:
return db.execute(
sa.select(models.DatabaseConfiguration).where(
models.DatabaseConfiguration.name == name
)
).scalar_one_or_none()


def get_pydantic_configuration_by_name(
db: orm.Session, pydantic_model: models.ConfigurationBase
):
return pydantic_model.model_validate(
get_configuration_by_name(db, pydantic_model._name)
)


def create_configuration(
db: orm.Session,
name: str,
configuration: dict[str, str],
) -> models.DatabaseConfiguration:
db_configuration = models.DatabaseConfiguration(
name=name, configuration=configuration
)
db.add(db_configuration)
db.commit()
return db_configuration


def update_configuration(
db: orm.Session,
db_configuration: models.DatabaseConfiguration,
configuration: dict[str, str],
) -> models.DatabaseConfiguration:
db_configuration.configuration = configuration
db.commit()
return db_configuration


def delete_configuration(
db: orm.Session, db_configuration: models.DatabaseConfiguration
):
db.delete(db_configuration)
db.commit()
3 changes: 3 additions & 0 deletions backend/capellacollab/settings/configuration/injectables.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
# SPDX-License-Identifier: Apache-2.0

80 changes: 80 additions & 0 deletions backend/capellacollab/settings/configuration/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
# SPDX-License-Identifier: Apache-2.0

import typing as t

import pydantic
from sqlalchemy import orm

from capellacollab.core import database


class DatabaseConfiguration(database.Base):
__tablename__ = "configuration"

id: orm.Mapped[int] = orm.mapped_column(
unique=True, primary_key=True, index=True
)

name: orm.Mapped[str] = orm.mapped_column(unique=True, index=True)
configuration: orm.Mapped[dict[str, t.Any]]


class CPUCostConfiguration(pydantic.BaseModel):
model_config = pydantic.ConfigDict(extra="forbid")

reserved: float | None = pydantic.Field(
default=None,
description="CPU reserved costs per mCore / hour",
)
burst: float | None = pydantic.Field(
default=None, description="CPU burst costs per mCore / hour"
)


class MemoryCostConfiguration(pydantic.BaseModel):
model_config = pydantic.ConfigDict(extra="forbid")

reserved: float | None = pydantic.Field(
default=None, description="Memory reserved costs per MiB / hour"
)
burst: float | None = pydantic.Field(
default=None, description="Memory burst costs per MiB / hour"
)


class SessionsCostConfiguration(pydantic.BaseModel):
model_config = pydantic.ConfigDict(extra="forbid")

cpu: CPUCostConfiguration = pydantic.Field(default=CPUCostConfiguration())
memory: MemoryCostConfiguration = pydantic.Field(
default=MemoryCostConfiguration()
)
storage: float | None = pydantic.Field(
default=None, description="Storage costs per GiB / month"
)
currency: str = pydantic.Field(default="€", min_length=1, max_length=5)


class SessionsConfiguration(pydantic.BaseModel):
model_config = pydantic.ConfigDict(extra="forbid")

costs: SessionsCostConfiguration = pydantic.Field(
default=SessionsCostConfiguration()
)


class ConfigurationBase(pydantic.BaseModel):
model_config = pydantic.ConfigDict(extra="forbid")

_name: str


class GlobalConfiguration(ConfigurationBase):
"""Global application configuration."""

_name = "global"

sessions: SessionsConfiguration = pydantic.Field(
default=SessionsConfiguration()
)
45 changes: 45 additions & 0 deletions backend/capellacollab/settings/configuration/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# SPDX-FileCopyrightText: Copyright DB Netz AG and the capella-collab-manager contributors
# SPDX-License-Identifier: Apache-2.0

import fastapi
from sqlalchemy import orm

from capellacollab.core import database
from capellacollab.core.authentication import injectables as auth_injectables
from capellacollab.users import models as users_models

from . import crud, models

router = fastapi.APIRouter(
dependencies=[
fastapi.Depends(
auth_injectables.RoleVerification(
required_role=users_models.Role.ADMIN
)
)
]
)


@router.get("/global", response_model=models.GlobalConfiguration)
def get_global_configuration(
db: orm.Session = fastapi.Depends(database.get_db),
):
configuration = crud.get_configuration_by_name(db, "global")
if configuration:
return configuration.configuration
return models.GlobalConfiguration().model_validate({})


@router.put("/global", response_model=models.GlobalConfiguration)
def update_global_configuration(
body: models.GlobalConfiguration,
db: orm.Session = fastapi.Depends(database.get_db),
):
configuration = crud.get_configuration_by_name(db, "global")

if configuration:
return crud.update_configuration(db, configuration, body.model_dump())
return crud.create_configuration(
db, name="global", configuration=body.model_dump()
)
2 changes: 2 additions & 0 deletions backend/capellacollab/settings/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import fastapi

from capellacollab.core.authentication import injectables as auth_injectables
from capellacollab.settings.configuration import routes as configuration_routes
from capellacollab.settings.integrations.purevariants import (
routes as purevariants_routes,
)
Expand All @@ -26,3 +27,4 @@
router.include_router(
purevariants_routes.router, prefix="/integrations/pure-variants"
)
router.include_router(configuration_routes.router, prefix="/configurations")
16 changes: 13 additions & 3 deletions frontend/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,22 @@
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {
"path": "./webpack.config.ts",
"mergeRules": {
"externals": "replace"
}
},
"outputPath": "dist/capellacollab",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": ["src/favicon.ico", "src/assets"],
"styles": [
"node_modules/monaco-editor/min/vs/editor/editor.main.css",
"src/custom-theme.scss",
"src/styles.css",
"node_modules/ngx-toastr/toastr.css"
Expand Down Expand Up @@ -87,7 +94,7 @@
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"builder": "@angular-builders/custom-webpack:dev-server",
"configurations": {
"production": {
"browserTarget": "capellacollab:build:production"
Expand Down Expand Up @@ -115,7 +122,10 @@
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.css"],
"styles": [
"node_modules/monaco-editor/min/vs/editor/editor.main.css",
"src/styles.css"
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
Expand Down
Loading

0 comments on commit 294053d

Please sign in to comment.