diff --git a/packages/postgres-database/src/simcore_postgres_database/migration/versions/3810966d1534_adding_pricing_and_harware_info_to_comp_.py b/packages/postgres-database/src/simcore_postgres_database/migration/versions/3810966d1534_adding_pricing_and_harware_info_to_comp_.py
new file mode 100644
index 00000000000..b15524ce9de
--- /dev/null
+++ b/packages/postgres-database/src/simcore_postgres_database/migration/versions/3810966d1534_adding_pricing_and_harware_info_to_comp_.py
@@ -0,0 +1,40 @@
+"""adding pricing and harware info to comp_tasks
+
+Revision ID: 3810966d1534
+Revises: 5c62b190e124
+Create Date: 2023-10-17 14:35:21.032940+00:00
+
+"""
+import sqlalchemy as sa
+from alembic import op
+from sqlalchemy.dialects import postgresql
+
+# revision identifiers, used by Alembic.
+revision = "3810966d1534"
+down_revision = "5c62b190e124"
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column(
+ "comp_tasks",
+ sa.Column(
+ "pricing_info", postgresql.JSONB(astext_type=sa.Text()), nullable=True
+ ),
+ )
+ op.add_column(
+ "comp_tasks",
+ sa.Column(
+ "hardware_info", postgresql.JSONB(astext_type=sa.Text()), nullable=True
+ ),
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column("comp_tasks", "hardware_info")
+ op.drop_column("comp_tasks", "pricing_info")
+ # ### end Alembic commands ###
diff --git a/packages/postgres-database/src/simcore_postgres_database/models/comp_tasks.py b/packages/postgres-database/src/simcore_postgres_database/models/comp_tasks.py
index a6602455111..60bfc3f95c3 100644
--- a/packages/postgres-database/src/simcore_postgres_database/models/comp_tasks.py
+++ b/packages/postgres-database/src/simcore_postgres_database/models/comp_tasks.py
@@ -87,6 +87,19 @@ class NodeClass(enum.Enum):
),
column_created_datetime(timezone=True),
column_modified_datetime(timezone=True),
+ sa.Column(
+ "pricing_info",
+ postgresql.JSONB,
+ nullable=True,
+ doc="Billing information of this task",
+ ),
+ sa.Column(
+ "hardware_info",
+ postgresql.JSONB,
+ nullable=True,
+ doc="Harware information of this task",
+ ),
+ # ------
sa.UniqueConstraint("project_id", "node_id", name="project_node_uniqueness"),
)
diff --git a/packages/postgres-database/src/simcore_postgres_database/utils_projects_nodes.py b/packages/postgres-database/src/simcore_postgres_database/utils_projects_nodes.py
index 448c7be5e60..b1b9a6022f0 100644
--- a/packages/postgres-database/src/simcore_postgres_database/utils_projects_nodes.py
+++ b/packages/postgres-database/src/simcore_postgres_database/utils_projects_nodes.py
@@ -5,6 +5,10 @@
import sqlalchemy
from aiopg.sa.connection import SAConnection
+from simcore_postgres_database.models.projects_node_to_pricing_unit import (
+ projects_node_to_pricing_unit,
+)
+from sqlalchemy.dialects.postgresql import insert as pg_insert
from .errors import ForeignKeyViolation, UniqueViolation
from .models.projects_nodes import projects_nodes
@@ -193,3 +197,67 @@ async def delete(self, connection: SAConnection, *, node_id: uuid.UUID) -> None:
& (projects_nodes.c.node_id == f"{node_id}")
)
await connection.execute(delete_stmt)
+
+ async def get_project_node_pricing_unit_id(
+ self, connection: SAConnection, *, node_uuid: uuid.UUID
+ ) -> tuple | None:
+ """get a pricing unit that is connected to the project node or None if there is non connected
+
+ NOTE: Do not use this in an asyncio.gather call as this will fail!
+ """
+ result = await connection.execute(
+ sqlalchemy.select(
+ projects_node_to_pricing_unit.c.pricing_plan_id,
+ projects_node_to_pricing_unit.c.pricing_unit_id,
+ )
+ .select_from(
+ projects_nodes.join(
+ projects_node_to_pricing_unit,
+ projects_nodes.c.project_node_id
+ == projects_node_to_pricing_unit.c.project_node_id,
+ )
+ )
+ .where(
+ (projects_nodes.c.project_uuid == f"{self.project_uuid}")
+ & (projects_nodes.c.node_id == f"{node_uuid}")
+ )
+ )
+ row = await result.fetchone()
+ if row:
+ return (row[0], row[1])
+ return None
+
+ async def connect_pricing_unit_to_project_node(
+ self,
+ connection: SAConnection,
+ *,
+ node_uuid: uuid.UUID,
+ pricing_plan_id: int,
+ pricing_unit_id: int,
+ ) -> None:
+ result = await connection.scalar(
+ sqlalchemy.select(projects_nodes.c.project_node_id).where(
+ (projects_nodes.c.project_uuid == f"{self.project_uuid}")
+ & (projects_nodes.c.node_id == f"{node_uuid}")
+ )
+ )
+ project_node_id = int(result) if result else 0
+
+ insert_stmt = pg_insert(projects_node_to_pricing_unit).values(
+ project_node_id=project_node_id,
+ pricing_plan_id=pricing_plan_id,
+ pricing_unit_id=pricing_unit_id,
+ created=sqlalchemy.func.now(),
+ modified=sqlalchemy.func.now(),
+ )
+ on_update_stmt = insert_stmt.on_conflict_do_update(
+ index_elements=[
+ projects_node_to_pricing_unit.c.project_node_id,
+ ],
+ set_={
+ "pricing_plan_id": insert_stmt.excluded.pricing_plan_id,
+ "pricing_unit_id": insert_stmt.excluded.pricing_unit_id,
+ "modified": sqlalchemy.func.now(),
+ },
+ )
+ await connection.execute(on_update_stmt)
diff --git a/services/director-v2/src/simcore_service_director_v2/api/dependencies/rut_client.py b/services/director-v2/src/simcore_service_director_v2/api/dependencies/rut_client.py
new file mode 100644
index 00000000000..70bc94b7ad2
--- /dev/null
+++ b/services/director-v2/src/simcore_service_director_v2/api/dependencies/rut_client.py
@@ -0,0 +1,7 @@
+from fastapi import Request
+
+from ...modules.resource_usage_tracker_client import ResourceUsageTrackerClient
+
+
+def get_rut_client(request: Request) -> ResourceUsageTrackerClient:
+ return ResourceUsageTrackerClient.get_from_state(request.app)
diff --git a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
index ffe2b0cf395..bf4251ff9e6 100644
--- a/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
+++ b/services/director-v2/src/simcore_service_director_v2/api/routes/computations.py
@@ -30,7 +30,7 @@
from models_library.clusters import DEFAULT_CLUSTER_ID
from models_library.projects import ProjectAtDB, ProjectID
from models_library.projects_nodes_io import NodeID
-from models_library.services import ServiceKey, ServiceKeyVersion, ServiceVersion
+from models_library.services import ServiceKeyVersion
from models_library.users import UserID
from models_library.utils.fastapi_encoders import jsonable_encoder
from pydantic import AnyHttpUrl, parse_obj_as
@@ -47,6 +47,7 @@
ClusterAccessForbiddenError,
ClusterNotFoundError,
ComputationalRunNotFoundError,
+ PricingPlanUnitNotFoundError,
ProjectNotFoundError,
SchedulerError,
)
@@ -62,7 +63,7 @@
from ...modules.db.repositories.projects import ProjectsRepository
from ...modules.db.repositories.users import UsersRepository
from ...modules.director_v0 import DirectorV0Client
-from ...modules.resource_usage_client import ResourceUsageApi
+from ...modules.resource_usage_tracker_client import ResourceUsageTrackerClient
from ...utils.computations import (
find_deprecated_tasks,
get_pipeline_state_from_task_states,
@@ -82,6 +83,7 @@
from ..dependencies.catalog import get_catalog_client
from ..dependencies.database import get_repository
from ..dependencies.director_v0 import get_director_v0_client
+from ..dependencies.rut_client import get_rut_client
from ..dependencies.scheduler import get_scheduler
from .computations_tasks import analyze_pipeline
@@ -122,6 +124,7 @@ async def create_computation( # noqa: C901, PLR0912
scheduler: Annotated[BaseCompScheduler, Depends(get_scheduler)],
catalog_client: Annotated[CatalogClient, Depends(get_catalog_client)],
users_repo: Annotated[UsersRepository, Depends(get_repository(UsersRepository))],
+ rut_client: Annotated[ResourceUsageTrackerClient, Depends(get_rut_client)],
) -> ComputationGet:
log.debug(
"User %s is creating a new computation from project %s",
@@ -205,6 +208,8 @@ async def create_computation( # noqa: C901, PLR0912
published_nodes=min_computation_nodes if computation.start_pipeline else [],
user_id=computation.user_id,
product_name=computation.product_name,
+ rut_client=rut_client,
+ is_wallet=bool(computation.wallet_info),
)
if computation.start_pipeline:
@@ -225,25 +230,10 @@ async def create_computation( # noqa: C901, PLR0912
# Billing info
wallet_id = None
wallet_name = None
- pricing_plan_id = None
- pricing_unit_id = None
- pricing_unit_cost_id = None
if computation.wallet_info:
wallet_id = computation.wallet_info.wallet_id
wallet_name = computation.wallet_info.wallet_name
- resource_usage_api = ResourceUsageApi.get_from_state(request.app)
- # NOTE: MD/SAN -> add real service version/key and store in DB, issue: https://github.com/ITISFoundation/osparc-issues/issues/1131
- (
- pricing_plan_id,
- pricing_unit_id,
- pricing_unit_cost_id,
- ) = await resource_usage_api.get_default_service_pricing_plan_and_pricing_unit(
- computation.product_name,
- ServiceKey("simcore/services/comp/itis/sleeper"),
- ServiceVersion("2.1.6"),
- )
-
await scheduler.run_new_pipeline(
computation.user_id,
computation.project_id,
@@ -259,9 +249,6 @@ async def create_computation( # noqa: C901, PLR0912
user_email=await users_repo.get_user_email(computation.user_id),
wallet_id=wallet_id,
wallet_name=wallet_name,
- pricing_plan_id=pricing_plan_id,
- pricing_unit_id=pricing_unit_id,
- pricing_unit_cost_id=pricing_unit_cost_id,
),
use_on_demand_clusters=computation.use_on_demand_clusters,
)
@@ -317,6 +304,8 @@ async def create_computation( # noqa: C901, PLR0912
raise HTTPException(
status_code=status.HTTP_406_NOT_ACCEPTABLE, detail=f"{e}"
) from e
+ except PricingPlanUnitNotFoundError as e:
+ raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"{e}") from e
@router.get(
diff --git a/services/director-v2/src/simcore_service_director_v2/core/application.py b/services/director-v2/src/simcore_service_director_v2/core/application.py
index eb3813206bd..9ad93646615 100644
--- a/services/director-v2/src/simcore_service_director_v2/core/application.py
+++ b/services/director-v2/src/simcore_service_director_v2/core/application.py
@@ -28,7 +28,7 @@
osparc_variables_substitutions,
rabbitmq,
remote_debug,
- resource_usage_client,
+ resource_usage_tracker_client,
storage,
)
from .errors import (
@@ -176,7 +176,7 @@ def init_app(settings: AppSettings | None = None) -> FastAPI:
comp_scheduler.setup(app)
if settings.DIRECTOR_V2_RESOURCE_USAGE_TRACKER:
- resource_usage_client.setup(app)
+ resource_usage_tracker_client.setup(app)
node_rights.setup(app)
diff --git a/services/director-v2/src/simcore_service_director_v2/core/errors.py b/services/director-v2/src/simcore_service_director_v2/core/errors.py
index 0554e06d6c2..8cb99519416 100644
--- a/services/director-v2/src/simcore_service_director_v2/core/errors.py
+++ b/services/director-v2/src/simcore_service_director_v2/core/errors.py
@@ -92,6 +92,13 @@ def __init__(self, project_id: ProjectID):
super().__init__(f"project {project_id} not found")
+class PricingPlanUnitNotFoundError(DirectorException):
+ """Pricing plan unit not found error"""
+
+ def __init__(self, msg: str):
+ super().__init__(msg)
+
+
class PipelineNotFoundError(DirectorException):
"""Pipeline not found error"""
diff --git a/services/director-v2/src/simcore_service_director_v2/models/comp_runs.py b/services/director-v2/src/simcore_service_director_v2/models/comp_runs.py
index e6f54e66df4..03265d56744 100644
--- a/services/director-v2/src/simcore_service_director_v2/models/comp_runs.py
+++ b/services/director-v2/src/simcore_service_director_v2/models/comp_runs.py
@@ -21,9 +21,6 @@ class RunMetadataDict(TypedDict, total=False):
user_email: str
wallet_id: int | None
wallet_name: str | None
- pricing_plan_id: int | None
- pricing_unit_id: int | None
- pricing_unit_cost_id: int | None
class CompRunsAtDB(BaseModel):
diff --git a/services/director-v2/src/simcore_service_director_v2/models/comp_tasks.py b/services/director-v2/src/simcore_service_director_v2/models/comp_tasks.py
index abaa009e12f..1e6eef80701 100644
--- a/services/director-v2/src/simcore_service_director_v2/models/comp_tasks.py
+++ b/services/director-v2/src/simcore_service_director_v2/models/comp_tasks.py
@@ -138,6 +138,9 @@ class CompTaskAtDB(BaseModel):
)
created: datetime.datetime
modified: datetime.datetime
+ # Additional information about price and hardware (ex. AWS EC2 instance type)
+ pricing_info: dict | None
+ hardware_info: dict | None
@validator("state", pre=True)
@classmethod
@@ -214,6 +217,12 @@ class Config:
"last_heartbeat": None,
"created": "2022-05-20 13:28:31.139+00",
"modified": "2023-06-23 15:58:32.833081+00",
+ "pricing_info": {
+ "pricing_plan_id": 1,
+ "pricing_unit_id": 1,
+ "pricing_unit_cost_id": 1,
+ },
+ "hardware_info": {"aws_ec2_instance": ["aws-specific-instance"]},
}
for image_example in Image.Config.schema_extra["examples"]
]
diff --git a/services/director-v2/src/simcore_service_director_v2/modules/comp_scheduler/base_scheduler.py b/services/director-v2/src/simcore_service_director_v2/modules/comp_scheduler/base_scheduler.py
index effa63cd6b4..1f9776499ad 100644
--- a/services/director-v2/src/simcore_service_director_v2/modules/comp_scheduler/base_scheduler.py
+++ b/services/director-v2/src/simcore_service_director_v2/modules/comp_scheduler/base_scheduler.py
@@ -380,9 +380,15 @@ async def _process_started_tasks(
),
wallet_id=run_metadata.get("wallet_id"),
wallet_name=run_metadata.get("wallet_name"),
- pricing_plan_id=run_metadata.get("pricing_plan_id"),
- pricing_unit_id=run_metadata.get("pricing_unit_id"),
- pricing_unit_cost_id=run_metadata.get("pricing_unit_cost_id"),
+ pricing_plan_id=t.pricing_info.get("pricing_plan_id")
+ if t.pricing_info
+ else None,
+ pricing_unit_id=t.pricing_info.get("pricing_unit_id")
+ if t.pricing_info
+ else None,
+ pricing_unit_cost_id=t.pricing_info.get("pricing_unit_cost_id")
+ if t.pricing_info
+ else None,
product_name=run_metadata.get(
"product_name", UNDEFINED_STR_METADATA
),
diff --git a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py
index 9d44f4dcc3f..1612ec0f816 100644
--- a/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py
+++ b/services/director-v2/src/simcore_service_director_v2/modules/db/repositories/comp_tasks.py
@@ -17,6 +17,7 @@
from models_library.projects_nodes import Node
from models_library.projects_nodes_io import NodeID
from models_library.projects_state import RunningState
+from models_library.resource_tracker import HardwareInfo, PricingInfo
from models_library.service_settings_labels import (
SimcoreServiceLabels,
SimcoreServiceSettingsLabel,
@@ -29,6 +30,9 @@
from servicelib.utils import logged_gather
from simcore_postgres_database.utils_projects_nodes import ProjectNodesRepo
from simcore_service_director_v2.core.errors import ComputationalTaskNotFoundError
+from simcore_service_director_v2.modules.resource_usage_tracker_client import (
+ ResourceUsageTrackerClient,
+)
from simcore_service_director_v2.utils.comp_scheduler import COMPLETED_STATES
from sqlalchemy import literal_column
from sqlalchemy.dialects.postgresql import insert
@@ -134,18 +138,17 @@ async def _generate_task_image(
catalog_client: CatalogClient,
connection: aiopg.sa.connection.SAConnection,
user_id: UserID,
- project_uuid: ProjectID,
node_id: NodeID,
node: Node,
node_extras: ServiceExtras | None,
node_labels: SimcoreServiceLabels | None,
+ project_nodes_repo: ProjectNodesRepo,
) -> Image:
# aggregates node_details and node_extras into Image
data: dict[str, Any] = {
"name": node.key,
"tag": node.version,
}
- project_nodes_repo = ProjectNodesRepo(project_uuid=project_uuid)
project_node = await project_nodes_repo.get(connection, node_id=node_id)
node_resources = parse_obj_as(ServiceResourcesDict, project_node.required_resources)
if not node_resources:
@@ -171,6 +174,8 @@ async def _generate_tasks_list_from_project(
user_id: UserID,
product_name: str,
connection: aiopg.sa.connection.SAConnection,
+ rut_client: ResourceUsageTrackerClient,
+ is_wallet: bool,
) -> list[CompTaskAtDB]:
list_comp_tasks = []
@@ -205,15 +210,16 @@ async def _generate_tasks_list_from_project(
if not node_details:
continue
+ project_nodes_repo = ProjectNodesRepo(project_uuid=project.uuid)
image = await _generate_task_image(
catalog_client=catalog_client,
connection=connection,
user_id=user_id,
- project_uuid=project.uuid,
node_id=NodeID(node_id),
node=node,
node_extras=node_extras,
node_labels=node_labels,
+ project_nodes_repo=project_nodes_repo,
)
assert node.state is not None # nosec
@@ -227,6 +233,44 @@ async def _generate_tasks_list_from_project(
):
task_state = RunningState.PUBLISHED
+ pricing_info = None
+ hardware_info = None
+ if is_wallet:
+ output = await project_nodes_repo.get_project_node_pricing_unit_id(
+ connection, node_uuid=NodeID(node_id)
+ )
+ if output:
+ pricing_plan_id, pricing_unit_id = output
+ pricing_unit_get = await rut_client.get_pricing_unit(
+ product_name, pricing_plan_id, pricing_unit_id
+ )
+ pricing_unit_cost_id = pricing_unit_get.current_cost_per_unit_id
+ aws_ec2_instances = pricing_unit_get.specific_info["aws_ec2_instances"]
+ else:
+ (
+ pricing_plan_id,
+ pricing_unit_id,
+ pricing_unit_cost_id,
+ aws_ec2_instances,
+ ) = await rut_client.get_default_pricing_and_hardware_info(
+ product_name,
+ node.key,
+ node.version,
+ )
+ await project_nodes_repo.connect_pricing_unit_to_project_node(
+ connection,
+ node_uuid=NodeID(node_id),
+ pricing_plan_id=pricing_plan_id,
+ pricing_unit_id=pricing_unit_id,
+ )
+
+ pricing_info = PricingInfo(
+ pricing_plan_id=pricing_plan_id,
+ pricing_unit_id=pricing_unit_id,
+ pricing_unit_cost_id=pricing_unit_cost_id,
+ )
+ hardware_info = HardwareInfo(aws_ec2_instances=aws_ec2_instances)
+
task_db = CompTaskAtDB(
project_id=project.uuid,
node_id=NodeID(node_id),
@@ -246,6 +290,8 @@ async def _generate_tasks_list_from_project(
last_heartbeat=None,
created=arrow.utcnow().datetime,
modified=arrow.utcnow().datetime,
+ pricing_info=pricing_info.dict() if pricing_info else None,
+ hardware_info=hardware_info.dict() if hardware_info else None,
)
list_comp_tasks.append(task_db)
@@ -314,6 +360,8 @@ async def upsert_tasks_from_project(
published_nodes: list[NodeID],
user_id: UserID,
product_name: str,
+ rut_client: ResourceUsageTrackerClient,
+ is_wallet: bool,
) -> list[CompTaskAtDB]:
# NOTE: really do an upsert here because of issue https://github.com/ITISFoundation/osparc-simcore/issues/2125
async with self.db_engine.acquire() as conn:
@@ -327,6 +375,8 @@ async def upsert_tasks_from_project(
user_id,
product_name,
conn,
+ rut_client,
+ is_wallet,
)
# get current tasks
result = await conn.execute(
diff --git a/services/director-v2/src/simcore_service_director_v2/modules/resource_usage_client.py b/services/director-v2/src/simcore_service_director_v2/modules/resource_usage_tracker_client.py
similarity index 58%
rename from services/director-v2/src/simcore_service_director_v2/modules/resource_usage_client.py
rename to services/director-v2/src/simcore_service_director_v2/modules/resource_usage_tracker_client.py
index a7177ec610d..87edee57fc2 100644
--- a/services/director-v2/src/simcore_service_director_v2/modules/resource_usage_client.py
+++ b/services/director-v2/src/simcore_service_director_v2/modules/resource_usage_tracker_client.py
@@ -8,17 +8,23 @@
import httpx
from fastapi import FastAPI
+from models_library.api_schemas_resource_usage_tracker.credit_transactions import (
+ WalletTotalCredits,
+)
from models_library.api_schemas_resource_usage_tracker.pricing_plans import (
+ PricingUnitGet,
ServicePricingPlanGet,
)
from models_library.products import ProductName
from models_library.resource_tracker import (
+ PricingAndHardwareInfoTuple,
PricingPlanId,
- PricingUnitCostId,
PricingUnitId,
)
from models_library.services import ServiceKey, ServiceVersion
+from models_library.wallets import WalletID
from pydantic import parse_obj_as
+from simcore_service_director_v2.core.errors import PricingPlanUnitNotFoundError
from ..core.settings import AppSettings
@@ -26,12 +32,12 @@
@dataclass
-class ResourceUsageApi:
+class ResourceUsageTrackerClient:
client: httpx.AsyncClient
exit_stack: contextlib.AsyncExitStack
@classmethod
- def create(cls, settings: AppSettings) -> "ResourceUsageApi":
+ def create(cls, settings: AppSettings) -> "ResourceUsageTrackerClient":
client = httpx.AsyncClient(
base_url=settings.DIRECTOR_V2_RESOURCE_USAGE_TRACKER.api_base_url,
)
@@ -84,34 +90,63 @@ async def get_default_service_pricing_plan(
response.raise_for_status()
return parse_obj_as(ServicePricingPlanGet, response.json())
- async def get_default_service_pricing_plan_and_pricing_unit(
+ async def get_default_pricing_and_hardware_info(
self,
product_name: ProductName,
service_key: ServiceKey,
service_version: ServiceVersion,
- ) -> tuple[PricingPlanId, PricingUnitId, PricingUnitCostId]:
- pricing_plan = await self.get_default_service_pricing_plan(
- product_name, service_key, service_version
+ ) -> PricingAndHardwareInfoTuple:
+ service_pricing_plan_get = await self.get_default_service_pricing_plan(
+ product_name=product_name,
+ service_key=service_key,
+ service_version=service_version,
)
- if pricing_plan:
- default_pricing_plan = pricing_plan
- default_pricing_unit = pricing_plan.pricing_units[0]
- return (
- default_pricing_plan.pricing_plan_id,
- default_pricing_unit.pricing_unit_id,
- default_pricing_unit.current_cost_per_unit_id,
- )
- raise ValueError(
- f"No default pricing plan provided for requested service key: {service_key} version: {service_version} product: {product_name}"
+ for unit in service_pricing_plan_get.pricing_units:
+ if unit.default:
+ return PricingAndHardwareInfoTuple(
+ service_pricing_plan_get.pricing_plan_id,
+ unit.pricing_unit_id,
+ unit.current_cost_per_unit_id,
+ unit.specific_info["aws_ec2_instances"],
+ )
+ raise PricingPlanUnitNotFoundError(
+ "Default pricing plan and unit does not exist"
+ )
+
+ async def get_pricing_unit(
+ self,
+ product_name: ProductName,
+ pricing_plan_id: PricingPlanId,
+ pricing_unit_id: PricingUnitId,
+ ) -> PricingUnitGet:
+ response = await self.client.get(
+ f"/pricing-plans/{pricing_plan_id}/pricing-units/{pricing_unit_id}",
+ params={
+ "product_name": product_name,
+ },
+ )
+ response.raise_for_status()
+ return parse_obj_as(PricingUnitGet, response.json())
+
+ async def get_wallet_credits(
+ self,
+ product_name: ProductName,
+ wallet_id: WalletID,
+ ) -> WalletTotalCredits:
+ response = await self.client.post(
+ "/credit-transactions/credits:sum",
+ params={"product_name": product_name, "wallet_id": wallet_id},
)
+ response.raise_for_status()
+ return parse_obj_as(WalletTotalCredits, response.json())
#
# app
#
@classmethod
- def get_from_state(cls, app: FastAPI) -> "ResourceUsageApi":
- return cast("ResourceUsageApi", app.state.resource_usage_api)
+ def get_from_state(cls, app: FastAPI) -> "ResourceUsageTrackerClient":
+ return cast("ResourceUsageTrackerClient", app.state.resource_usage_api)
@classmethod
def setup(cls, app: FastAPI):
@@ -139,4 +174,4 @@ async def on_shutdown():
def setup(app: FastAPI):
assert app.state # nosec
- ResourceUsageApi.setup(app)
+ ResourceUsageTrackerClient.setup(app)
diff --git a/services/static-webserver/client/source/class/osparc/About.js b/services/static-webserver/client/source/class/osparc/About.js
index 91ba0d9d8e7..33adf8a1984 100644
--- a/services/static-webserver/client/source/class/osparc/About.js
+++ b/services/static-webserver/client/source/class/osparc/About.js
@@ -28,7 +28,6 @@ qx.Class.define("osparc.About", {
contentPadding: this.self().PADDING,
showMaximize: false,
showMinimize: false,
- resizable: false,
centerOnAppear: true,
clickAwayClose: true,
modal: true
diff --git a/services/static-webserver/client/source/class/osparc/auth/ui/RegistrationView.js b/services/static-webserver/client/source/class/osparc/auth/ui/RegistrationView.js
index 3aec74b16a8..e1cb079a2a7 100644
--- a/services/static-webserver/client/source/class/osparc/auth/ui/RegistrationView.js
+++ b/services/static-webserver/client/source/class/osparc/auth/ui/RegistrationView.js
@@ -94,7 +94,7 @@ qx.Class.define("osparc.auth.ui.RegistrationView", {
// buttons
const grp = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
- const submitBtn = this.__submitBtn = new qx.ui.form.Button(this.tr("Submit")).set({
+ const submitBtn = this.__submitBtn = new osparc.ui.form.FetchButton(this.tr("Submit")).set({
center: true,
appearance: "strong-button"
});
@@ -118,7 +118,7 @@ qx.Class.define("osparc.auth.ui.RegistrationView", {
password: password1.getValue(),
confirm: password2.getValue(),
invitation: invitationToken ? invitationToken : ""
- });
+ }, submitBtn);
}
}
}, this);
@@ -128,7 +128,8 @@ qx.Class.define("osparc.auth.ui.RegistrationView", {
this.add(grp);
},
- __submit: function(userData) {
+ __submit: function(userData, submitButton) {
+ submitButton.setFetching(true);
osparc.auth.Manager.getInstance().register(userData)
.then(log => {
this.fireDataEvent("done", log.message);
@@ -137,7 +138,8 @@ qx.Class.define("osparc.auth.ui.RegistrationView", {
.catch(err => {
const msg = err.message || this.tr("Cannot register user");
osparc.FlashMessenger.getInstance().logAs(msg, "ERROR");
- });
+ })
+ .finally(() => submitButton.setFetching(false));
},
_onAppear: function() {
diff --git a/services/static-webserver/client/source/class/osparc/dashboard/ResourceMoreOptions.js b/services/static-webserver/client/source/class/osparc/dashboard/ResourceMoreOptions.js
index 74764e6f455..18f680615dd 100644
--- a/services/static-webserver/client/source/class/osparc/dashboard/ResourceMoreOptions.js
+++ b/services/static-webserver/client/source/class/osparc/dashboard/ResourceMoreOptions.js
@@ -104,28 +104,26 @@ qx.Class.define("osparc.dashboard.ResourceMoreOptions", {
__servicesUpdatePage: null,
__addToolbar: function() {
- const toolbar = this.__toolbar = new qx.ui.container.Composite(new qx.ui.layout.HBox(40));
+ const toolbar = this.__toolbar = new qx.ui.container.Composite(new qx.ui.layout.HBox(20));
const resourceData = this.__resourceData;
const title = new qx.ui.basic.Label(resourceData.name).set({
font: "text-16",
alignY: "middle",
- maxWidth: this.self().WIDTH-100,
+ allowGrowX: true,
rich: true,
wrap: true
});
- toolbar.add(title);
+ toolbar.add(title, {
+ flex: 1
+ });
if (osparc.utils.Resources.isService(resourceData)) {
const serviceVersionSelector = this.__createServiceVersionSelector();
toolbar.add(serviceVersionSelector);
}
- toolbar.add(new qx.ui.core.Spacer(), {
- flex: 1
- });
-
const openButton = new qx.ui.form.Button(this.tr("Open")).set({
appearance: "strong-button",
font: "text-14",
diff --git a/services/static-webserver/client/source/class/osparc/data/Resources.js b/services/static-webserver/client/source/class/osparc/data/Resources.js
index 43fd7aada03..6e0dbaff536 100644
--- a/services/static-webserver/client/source/class/osparc/data/Resources.js
+++ b/services/static-webserver/client/source/class/osparc/data/Resources.js
@@ -693,6 +693,15 @@ qx.Class.define("osparc.data.Resources", {
}
}
},
+ "productMetadata": {
+ useCache: true,
+ endpoints: {
+ get: {
+ method: "GET",
+ url: statics.API + "/products/{productName}"
+ }
+ }
+ },
"invitations": {
endpoints: {
post: {
@@ -747,7 +756,7 @@ qx.Class.define("osparc.data.Resources", {
/*
* AUTO RECHARGE
*/
- "auto-recharge": {
+ "autoRecharge": {
useCache: false,
endpoints: {
get: {
@@ -1064,11 +1073,16 @@ qx.Class.define("osparc.data.Resources", {
let message = null;
let status = null;
if (e.getData().error) {
- const logs = e.getData().error.logs || null;
+ const errorData = e.getData().error;
+ const logs = errorData.logs || null;
if (logs && logs.length) {
message = logs[0].message;
}
- status = e.getData().error.status;
+ const errors = errorData.errors || [];
+ if (message === null && errors && errors.length) {
+ message = errors[0].message;
+ }
+ status = errorData.status;
} else {
const req = e.getRequest();
message = req.getResponse();
diff --git a/services/static-webserver/client/source/class/osparc/data/model/Wallet.js b/services/static-webserver/client/source/class/osparc/data/model/Wallet.js
index 5ce8ca1dff7..c5e5ddc143f 100644
--- a/services/static-webserver/client/source/class/osparc/data/model/Wallet.js
+++ b/services/static-webserver/client/source/class/osparc/data/model/Wallet.js
@@ -96,7 +96,8 @@ qx.Class.define("osparc.data.model.Wallet", {
autoRecharge: {
check: "Object",
init: null,
- nullable: true
+ nullable: true,
+ event: "changeAutoRecharge"
},
preferredWallet: {
diff --git a/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js b/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js
index c26e42a4739..db4631d89d9 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/WorkbenchView.js
@@ -607,20 +607,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", {
const nodeId = data.nodeId;
const msg = data.msg;
const logLevel = ("level" in data) ? data["level"] : "INFO";
- switch (logLevel) {
- case "DEBUG":
- this.__loggerView.debug(nodeId, msg);
- break;
- case "WARNING":
- this.__loggerView.warn(nodeId, msg);
- break;
- case "ERROR":
- this.__loggerView.error(nodeId, msg);
- break;
- default:
- this.__loggerView.info(nodeId, msg);
- break;
- }
+ this.__logsToLogger(nodeId, [msg], logLevel);
}, this);
workbench.addListener("fileRequested", () => {
@@ -633,6 +620,29 @@ qx.Class.define("osparc.desktop.WorkbenchView", {
this.__workbenchUIConnected = true;
},
+ __logsToLogger: function(nodeId, logs, logLevel) {
+ // the node logger is mainly used in App Mode
+ const nodeLogger = this.__getNodeLogger(nodeId);
+ switch (logLevel) {
+ case "DEBUG":
+ this.__loggerView.debugs(nodeId, logs);
+ nodeLogger.debugs(nodeId, logs);
+ break;
+ case "WARNING":
+ this.__loggerView.warns(nodeId, logs);
+ nodeLogger.warns(nodeId, logs);
+ break;
+ case "ERROR":
+ this.__loggerView.errors(nodeId, logs);
+ nodeLogger.errors(nodeId, logs);
+ break;
+ default:
+ this.__loggerView.infos(nodeId, logs);
+ nodeLogger.infos(nodeId, logs);
+ break;
+ }
+ },
+
__attachSocketEventHandlers: function() {
// Listen to socket
const socket = osparc.wrapper.WebSocket.getInstance();
@@ -650,24 +660,7 @@ qx.Class.define("osparc.desktop.WorkbenchView", {
const messages = data["messages"];
const logLevelMap = osparc.widget.logger.LoggerView.LOG_LEVEL_MAP;
const logLevel = ("log_level" in data) ? logLevelMap[data["log_level"]] : "INFO";
- switch (logLevel) {
- case "DEBUG":
- this.__loggerView.debugs(nodeId, messages);
- break;
- case "WARNING":
- this.__loggerView.warns(nodeId, messages);
- break;
- case "ERROR":
- this.__loggerView.errors(nodeId, messages);
- break;
- default:
- this.__loggerView.infos(nodeId, messages);
- break;
- }
- const nodeLogger = this.__getNodeLogger(nodeId);
- if (nodeLogger) {
- nodeLogger.infos(nodeId, messages);
- }
+ this.__logsToLogger(nodeId, messages, logLevel);
}, this);
}
socket.emit(slotName);
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Activity.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Activity.js
new file mode 100644
index 00000000000..c84dd469100
--- /dev/null
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Activity.js
@@ -0,0 +1,218 @@
+/* ************************************************************************
+
+ osparc - the simcore frontend
+
+ https://osparc.io
+
+ Copyright:
+ 2023 IT'IS Foundation, https://itis.swiss
+
+ License:
+ MIT: https://opensource.org/licenses/MIT
+
+ Authors:
+ * Odei Maiz (odeimaiz)
+
+************************************************************************ */
+
+qx.Class.define("osparc.desktop.credits.Activity", {
+ extend: qx.ui.core.Widget,
+
+ construct: function() {
+ this.base(arguments);
+
+ this._setLayout(new qx.ui.layout.VBox(15));
+
+ this.getChildControl("activity-intro");
+
+ const walletSelectorLayout = this.getChildControl("wallet-selector-layout");
+ const walletSelector = walletSelectorLayout.getChildren()[1];
+
+ const loadingImage = this.getChildControl("loading-image");
+ loadingImage.show();
+ const table = this.getChildControl("activity-table");
+ table.exclude();
+
+ this.__fetchData();
+ walletSelector.addListener("changeSelection", () => {
+ this.__prevUsageRequestParams = null;
+ this.__nextUsageRequestParams = null;
+ this.__fetchData();
+ });
+ },
+
+ statics: {
+ ITEMS_PER_PAGE: 15
+ },
+
+ members: {
+ __prevUsageRequestParams: null,
+ __nextUsageRequestParams: null,
+
+ _createChildControlImpl: function(id) {
+ let control;
+ switch (id) {
+ case "activity-intro":
+ control = new qx.ui.basic.Label().set({
+ value: this.tr("Transactions and Usage both together. Go to their specific sections to get more details"),
+ font: "text-14"
+ });
+ this._add(control);
+ break;
+ case "wallet-selector-layout":
+ control = osparc.desktop.credits.Utils.createWalletSelectorLayout("read");
+ this._add(control);
+ break;
+ case "loading-image":
+ control = new qx.ui.basic.Image().set({
+ source: "@FontAwesome5Solid/circle-notch/64",
+ alignX: "center",
+ alignY: "middle"
+ });
+ control.getContentElement().addClass("rotate");
+ this._add(control);
+ break;
+ case "activity-table":
+ control = new osparc.desktop.credits.ActivityTable().set({
+ height: (this.self().ITEMS_PER_PAGE*20 + 40)
+ });
+ this._add(control);
+ break;
+ case "page-buttons":
+ control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5)).set({
+ allowGrowX: true,
+ alignX: "center",
+ alignY: "middle"
+ });
+ this._add(control);
+ break;
+ case "prev-page-button": {
+ control = new qx.ui.form.Button().set({
+ icon: "@FontAwesome5Solid/chevron-left/12",
+ allowGrowX: false
+ });
+ control.addListener("execute", () => this.__fetchData(this.__getPrevRequest()));
+ const pageButtons = this.getChildControl("page-buttons");
+ pageButtons.add(control);
+ break;
+ }
+ case "current-page-label": {
+ control = new qx.ui.basic.Label().set({
+ font: "text-14",
+ textAlign: "center",
+ alignY: "middle"
+ });
+ const pageButtons = this.getChildControl("page-buttons");
+ pageButtons.add(control);
+ break;
+ }
+ case "next-page-button": {
+ control = new qx.ui.form.Button().set({
+ icon: "@FontAwesome5Solid/chevron-right/12",
+ allowGrowX: false
+ });
+ control.addListener("execute", () => this.__fetchData(this.__getNextUsageRequest()));
+ const pageButtons = this.getChildControl("page-buttons");
+ pageButtons.add(control);
+ break;
+ }
+ }
+ return control || this.base(arguments, id);
+ },
+
+ __fetchData: function(request) {
+ const loadingImage = this.getChildControl("loading-image");
+ loadingImage.show();
+ const table = this.getChildControl("activity-table");
+ table.exclude();
+
+ if (request === undefined) {
+ request = this.__getNextUsageRequest();
+ }
+ Promise.all([
+ request,
+ osparc.data.Resources.fetch("payments", "get")
+ ])
+ .then(responses => {
+ const usagesResp = responses[0];
+ const usages = usagesResp["data"];
+ const transactions = responses[1]["data"];
+ const activities1 = osparc.desktop.credits.ActivityTable.usagesToActivities(usages);
+ // Filter out some transactions
+ const walletId = this.__getSelectedWalletId();
+ const filteredTransactions = transactions.filter(transaction => transaction["completedStatus"] !== "FAILED" && transaction["walletId"] === walletId);
+ const activities2 = osparc.desktop.credits.ActivityTable.transactionsToActivities(filteredTransactions);
+ const activities = activities1.concat(activities2);
+ activities.sort((a, b) => new Date(b["date"]).getTime() - new Date(a["date"]).getTime());
+ this.__setData(activities);
+
+ this.__prevUsageRequestParams = usagesResp["_links"]["prev"];
+ this.__nextUsageRequestParams = usagesResp["_links"]["next"];
+ this.__evaluatePageButtons(usagesResp);
+ })
+ .finally(() => {
+ loadingImage.exclude();
+ table.show();
+ });
+ },
+
+ __getPrevRequest: function() {
+ const params = {
+ url: {
+ offset: this.self().ITEMS_PER_PAGE,
+ limit: this.self().ITEMS_PER_PAGE
+ }
+ };
+ if (this.__prevUsageRequestParams) {
+ params.url.offset = osparc.utils.Utils.getParamFromURL(this.__prevUsageRequestParams, "offset");
+ params.url.limit = osparc.utils.Utils.getParamFromURL(this.__prevUsageRequestParams, "limit");
+ }
+ return this.__getUsageCommonRequest(params);
+ },
+
+ __getNextUsageRequest: function() {
+ const params = {
+ url: {
+ offset: 0,
+ limit: this.self().ITEMS_PER_PAGE
+ }
+ };
+ if (this.__nextUsageRequestParams) {
+ params.url.offset = osparc.utils.Utils.getParamFromURL(this.__nextUsageRequestParams, "offset");
+ params.url.limit = osparc.utils.Utils.getParamFromURL(this.__nextUsageRequestParams, "limit");
+ }
+ return this.__getUsageCommonRequest(params);
+ },
+
+ __getSelectedWalletId: function() {
+ const walletSelector = this.getChildControl("wallet-selector-layout").getChildren()[1];
+ const walletSelection = walletSelector.getSelection();
+ return walletSelection && walletSelection.length ? walletSelection[0].walletId : null;
+ },
+
+ __getUsageCommonRequest: function(params) {
+ const options = {
+ resolveWResponse: true
+ };
+
+ const walletId = this.__getSelectedWalletId();
+ if (walletId) {
+ params.url["walletId"] = walletId.toString();
+ return osparc.data.Resources.fetch("resourceUsagePerWallet", "getPage", params, undefined, options);
+ }
+ return null;
+ },
+
+ __setData: function(data) {
+ const table = this.getChildControl("activity-table");
+ table.addData(data);
+ },
+
+ __evaluatePageButtons:function(resp) {
+ // this is not correct because we are populating the table with two different resources
+ this.getChildControl("prev-page-button").setEnabled(Boolean(this.__prevUsageRequestParams));
+ this.getChildControl("current-page-label").setValue(((resp["_meta"]["offset"]/this.self().ITEMS_PER_PAGE)+1).toString());
+ this.getChildControl("next-page-button").setEnabled(Boolean(this.__nextUsageRequestParams));
+ }
+ }
+});
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/ActivityTable.js b/services/static-webserver/client/source/class/osparc/desktop/credits/ActivityTable.js
new file mode 100644
index 00000000000..7152c2fed45
--- /dev/null
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/ActivityTable.js
@@ -0,0 +1,142 @@
+/* ************************************************************************
+
+ osparc - the simcore frontend
+
+ https://osparc.io
+
+ Copyright:
+ 2023 IT'IS Foundation, https://itis.swiss
+
+ License:
+ MIT: https://opensource.org/licenses/MIT
+
+ Authors:
+ * Odei Maiz (odeimaiz)
+
+************************************************************************ */
+
+/**
+ * Both, usage and transactions, mixed
+ */
+
+qx.Class.define("osparc.desktop.credits.ActivityTable", {
+ extend: osparc.ui.table.Table,
+
+ construct: function() {
+ const model = new qx.ui.table.model.Simple();
+ const cols = this.self().COLUMNS;
+ const colNames = Object.values(cols).map(col => col.title);
+ model.setColumns(colNames);
+
+ this.base(arguments, model, {
+ tableColumnModel: obj => new qx.ui.table.columnmodel.Resize(obj),
+ statusBarVisible: false
+ });
+ this.makeItLoose();
+
+ const columnModel = this.getTableColumnModel();
+ columnModel.getBehavior().setWidth(this.self().COLUMNS.wallet.pos, 100);
+ columnModel.getBehavior().setWidth(this.self().COLUMNS.invoice.pos, 60);
+
+ if (!osparc.desktop.credits.Utils.areWalletsEnabled()) {
+ columnModel.setColumnVisible(this.self().COLUMNS.wallet.pos, false);
+ columnModel.setColumnVisible(this.self().COLUMNS.invoice.pos, false);
+ }
+ },
+
+ statics: {
+ COLUMNS: {
+ date: {
+ pos: 0,
+ title: qx.locale.Manager.tr("Date")
+ },
+ type: {
+ pos: 1,
+ title: qx.locale.Manager.tr("Type")
+ },
+ title: {
+ pos: 2,
+ title: qx.locale.Manager.tr("Title")
+ },
+ credits: {
+ pos: 3,
+ title: qx.locale.Manager.tr("Credits")
+ },
+ wallet: {
+ pos: 4,
+ title: qx.locale.Manager.tr("Credit Account")
+ },
+ invoice: {
+ pos: 5,
+ title: qx.locale.Manager.tr("Invoice")
+ }
+ },
+
+ createPdfIconWithLink: function(link) {
+ return ``;
+ },
+
+ usagesToActivities: function(usages) {
+ const activities = [];
+ usages.forEach(usage => {
+ const activity = {
+ date: usage["started_at"],
+ type: "Usage",
+ title: usage["project_name"],
+ credits: usage["credit_cost"],
+ walletId: usage["wallet_id"],
+ invoice: ""
+ };
+ activities.push(activity);
+ });
+ return activities;
+ },
+
+ transactionsToActivities: function(transactions) {
+ const activities = [];
+ transactions.forEach(transaction => {
+ const activity = {
+ date: transaction["createdAt"],
+ type: "Transaction",
+ title: transaction["comment"] ? transaction["comment"] : "",
+ credits: transaction["osparcCredits"].toFixed(2),
+ walletId: transaction["walletId"],
+ invoice: transaction["invoice"]
+ };
+ activities.push(activity);
+ });
+ return activities;
+ },
+
+ respDataToTableRow: function(data) {
+ const cols = this.COLUMNS;
+ const newData = [];
+ newData[cols["date"].pos] = osparc.utils.Utils.formatDateAndTime(new Date(data["date"]));
+ newData[cols["type"].pos] = data["type"];
+ newData[cols["credits"].pos] = data["credits"] ? data["credits"] : "-";
+ const found = osparc.desktop.credits.Utils.getWallet(data["walletId"]);
+ newData[cols["wallet"].pos] = found ? found.getName() : data["walletId"];
+ const invoiceUrl = data["invoice"];
+ newData[cols["invoice"].pos] = invoiceUrl? this.createPdfIconWithLink(invoiceUrl) : "";
+ return newData;
+ },
+
+ respDataToTableData: function(datas) {
+ const newDatas = [];
+ if (datas) {
+ for (const data of datas) {
+ const newData = this.respDataToTableRow(data);
+ newDatas.push(newData);
+ }
+ }
+ return newDatas;
+ }
+ },
+
+ members: {
+ addData: function(datas) {
+ const newDatas = this.self().respDataToTableData(datas);
+ this.setData(newDatas);
+ }
+ }
+});
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/AutoRecharge.js b/services/static-webserver/client/source/class/osparc/desktop/credits/AutoRecharge.js
index 11dfa09958e..0d706631997 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/AutoRecharge.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/AutoRecharge.js
@@ -126,7 +126,7 @@ qx.Class.define("osparc.desktop.credits.AutoRecharge", {
walletId: wallet.getWalletId()
}
};
- osparc.data.Resources.fetch("auto-recharge", "get", params)
+ osparc.data.Resources.fetch("autoRecharge", "get", params)
.then(arData => this.__populateForm(arData))
.catch(err => console.error(err.message));
},
@@ -223,6 +223,25 @@ qx.Class.define("osparc.desktop.credits.AutoRecharge", {
};
},
+ __updateAutoRecharge: function(enabled, fetchButton, successfulMsg) {
+ const wallet = this.getWallet();
+ fetchButton.setFetching(true);
+ const params = {
+ url: {
+ walletId: wallet.getWalletId()
+ },
+ data: this.__getFieldsData()
+ };
+ params.data["enabled"] = enabled;
+ osparc.data.Resources.fetch("autoRecharge", "put", params)
+ .then(arData => {
+ this.__populateForm(arData);
+ wallet.setAutoRecharge(arData);
+ osparc.FlashMessenger.getInstance().logAs(successfulMsg, "INFO");
+ })
+ .finally(() => fetchButton.setFetching(false));
+ },
+
__getEnableAutoRechargeButton: function() {
const enableAutoRechargeBtn = new osparc.ui.form.FetchButton().set({
label: this.tr("Enable"),
@@ -231,23 +250,8 @@ qx.Class.define("osparc.desktop.credits.AutoRecharge", {
maxWidth: 200,
center: true
});
- enableAutoRechargeBtn.addListener("execute", () => {
- enableAutoRechargeBtn.setFetching(true);
- const params = {
- url: {
- walletId: this.getWallet().getWalletId()
- },
- data: this.__getFieldsData()
- };
- params.data["enabled"] = true;
- osparc.data.Resources.fetch("auto-recharge", "put", params)
- .then(arData => {
- this.__populateForm(arData);
- const msg = this.tr("Auto recharge was successfully enabled");
- osparc.FlashMessenger.getInstance().logAs(msg, "INFO");
- })
- .finally(() => enableAutoRechargeBtn.setFetching(false));
- });
+ const successfulMsg = this.tr("Auto recharge was successfully enabled");
+ enableAutoRechargeBtn.addListener("execute", () => this.__updateAutoRecharge(true, enableAutoRechargeBtn, successfulMsg));
return enableAutoRechargeBtn;
},
@@ -259,23 +263,8 @@ qx.Class.define("osparc.desktop.credits.AutoRecharge", {
maxWidth: 200,
center: true
});
- saveAutoRechargeBtn.addListener("execute", () => {
- saveAutoRechargeBtn.setFetching(true);
- const params = {
- url: {
- walletId: this.getWallet().getWalletId()
- },
- data: this.__getFieldsData()
- };
- params.data["enabled"] = true;
- osparc.data.Resources.fetch("auto-recharge", "put", params)
- .then(arData => {
- this.__populateForm(arData);
- const msg = this.tr("Changes on the Auto recharge were successfully saved");
- osparc.FlashMessenger.getInstance().logAs(msg, "INFO");
- })
- .finally(() => saveAutoRechargeBtn.setFetching(false));
- });
+ const successfulMsg = this.tr("Changes on the Auto recharge were successfully saved");
+ saveAutoRechargeBtn.addListener("execute", () => this.__updateAutoRecharge(true, saveAutoRechargeBtn, successfulMsg));
return saveAutoRechargeBtn;
},
@@ -287,23 +276,8 @@ qx.Class.define("osparc.desktop.credits.AutoRecharge", {
maxWidth: 200,
center: true
});
- disableAutoRechargeBtn.addListener("execute", () => {
- disableAutoRechargeBtn.setFetching(true);
- const params = {
- url: {
- walletId: this.getWallet().getWalletId()
- },
- data: this.__getFieldsData()
- };
- params.data["enabled"] = false;
- osparc.data.Resources.fetch("auto-recharge", "put", params)
- .then(arData => {
- this.__populateForm(arData);
- const msg = this.tr("Auto recharge was successfully disabled");
- osparc.FlashMessenger.getInstance().logAs(msg, "INFO");
- })
- .finally(() => disableAutoRechargeBtn.setFetching(false));
- });
+ const successfulMsg = this.tr("Auto recharge was successfully disabled");
+ disableAutoRechargeBtn.addListener("execute", () => this.__updateAutoRecharge(false, disableAutoRechargeBtn, successfulMsg));
return disableAutoRechargeBtn;
}
}
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCredits.js b/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCredits.js
index fcd3b40c827..fb2bff4fd56 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCredits.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/BuyCredits.js
@@ -21,12 +21,34 @@ qx.Class.define("osparc.desktop.credits.BuyCredits", {
construct: function() {
this.base(arguments);
- const grid = new qx.ui.layout.Grid(80, 50);
- grid.setColumnMaxWidth(0, 400);
- grid.setColumnMaxWidth(1, 400);
- this._setLayout(grid);
+ this._setLayout(new qx.ui.layout.VBox(15));
- this.__buildLayout();
+ this.getChildControl("credits-intro");
+
+ const walletSelectorLayout = this.getChildControl("wallet-selector-layout");
+ const walletSelector = walletSelectorLayout.getChildren()[1];
+ const walletSelection = walletSelector.getSelection();
+ const selectedWalletId = walletSelection && walletSelection.length ? walletSelection[0].walletId : null;
+ const walletFound = osparc.desktop.credits.Utils.getWallet(selectedWalletId);
+ if (walletFound) {
+ this.setWallet(walletFound);
+ }
+
+ this.getChildControl("credits-left-view");
+
+ this.__populateLayout();
+
+ const wallets = osparc.store.Store.getInstance().getWallets();
+ walletSelector.addListener("changeSelection", e => {
+ const selection = e.getData();
+ const walletId = selection[0].walletId;
+ const found = wallets.find(wallet => wallet.getWalletId() === parseInt(walletId));
+ if (found) {
+ this.setWallet(found);
+ } else {
+ this.setWallet(null);
+ }
+ });
},
properties: {
@@ -47,106 +69,102 @@ qx.Class.define("osparc.desktop.credits.BuyCredits", {
_createChildControlImpl: function(id) {
let control;
switch (id) {
- case "wallet-layout":
- control = new qx.ui.container.Composite(new qx.ui.layout.VBox(10));
- this._add(control, {
- row: 0,
- column: 0
+ case "credits-intro":
+ control = this.__getCreditsExplanation();
+ this._add(control);
+ break;
+ case "wallet-selector-layout":
+ control = osparc.desktop.credits.Utils.createWalletSelectorLayout("read");
+ this._add(control);
+ break;
+ case "credits-left-view":
+ control = this.__getCreditsLeftView();
+ this._add(control);
+ break;
+ case "wallet-billing-settings":
+ control = new qx.ui.container.Composite(new qx.ui.layout.HBox(30));
+ this._add(control);
+ break;
+ case "payment-mode-layout":
+ control = new qx.ui.container.Composite(new qx.ui.layout.HBox(5));
+ this.getChildControl("wallet-billing-settings").add(control);
+ break;
+ case "payment-mode-title":
+ control = new qx.ui.basic.Label(this.tr("Payment mode")).set({
+ font: "text-14"
});
+ this.getChildControl("payment-mode-layout").add(control);
break;
- case "explanation-layout":
- control = new qx.ui.container.Composite(new qx.ui.layout.VBox(10));
- this._add(control, {
- row: 0,
- column: 1
+ case "payment-mode": {
+ this.getChildControl("payment-mode-title");
+ control = new qx.ui.form.SelectBox().set({
+ allowGrowX: false,
+ allowGrowY: false
});
+ const autoItem = new qx.ui.form.ListItem(this.tr("Automatic"), null, "automatic");
+ control.add(autoItem);
+ const manualItem = new qx.ui.form.ListItem(this.tr("Manual"), null, "manual");
+ control.add(manualItem);
+ this.getChildControl("payment-mode-layout").add(control);
break;
+ }
case "one-time-payment":
- control = new osparc.desktop.credits.OneTimePayment();
+ control = new osparc.desktop.credits.OneTimePayment().set({
+ maxWidth: 300
+ });
this.bind("wallet", control, "wallet");
control.addListener("transactionCompleted", () => this.fireEvent("transactionCompleted"));
- this._add(control, {
- row: 1,
- column: 0
- });
+ this.getChildControl("wallet-billing-settings").add(control);
break;
case "auto-recharge":
- control = new osparc.desktop.credits.AutoRecharge();
- this.bind("wallet", control, "wallet");
- this._add(control, {
- row: 1,
- column: 1
+ control = new osparc.desktop.credits.AutoRecharge().set({
+ maxWidth: 300
});
- break;
- case "wallet-info": {
- control = new qx.ui.container.Composite(new qx.ui.layout.VBox(5));
- const label = new qx.ui.basic.Label().set({
- value: this.tr("Credit Account:"),
- font: "text-14"
- });
- control.add(label);
- this.getChildControl("wallet-layout").add(control);
- break;
- }
- case "wallet-selector":
- control = this.__getWalletSelector();
- this.getChildControl("wallet-info").add(control);
- break;
- case "credits-left-view":
- control = this.__getCreditsLeftView();
- this.getChildControl("wallet-info").add(control);
- break;
- case "credits-explanation":
- control = this.__getCreditsExplanation();
- this.getChildControl("explanation-layout").add(control);
+ this.bind("wallet", control, "wallet");
+ this.getChildControl("wallet-billing-settings").add(control);
break;
}
return control || this.base(arguments, id);
},
- __applyWallet: function(wallet) {
+ __populateLayout: function() {
+ const wallet = this.getWallet();
+ console.log("wallet", wallet);
if (wallet) {
- const walletSelector = this.getChildControl("wallet-selector");
- walletSelector.getSelectables().forEach(selectable => {
- if (selectable.walletId === wallet.getWalletId()) {
- walletSelector.setSelection([selectable]);
+ const paymentMode = this.getChildControl("payment-mode");
+ const autoRecharge = this.getChildControl("auto-recharge");
+ const oneTime = this.getChildControl("one-time-payment");
+ autoRecharge.show();
+ oneTime.exclude();
+ paymentMode.addListener("changeSelection", e => {
+ const model = e.getData()[0].getModel();
+ if (model === "manual") {
+ autoRecharge.exclude();
+ oneTime.show();
+ } else {
+ autoRecharge.show();
+ oneTime.exclude();
}
});
}
},
- __buildLayout: function() {
- this.getChildControl("wallet-selector");
- this.getChildControl("credits-left-view");
- this.getChildControl("one-time-payment");
- this.getChildControl("credits-explanation");
- this.getChildControl("one-time-payment");
- this.getChildControl("auto-recharge");
- },
-
- __getWalletSelector: function() {
- const walletSelector = osparc.desktop.credits.Utils.createWalletSelector("write", false, false);
-
- walletSelector.addListener("changeSelection", e => {
- const selection = e.getData();
- if (selection.length) {
- const store = osparc.store.Store.getInstance();
- const found = store.getWallets().find(wallet => wallet.getWalletId() === parseInt(selection[0].walletId));
- if (found) {
- this.setWallet(found);
+ __applyWallet: function(wallet) {
+ if (wallet) {
+ const walletSelectorLayout = this.getChildControl("wallet-selector-layout");
+ const walletSelector = walletSelectorLayout.getChildren()[1];
+ walletSelector.getSelectables().forEach(selectable => {
+ if (selectable.walletId === wallet.getWalletId()) {
+ walletSelector.setSelection([selectable]);
}
- }
- });
-
- if (walletSelector.getSelectables().length) {
- walletSelector.setSelection([walletSelector.getSelectables()[0]]);
+ });
}
-
- return walletSelector;
},
__getCreditsLeftView: function() {
- const creditsIndicator = new osparc.desktop.credits.CreditsIndicator();
+ const creditsIndicator = new osparc.desktop.credits.CreditsIndicator().set({
+ maxWidth: 200
+ });
creditsIndicator.getChildControl("credits-label").set({
alignX: "left"
});
@@ -157,21 +175,13 @@ qx.Class.define("osparc.desktop.credits.BuyCredits", {
__getCreditsExplanation: function() {
const layout = new qx.ui.container.Composite(new qx.ui.layout.VBox(20));
- const label1 = new qx.ui.basic.Label().set({
+ const label = new qx.ui.basic.Label().set({
value: "Explain here what a Credit is and what one can run/do with them.",
- font: "text-16",
- rich: true,
- wrap: true
- });
- layout.add(label1);
-
- const label2 = new qx.ui.basic.Label().set({
- value: "If something goes wrong you won't be charged",
- font: "text-16",
+ font: "text-14",
rich: true,
wrap: true
});
- layout.add(label2);
+ layout.add(label);
return layout;
}
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/OneTimePayment.js b/services/static-webserver/client/source/class/osparc/desktop/credits/OneTimePayment.js
index 7625e38bdb2..6a50644c7ed 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/OneTimePayment.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/OneTimePayment.js
@@ -307,10 +307,7 @@ qx.Class.define("osparc.desktop.credits.OneTimePayment", {
const modal = true;
const useNativeModalDialog = false; // this allow using the Blocker
- const blocker = qx.bom.Window.getBlocker();
- blocker.setBlockerColor("#FFF");
- blocker.setBlockerOpacity(0.6);
- let pgWindow = qx.bom.Window.open(
+ const pgWindow = osparc.desktop.credits.PaymentGatewayWindow.popUp(
url,
"pgWindow",
options,
@@ -318,27 +315,6 @@ qx.Class.define("osparc.desktop.credits.OneTimePayment", {
useNativeModalDialog
);
- // enhance the blocker
- const blockerDomEl = blocker.getBlockerElement();
- blockerDomEl.style.cursor = "pointer";
-
- // text on blocker
- const label = document.createElement("h1");
- label.innerHTML = "Don’t see the secure Payment Window?
Click here to complete your purchase";
- label.style.position = "fixed";
- const labelWidth = 550;
- const labelHeight = 100;
- label.style.width = labelWidth + "px";
- label.style.height = labelHeight + "px";
- const root = qx.core.Init.getApplication().getRoot();
- if (root && root.getBounds()) {
- label.style.left = Math.round(root.getBounds().width/2) - labelWidth/2 + "px";
- label.style.top = Math.round(root.getBounds().height/2) - labelHeight/2 + "px";
- }
- blockerDomEl.appendChild(label);
-
- blockerDomEl.addEventListener("click", () => pgWindow.focus());
-
// Listen to socket event
const socket = osparc.wrapper.WebSocket.getInstance();
const slotName = "paymentCompleted";
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Overview.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Overview.js
deleted file mode 100644
index be0cd8cfbd0..00000000000
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/Overview.js
+++ /dev/null
@@ -1,346 +0,0 @@
-/* ************************************************************************
-
- osparc - the simcore frontend
-
- https://osparc.io
-
- Copyright:
- 2023 IT'IS Foundation, https://itis.swiss
-
- License:
- MIT: https://opensource.org/licenses/MIT
-
- Authors:
- * Odei Maiz (odeimaiz)
-
-************************************************************************ */
-
-qx.Class.define("osparc.desktop.credits.Overview", {
- extend: qx.ui.core.Widget,
-
- construct: function() {
- this.base(arguments);
-
- const grid = new qx.ui.layout.Grid(20, 20);
- grid.setColumnFlex(0, 1);
- grid.setColumnFlex(1, 1);
- this._setLayout(grid);
-
- this.__buildLayout();
- },
-
- events: {
- "buyCredits": "qx.event.type.Data",
- "toWallets": "qx.event.type.Event",
- "toTransactions": "qx.event.type.Event",
- "toUsageOverview": "qx.event.type.Event"
- },
-
- members: {
- _createChildControlImpl: function(id) {
- let control;
- switch (id) {
- case "wallets-card": {
- const content = this.__createWalletsView();
- const wallets = osparc.store.Store.getInstance().getWallets();
- control = this.__createOverviewCard(`Credit Accounts (${wallets.length})`, content, "toWallets");
- control.getChildren()[0].setValue(this.tr("Credits"));
- this._add(control, {
- column: 0,
- row: 0
- });
- break;
- }
- case "transactions-card": {
- const content = this.__createTransactionsView();
- control = this.__createOverviewCard("Transactions", content, "toTransactions");
- this._add(control, {
- column: 1,
- row: 0
- });
- break;
- }
- case "usage-card": {
- const content = this.__createUsageView();
- control = this.__createOverviewCard("Usage", content, "toUsageOverview");
- this._add(control, {
- column: 0,
- row: 1,
- colSpan: 2
- });
- break;
- }
- }
- return control || this.base(arguments, id);
- },
-
- __buildLayout: function() {
- this.getChildControl("wallets-card");
- this.getChildControl("transactions-card");
- this.getChildControl("usage-card");
- },
-
- __createOverviewCard: function(cardName, content, signalName) {
- const layout = new qx.ui.container.Composite(new qx.ui.layout.VBox(10)).set({
- minWidth: 200,
- minHeight: 200,
- padding: 15,
- backgroundColor: "background-main-1"
- });
- layout.getContentElement().setStyles({
- "border-radius": "4px"
- });
-
- const title = new qx.ui.basic.Label().set({
- value: cardName,
- font: "text-14"
- });
- layout.add(title);
-
- content.setPadding(5);
- layout.add(content, {
- flex: 1
- });
-
- const goToButton = new qx.ui.form.Button().set({
- label: this.tr("Go to ") + cardName,
- allowGrowX: false,
- alignX: "right"
- });
- goToButton.addListener("execute", () => this.fireEvent(signalName), this);
- layout.add(goToButton);
-
- return layout;
- },
-
- __createWalletsView: function() {
- const activeWallet = osparc.store.Store.getInstance().getActiveWallet();
- const preferredWallet = osparc.desktop.credits.Utils.getPreferredWallet();
- const oneWallet = activeWallet ? activeWallet : preferredWallet;
- if (oneWallet) {
- // show one wallet
- return this.__showOneWallet(oneWallet);
- }
- // show some wallets
- return this.__showSomeWallets();
- },
-
- __showOneWallet: function(wallet) {
- const layout = new qx.ui.container.Composite(new qx.ui.layout.VBox(10));
-
- const titleLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(10)).set({
- alignY: "middle"
- });
- const maxSize = 24;
- // thumbnail or shared or not shared
- const thumbnail = new qx.ui.basic.Image().set({
- backgroundColor: "transparent",
- alignX: "center",
- alignY: "middle",
- scale: true,
- allowShrinkX: true,
- allowShrinkY: true,
- maxHeight: maxSize,
- maxWidth: maxSize
- });
- const value = wallet.getThumbnail();
- if (value) {
- thumbnail.setSource(value);
- } else if (wallet.getAccessRights() && wallet.getAccessRights().length > 1) {
- thumbnail.setSource(osparc.utils.Icons.organization(maxSize-4));
- } else {
- thumbnail.setSource(osparc.utils.Icons.user(maxSize-4));
- }
- titleLayout.add(thumbnail);
- // name
- const walletName = new qx.ui.basic.Label().set({
- font: "text-14",
- alignY: "middle",
- maxWidth: 200
- });
- wallet.bind("name", walletName, "value");
- titleLayout.add(walletName);
- layout.add(titleLayout);
-
- const creditsIndicator = new osparc.desktop.credits.CreditsIndicator(wallet);
- layout.add(creditsIndicator);
-
- const buyButton = new qx.ui.form.Button().set({
- label: this.tr("Buy Credits"),
- icon: "@FontAwesome5Solid/dollar-sign/16",
- maxHeight: 30,
- alignY: "middle",
- allowGrowX: false,
- height: 25
- });
- const myAccessRights = wallet.getMyAccessRights();
- buyButton.setEnabled(Boolean(myAccessRights && myAccessRights["write"]));
- buyButton.addListener("execute", () => this.fireDataEvent("buyCredits", {
- walletId: wallet.getWalletId()
- }), this);
- layout.add(buyButton);
-
- return layout;
- },
-
- __showSomeWallets: function() {
- const grid = new qx.ui.layout.Grid(12, 8);
- const layout = new qx.ui.container.Composite(grid);
- const maxWallets = 5;
- const wallets = osparc.store.Store.getInstance().getWallets();
- for (let i=0; i 1) {
- thumbnail.setSource(osparc.utils.Icons.organization(maxSize-4));
- } else {
- thumbnail.setSource(osparc.utils.Icons.user(maxSize-4));
- }
- layout.add(thumbnail, {
- column,
- row: i
- });
- column++;
-
- // name
- const walletName = new qx.ui.basic.Label().set({
- font: "text-14",
- maxWidth: 100
- });
- wallet.bind("name", walletName, "value");
- layout.add(walletName, {
- column,
- row: i
- });
- column++;
-
- // indicator
- const creditsIndicator = new osparc.desktop.credits.CreditsIndicator(wallet);
- layout.add(creditsIndicator, {
- column,
- row: i
- });
- column++;
- }
-
- return layout;
- },
-
- __createTransactionsView: function() {
- const grid = new qx.ui.layout.Grid(12, 8);
- const layout = new qx.ui.container.Composite(grid);
-
- const headers = [
- "Date",
- "Price",
- "Credits",
- "Credit Account",
- "Comment"
- ];
- headers.forEach((header, column) => {
- const text = new qx.ui.basic.Label(header).set({
- font: "text-14"
- });
- layout.add(text, {
- row: 0,
- column
- });
- });
-
- osparc.data.Resources.fetch("payments", "get")
- .then(transactions => {
- if ("data" in transactions) {
- const maxTransactions = 4;
- transactions["data"].forEach((transaction, row) => {
- if (row < maxTransactions) {
- let walletName = null;
- if (transaction["walletId"]) {
- const found = osparc.desktop.credits.Utils.getWallet(transaction["walletId"]);
- if (found) {
- walletName = found.getName();
- }
- }
- const entry = [
- osparc.utils.Utils.formatDateAndTime(new Date(transaction["createdAt"])),
- transaction["priceDollars"].toFixed(2).toString(),
- transaction["osparcCredits"].toFixed(2).toString(),
- walletName,
- transaction["comment"]
- ];
- entry.forEach((data, column) => {
- const text = new qx.ui.basic.Label(data).set({
- font: "text-13"
- });
- layout.add(text, {
- row: row+1,
- column
- });
- });
- row++;
- }
- });
- }
- })
- .catch(err => console.error(err));
-
- return layout;
- },
-
- __createUsageView: function() {
- const grid = new qx.ui.layout.Grid(12, 8);
- const layout = new qx.ui.container.Composite(grid);
-
- const cols = osparc.resourceUsage.OverviewTable.COLUMNS;
- const colNames = Object.values(cols).map(col => col.title);
- colNames.forEach((colName, column) => {
- const text = new qx.ui.basic.Label(colName).set({
- font: "text-14"
- });
- layout.add(text, {
- row: 0,
- column
- });
- });
-
- const params = {
- url: {
- offset: 0,
- limit: 4 // show only the last 4 usage
- }
- };
- osparc.data.Resources.fetch("resourceUsage", "getPage", params)
- .then(async datas => {
- const entries = await osparc.resourceUsage.OverviewTable.respDataToTableData(datas);
- entries.forEach((entry, row) => {
- entry.forEach((data, column) => {
- const text = new qx.ui.basic.Label(data.toString()).set({
- font: "text-13"
- });
- layout.add(text, {
- row: row+1,
- column
- });
- });
- });
- });
- return layout;
- }
- }
-});
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/PaymentGatewayWindow.js b/services/static-webserver/client/source/class/osparc/desktop/credits/PaymentGatewayWindow.js
new file mode 100644
index 00000000000..b57f4e5536c
--- /dev/null
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/PaymentGatewayWindow.js
@@ -0,0 +1,59 @@
+/* ************************************************************************
+
+ osparc - the simcore frontend
+
+ https://osparc.io
+
+ Copyright:
+ 2023 IT'IS Foundation, https://itis.swiss
+
+ License:
+ MIT: https://opensource.org/licenses/MIT
+
+ Authors:
+ * Odei Maiz (odeimaiz)
+
+************************************************************************ */
+
+qx.Class.define("osparc.desktop.credits.PaymentGatewayWindow", {
+ type: "static",
+
+ statics: {
+ popUp: function(url, id, options, modal, useNativeModalDialog) {
+ const blocker = qx.bom.Window.getBlocker();
+ blocker.setBlockerColor("#FFF");
+ blocker.setBlockerOpacity(0.6);
+
+ const pgWindow = qx.bom.Window.open(
+ url,
+ id,
+ options,
+ modal,
+ useNativeModalDialog
+ );
+
+ // enhance the blocker
+ const blockerDomEl = blocker.getBlockerElement();
+ blockerDomEl.style.cursor = "pointer";
+
+ // text on blocker
+ const label = document.createElement("h1");
+ label.innerHTML = "Don’t see the secure Payment Window?
Click here to complete your purchase";
+ label.style.position = "fixed";
+ const labelWidth = 550;
+ const labelHeight = 100;
+ label.style.width = labelWidth + "px";
+ label.style.height = labelHeight + "px";
+ const root = qx.core.Init.getApplication().getRoot();
+ if (root && root.getBounds()) {
+ label.style.left = Math.round(root.getBounds().width/2) - labelWidth/2 + "px";
+ label.style.top = Math.round(root.getBounds().height/2) - labelHeight/2 + "px";
+ }
+ blockerDomEl.appendChild(label);
+
+ blockerDomEl.addEventListener("click", () => pgWindow.focus());
+
+ return pgWindow;
+ }
+ }
+});
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Summary.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Summary.js
new file mode 100644
index 00000000000..77e2f61c3d2
--- /dev/null
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Summary.js
@@ -0,0 +1,307 @@
+/* ************************************************************************
+
+ osparc - the simcore frontend
+
+ https://osparc.io
+
+ Copyright:
+ 2023 IT'IS Foundation, https://itis.swiss
+
+ License:
+ MIT: https://opensource.org/licenses/MIT
+
+ Authors:
+ * Odei Maiz (odeimaiz)
+
+************************************************************************ */
+
+qx.Class.define("osparc.desktop.credits.Summary", {
+ extend: qx.ui.core.Widget,
+
+ construct: function() {
+ this.base(arguments);
+
+ this._setLayout(new qx.ui.layout.VBox(10));
+
+ this.__buildLayout();
+ },
+
+ events: {
+ "buyCredits": "qx.event.type.Data",
+ "toWallets": "qx.event.type.Event",
+ "toActivity": "qx.event.type.Event"
+ },
+
+ members: {
+ _createChildControlImpl: function(id) {
+ let control;
+ switch (id) {
+ case "wallets-card": {
+ const content = this.__createWalletsView();
+ if (content) {
+ const wallets = osparc.store.Store.getInstance().getWallets();
+ control = this.__createOverviewCard(this.tr("Credits Balance"), content, `All Credit Accounts (${wallets.length})`, "toWallets");
+ this._add(control);
+ }
+ break;
+ }
+ case "settings-card": {
+ const content = this.__createSettingsView();
+ const wallet = this.__getWallet();
+ control = this.__createOverviewCard(this.tr("Settings"), content, this.tr("Credit Options"), "buyCredits", {
+ walletId: wallet ? wallet.getWalletId() : null
+ });
+ this._add(control);
+ break;
+ }
+ case "activity-card": {
+ const content = this.__createActivityView();
+ control = this.__createOverviewCard(this.tr("Last Activity"), content, this.tr("All Activity"), "toActivity");
+ this._add(control);
+ break;
+ }
+ }
+ return control || this.base(arguments, id);
+ },
+
+ __buildLayout: function() {
+ this.getChildControl("wallets-card");
+ this.getChildControl("settings-card");
+ this.getChildControl("activity-card");
+ },
+
+ __createOverviewCard: function(cardLabel, content, buttonLabel, signalName, signalData) {
+ const layout = new qx.ui.container.Composite(new qx.ui.layout.VBox(10)).set({
+ padding: 15,
+ backgroundColor: "background-main-1"
+ });
+ layout.getContentElement().setStyles({
+ "border-radius": "4px"
+ });
+
+ const topLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox());
+ const title = new qx.ui.basic.Label().set({
+ value: cardLabel,
+ font: "text-14",
+ allowGrowX: true
+ });
+ topLayout.add(title, {
+ flex: 1
+ });
+
+ const goToButton = new qx.ui.form.Button().set({
+ label: buttonLabel,
+ allowGrowX: false,
+ alignX: "right"
+ });
+ goToButton.addListener("execute", () => signalData ? this.fireDataEvent(signalName, signalData) : this.fireEvent(signalName), this);
+ topLayout.add(goToButton);
+ layout.add(topLayout);
+
+ content.setPadding(5);
+ layout.add(content, {
+ flex: 1
+ });
+
+ return layout;
+ },
+
+ __getWallet: function() {
+ const activeWallet = osparc.store.Store.getInstance().getActiveWallet();
+ const preferredWallet = osparc.desktop.credits.Utils.getPreferredWallet();
+ const wallet = activeWallet ? activeWallet : preferredWallet;
+ return wallet;
+ },
+
+ __createWalletsView: function() {
+ const wallet = this.__getWallet();
+ if (wallet) {
+ // show one wallet
+ const layout = new qx.ui.container.Composite(new qx.ui.layout.HBox(10));
+
+ const titleLayout = new qx.ui.container.Composite(new qx.ui.layout.HBox(10)).set({
+ alignY: "middle"
+ });
+ const maxSize = 24;
+ // thumbnail or shared or not shared
+ const thumbnail = new qx.ui.basic.Image().set({
+ backgroundColor: "transparent",
+ alignX: "center",
+ alignY: "middle",
+ scale: true,
+ allowShrinkX: true,
+ allowShrinkY: true,
+ maxHeight: maxSize,
+ maxWidth: maxSize
+ });
+ const value = wallet.getThumbnail();
+ if (value) {
+ thumbnail.setSource(value);
+ } else if (wallet.getAccessRights() && wallet.getAccessRights().length > 1) {
+ thumbnail.setSource(osparc.utils.Icons.organization(maxSize-4));
+ } else {
+ thumbnail.setSource(osparc.utils.Icons.user(maxSize-4));
+ }
+ titleLayout.add(thumbnail);
+
+ // name
+ const walletName = new qx.ui.basic.Label().set({
+ font: "text-14",
+ alignY: "middle"
+ });
+ wallet.bind("name", walletName, "value");
+ titleLayout.add(walletName);
+ layout.add(titleLayout);
+
+ // credits indicator
+ const creditsIndicator = new osparc.desktop.credits.CreditsIndicator(wallet);
+ layout.add(creditsIndicator);
+
+ // buy button
+ const buyButton = new qx.ui.form.Button().set({
+ label: this.tr("Buy Credits"),
+ icon: "@FontAwesome5Solid/dollar-sign/16",
+ maxHeight: 30,
+ alignY: "middle",
+ allowGrowX: false,
+ height: 25
+ });
+ const myAccessRights = wallet.getMyAccessRights();
+ buyButton.setEnabled(Boolean(myAccessRights && myAccessRights["write"]));
+ buyButton.addListener("execute", () => this.fireDataEvent("buyCredits", {
+ walletId: wallet.getWalletId()
+ }), this);
+ layout.add(buyButton);
+
+ return layout;
+ }
+ return null;
+ },
+
+ __createSettingsView: function() {
+ const wallet = this.__getWallet();
+ if (wallet) {
+ const grid = new qx.ui.layout.Grid(20, 10);
+ grid.setColumnAlign(0, "right", "middle");
+ const layout = new qx.ui.container.Composite(grid);
+
+ const t1t = new qx.ui.basic.Label(this.tr("Automatic Payment")).set({
+ font: "text-14"
+ });
+ layout.add(t1t, {
+ row: 0,
+ column: 0
+ });
+ const t1v = new qx.ui.basic.Label().set({
+ value: "Off",
+ font: "text-14"
+ });
+ layout.add(t1v, {
+ row: 0,
+ column: 1
+ });
+
+ const t2t = new qx.ui.basic.Label(this.tr("Monthly Spending Limit")).set({
+ font: "text-14"
+ });
+ layout.add(t2t, {
+ row: 1,
+ column: 0
+ });
+ const t2v = new qx.ui.basic.Label().set({
+ font: "text-14"
+ });
+ layout.add(t2v, {
+ row: 1,
+ column: 1
+ });
+
+ const t3t = new qx.ui.basic.Label(this.tr("Payment Method")).set({
+ font: "text-14"
+ });
+ layout.add(t3t, {
+ row: 2,
+ column: 0
+ });
+ const t3v = new qx.ui.basic.Label().set({
+ font: "text-14"
+ });
+ layout.add(t3v, {
+ row: 2,
+ column: 1
+ });
+
+ wallet.bind("autoRecharge", t1v, "value", {
+ converter: arData => arData["enabled"] ? this.tr("On") : this.tr("Off")
+ });
+ wallet.bind("autoRecharge", t2v, "value", {
+ converter: arData => arData["enabled"] ? (arData["topUpAmountInUsd"]*arData["topUpCountdown"]) + " US$" : null
+ });
+ wallet.bind("autoRecharge", t3v, "value", {
+ converter: arData => arData["enabled"] ? arData["paymentMethodId"] : null
+ });
+
+ return layout;
+ }
+ return null;
+ },
+
+ __createActivityView: function() {
+ const grid = new qx.ui.layout.Grid(12, 8);
+ const layout = new qx.ui.container.Composite(grid);
+
+ const cols = osparc.desktop.credits.ActivityTable.COLUMNS;
+ const colNames = Object.values(cols).map(col => col.title);
+ colNames.forEach((colName, column) => {
+ const text = new qx.ui.basic.Label(colName).set({
+ font: "text-13"
+ });
+ layout.add(text, {
+ row: 0,
+ column
+ });
+ });
+
+ const walletId = this.__getWallet().getWalletId();
+ const params = {
+ url: {
+ walletId: walletId,
+ offset: 0,
+ limit: 10
+ }
+ };
+ Promise.all([
+ osparc.data.Resources.fetch("resourceUsagePerWallet", "getPage", params),
+ osparc.data.Resources.fetch("payments", "get")
+ ])
+ .then(responses => {
+ const usages = responses[0];
+ const transactions = responses[1]["data"];
+ const activities1 = osparc.desktop.credits.ActivityTable.usagesToActivities(usages);
+ // Filter out some transactions
+ const filteredTransactions = transactions.filter(transaction => transaction["completedStatus"] !== "FAILED" && transaction["walletId"] === walletId);
+ const activities2 = osparc.desktop.credits.ActivityTable.transactionsToActivities(filteredTransactions);
+ const activities = activities1.concat(activities2);
+ activities.sort((a, b) => new Date(b["date"]).getTime() - new Date(a["date"]).getTime());
+
+ const maxRows = 6;
+ const entries = osparc.desktop.credits.ActivityTable.respDataToTableData(activities);
+ entries.forEach((entry, row) => {
+ if (row >= maxRows) {
+ return;
+ }
+ entry.forEach((data, column) => {
+ const text = new qx.ui.basic.Label(data.toString()).set({
+ font: "text-14"
+ });
+ layout.add(text, {
+ row: row+1,
+ column
+ });
+ });
+ });
+ });
+ return layout;
+ }
+ }
+});
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Transactions.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Transactions.js
index afbe36f5cab..ee55313d066 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/Transactions.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Transactions.js
@@ -21,132 +21,36 @@ qx.Class.define("osparc.desktop.credits.Transactions", {
construct: function() {
this.base(arguments);
- this._setLayout(new qx.ui.layout.VBox(20));
-
- this.__buildLayout();
+ this._setLayout(new qx.ui.layout.VBox(15));
+
+ const transactionsTable = this.getChildControl("transactions-table");
+ osparc.data.Resources.fetch("payments", "get")
+ .then(transactions => {
+ if ("data" in transactions) {
+ transactionsTable.addData(transactions["data"]);
+ }
+ })
+ .catch(err => console.error(err));
},
- statics: {
- COLUMNS: {
- date: {
- pos: 0,
- title: qx.locale.Manager.tr("Date")
- },
- price: {
- pos: 1,
- title: qx.locale.Manager.tr("Price")
- },
- credits: {
- pos: 2,
- title: qx.locale.Manager.tr("Credits")
- },
- wallet: {
- pos: 3,
- title: qx.locale.Manager.tr("Credit Account")
- },
- status: {
- pos: 4,
- title: qx.locale.Manager.tr("Status")
- },
- comment: {
- pos: 5,
- title: qx.locale.Manager.tr("Comment")
- },
- invoice: {
- pos: 6,
- title: qx.locale.Manager.tr("Invoice")
- }
- },
-
- addColorTag: function(status) {
- const color = this.getLevelColor(status);
- status = osparc.utils.Utils.onlyFirstsUp(status);
- return ("" + status + "");
- },
-
- getLevelColor: function(status) {
- const colorManager = qx.theme.manager.Color.getInstance();
- let logLevel = null;
- switch (status) {
- case "SUCCESS":
- logLevel = "info";
- break;
- case "PENDING":
- logLevel = "warning";
- break;
- case "CANCELED":
- case "FAILED":
- logLevel = "error";
- break;
- default:
- console.error("completedStatus unknown");
+ members: {
+ _createChildControlImpl: function(id) {
+ let control;
+ switch (id) {
+ case "transactions-table":
+ control = new osparc.desktop.credits.TransactionsTable();
+ this._add(control);
break;
}
- return colorManager.resolve("logger-"+logLevel+"-message");
- },
-
- createPdfIconWithLink: function(link) {
- return ``;
- },
-
- respDataToTableData: function(datas) {
- const newDatas = [];
- if (datas) {
- const cols = this.COLUMNS;
- datas.forEach(data => {
- const newData = [];
- newData[cols["date"].pos] = osparc.utils.Utils.formatDateAndTime(new Date(data["createdAt"]));
- newData[cols["price"].pos] = data["priceDollars"] ? data["priceDollars"] : 0;
- newData[cols["credits"].pos] = data["osparcCredits"] ? data["osparcCredits"] : 0;
- let walletName = "Unknown";
- const found = osparc.desktop.credits.Utils.getWallet(data["walletId"]);
- if (found) {
- walletName = found.getName();
- }
- newData[cols["wallet"].pos] = walletName;
- if (data["completedStatus"]) {
- newData[cols["status"].pos] = this.addColorTag(data["completedStatus"]);
- }
- newData[cols["comment"].pos] = data["comment"];
- const invoiceUrl = data["invoiceUrl"];
- newData[cols["invoice"].pos] = invoiceUrl? this.createPdfIconWithLink(invoiceUrl) : "";
-
- newDatas.push(newData);
- });
- }
- return newDatas;
- }
- },
-
- members: {
- __table: null,
-
- __buildLayout: function() {
- const tableModel = new qx.ui.table.model.Simple();
- const cols = this.self().COLUMNS;
- const colNames = Object.values(cols).map(col => col.title);
- tableModel.setColumns(colNames);
-
- const table = this.__table = new osparc.ui.table.Table(tableModel, {
- tableColumnModel: obj => new qx.ui.table.columnmodel.Resize(obj)
- });
- const htmlRenderer = new qx.ui.table.cellrenderer.Html();
- table.getTableColumnModel().setDataCellRenderer(cols.status.pos, htmlRenderer);
- table.getTableColumnModel().setDataCellRenderer(cols.invoice.pos, htmlRenderer);
- table.setColumnWidth(cols.invoice.pos, 50);
- table.makeItLoose();
- this._add(table);
-
- this.refetchData();
+ return control || this.base(arguments, id);
},
- refetchData: function() {
- this.__table.setData([]);
+ __fetchData: function() {
osparc.data.Resources.fetch("payments", "get")
.then(transactions => {
if ("data" in transactions) {
- const newDatas = this.self().respDataToTableData(transactions["data"]);
- this.__table.setData(newDatas);
+ const table = this.getChildControl("usage-table");
+ table.addData(transactions["data"]);
}
})
.catch(err => console.error(err));
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/TransactionsTable.js b/services/static-webserver/client/source/class/osparc/desktop/credits/TransactionsTable.js
new file mode 100644
index 00000000000..62b171cbdd1
--- /dev/null
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/TransactionsTable.js
@@ -0,0 +1,143 @@
+/* ************************************************************************
+
+ osparc - the simcore frontend
+
+ https://osparc.io
+
+ Copyright:
+ 2023 IT'IS Foundation, https://itis.swiss
+
+ License:
+ MIT: https://opensource.org/licenses/MIT
+
+ Authors:
+ * Odei Maiz (odeimaiz)
+
+************************************************************************ */
+
+qx.Class.define("osparc.desktop.credits.TransactionsTable", {
+ extend: osparc.ui.table.Table,
+
+ construct: function() {
+ const model = new qx.ui.table.model.Simple();
+ const cols = this.self().COLUMNS;
+ const colNames = Object.values(cols).map(col => col.title);
+ model.setColumns(colNames);
+
+ this.base(arguments, model, {
+ tableColumnModel: obj => new qx.ui.table.columnmodel.Resize(obj),
+ statusBarVisible: false
+ });
+
+ const htmlRenderer = new qx.ui.table.cellrenderer.Html();
+ const columnModel = this.getTableColumnModel();
+ columnModel.setDataCellRenderer(cols.status.pos, htmlRenderer);
+ columnModel.setDataCellRenderer(cols.invoice.pos, htmlRenderer);
+ this.setColumnWidth(cols.invoice.pos, 50);
+ this.makeItLoose();
+ },
+
+ statics: {
+ COLUMNS: {
+ date: {
+ pos: 0,
+ title: qx.locale.Manager.tr("Date")
+ },
+ price: {
+ pos: 1,
+ title: qx.locale.Manager.tr("Price")
+ },
+ credits: {
+ pos: 2,
+ title: qx.locale.Manager.tr("Credits")
+ },
+ wallet: {
+ pos: 3,
+ title: qx.locale.Manager.tr("Credit Account")
+ },
+ status: {
+ pos: 4,
+ title: qx.locale.Manager.tr("Status")
+ },
+ comment: {
+ pos: 5,
+ title: qx.locale.Manager.tr("Comment")
+ },
+ invoice: {
+ pos: 6,
+ title: qx.locale.Manager.tr("Invoice")
+ }
+ },
+
+ addColorTag: function(status) {
+ const color = this.getLevelColor(status);
+ status = osparc.utils.Utils.onlyFirstsUp(status);
+ return ("" + status + "");
+ },
+
+ getLevelColor: function(status) {
+ const colorManager = qx.theme.manager.Color.getInstance();
+ let logLevel = null;
+ switch (status) {
+ case "SUCCESS":
+ logLevel = "info";
+ break;
+ case "PENDING":
+ logLevel = "warning";
+ break;
+ case "CANCELED":
+ case "FAILED":
+ logLevel = "error";
+ break;
+ default:
+ console.error("completedStatus unknown");
+ break;
+ }
+ return colorManager.resolve("logger-"+logLevel+"-message");
+ },
+
+ createPdfIconWithLink: function(link) {
+ return ``;
+ },
+
+ respDataToTableRow: function(data) {
+ const cols = this.COLUMNS;
+ const newData = [];
+ newData[cols["date"].pos] = osparc.utils.Utils.formatDateAndTime(new Date(data["createdAt"]));
+ newData[cols["price"].pos] = data["priceDollars"] ? data["priceDollars"] : 0;
+ newData[cols["credits"].pos] = data["osparcCredits"] ? data["osparcCredits"] : 0;
+ let walletName = "Unknown";
+ const found = osparc.desktop.credits.Utils.getWallet(data["walletId"]);
+ if (found) {
+ walletName = found.getName();
+ }
+ newData[cols["wallet"].pos] = walletName;
+ if (data["completedStatus"]) {
+ newData[cols["status"].pos] = this.addColorTag(data["completedStatus"]);
+ }
+ newData[cols["comment"].pos] = data["comment"];
+ const invoiceUrl = data["invoiceUrl"];
+ newData[cols["invoice"].pos] = invoiceUrl? this.createPdfIconWithLink(invoiceUrl) : "";
+
+ return newData;
+ },
+
+ respDataToTableData: function(datas) {
+ const newDatas = [];
+ if (datas) {
+ datas.forEach(data => {
+ const newData = this.respDataToTableRow(data);
+ newDatas.push(newData);
+ });
+ }
+ return newDatas;
+ }
+ },
+
+ members: {
+ addData: function(datas) {
+ const newDatas = this.self().respDataToTableData(datas);
+ this.setData(newDatas);
+ }
+ }
+});
diff --git a/services/static-webserver/client/source/class/osparc/resourceUsage/Overview.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Usage.js
similarity index 75%
rename from services/static-webserver/client/source/class/osparc/resourceUsage/Overview.js
rename to services/static-webserver/client/source/class/osparc/desktop/credits/Usage.js
index 7b51b7ba9df..10116bf7707 100644
--- a/services/static-webserver/client/source/class/osparc/resourceUsage/Overview.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Usage.js
@@ -15,7 +15,7 @@
************************************************************************ */
-qx.Class.define("osparc.resourceUsage.Overview", {
+qx.Class.define("osparc.desktop.credits.Usage", {
extend: qx.ui.core.Widget,
construct: function() {
@@ -23,13 +23,8 @@ qx.Class.define("osparc.resourceUsage.Overview", {
this._setLayout(new qx.ui.layout.VBox(15));
- this.getChildControl("wallet-selector-title");
- const walletSelector = this.getChildControl("wallet-selector");
- this.getChildControl("wallet-selector-layout").exclude();
- const walletsEnabled = osparc.desktop.credits.Utils.areWalletsEnabled();
- if (walletsEnabled) {
- this.getChildControl("wallet-selector-layout").show();
- }
+ const walletSelectorLayout = this.getChildControl("wallet-selector-layout");
+ const walletSelector = walletSelectorLayout.getChildren()[1];
const loadingImage = this.getChildControl("loading-image");
loadingImage.show();
@@ -45,18 +40,7 @@ qx.Class.define("osparc.resourceUsage.Overview", {
},
statics: {
- ITEMS_PER_PAGE: 15,
-
- popUpInWindow: function() {
- const title = qx.locale.Manager.tr("Usage");
- const noteEditor = new osparc.resourceUsage.Overview();
- const viewWidth = 900;
- const viewHeight = 450;
- const win = osparc.ui.window.Window.popUpInWindow(noteEditor, title, viewWidth, viewHeight);
- win.center();
- win.open();
- return win;
- }
+ ITEMS_PER_PAGE: 15
},
members: {
@@ -67,27 +51,9 @@ qx.Class.define("osparc.resourceUsage.Overview", {
let control;
switch (id) {
case "wallet-selector-layout":
- control = new qx.ui.container.Composite(new qx.ui.layout.HBox(10));
+ control = osparc.desktop.credits.Utils.createWalletSelectorLayout("read");
this._add(control);
break;
- case "wallet-selector-title": {
- control = new qx.ui.basic.Label(this.tr("Select Credit Account")).set({
- alignY: "middle"
- });
- const layout = this.getChildControl("wallet-selector-layout");
- layout.add(control);
- break;
- }
- case "wallet-selector": {
- control = osparc.desktop.credits.Utils.createWalletSelector("read", false, true).set({
- allowGrowX: false
- });
- // select "All Credit Accounts" by default
- control.getSelectables()[0].setLabel("All Credit Accounts");
- const layout = this.getChildControl("wallet-selector-layout");
- layout.add(control);
- break;
- }
case "loading-image":
control = new qx.ui.basic.Image().set({
source: "@FontAwesome5Solid/circle-notch/64",
@@ -98,7 +64,7 @@ qx.Class.define("osparc.resourceUsage.Overview", {
this._add(control);
break;
case "usage-table":
- control = new osparc.resourceUsage.OverviewTable().set({
+ control = new osparc.desktop.credits.UsageTable().set({
height: (this.self().ITEMS_PER_PAGE*20 + 40)
});
this._add(control);
@@ -171,8 +137,8 @@ qx.Class.define("osparc.resourceUsage.Overview", {
__getPrevRequest: function() {
const params = {
url: {
- offset: osparc.resourceUsage.Overview.ITEMS_PER_PAGE,
- limit: osparc.resourceUsage.Overview.ITEMS_PER_PAGE
+ offset: this.self().ITEMS_PER_PAGE,
+ limit: this.self().ITEMS_PER_PAGE
}
};
if (this.__prevRequestParams) {
@@ -186,7 +152,7 @@ qx.Class.define("osparc.resourceUsage.Overview", {
const params = {
url: {
offset: 0,
- limit: osparc.resourceUsage.Overview.ITEMS_PER_PAGE
+ limit: this.self().ITEMS_PER_PAGE
}
};
if (this.__nextRequestParams) {
@@ -201,13 +167,15 @@ qx.Class.define("osparc.resourceUsage.Overview", {
resolveWResponse: true
};
- const walletSelector = this.getChildControl("wallet-selector");
+ const walletSelectorLayout = this.getChildControl("wallet-selector-layout");
+ const walletSelector = walletSelectorLayout.getChildren()[1];
const walletSelection = walletSelector.getSelection();
const walletId = walletSelection && walletSelection.length ? walletSelection[0].walletId : null;
if (walletId) {
params.url["walletId"] = walletId.toString();
return osparc.data.Resources.fetch("resourceUsagePerWallet", "getPage", params, undefined, options);
}
+ // Usage supports the non wallet enabled products
return osparc.data.Resources.fetch("resourceUsage", "getPage", params, undefined, options);
},
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/UsageTable.js b/services/static-webserver/client/source/class/osparc/desktop/credits/UsageTable.js
new file mode 100644
index 00000000000..40663635dbf
--- /dev/null
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/UsageTable.js
@@ -0,0 +1,125 @@
+/* ************************************************************************
+
+ osparc - the simcore frontend
+
+ https://osparc.io
+
+ Copyright:
+ 2023 IT'IS Foundation, https://itis.swiss
+
+ License:
+ MIT: https://opensource.org/licenses/MIT
+
+ Authors:
+ * Odei Maiz (odeimaiz)
+
+************************************************************************ */
+
+qx.Class.define("osparc.desktop.credits.UsageTable", {
+ extend: osparc.ui.table.Table,
+
+ construct: function() {
+ const model = new qx.ui.table.model.Simple();
+ const cols = this.self().COLUMNS;
+ const colNames = Object.values(cols).map(col => col.title);
+ model.setColumns(colNames);
+
+ this.base(arguments, model, {
+ tableColumnModel: obj => new qx.ui.table.columnmodel.Resize(obj),
+ statusBarVisible: false
+ });
+ this.makeItLoose();
+
+ const columnModel = this.getTableColumnModel();
+ columnModel.getBehavior().setWidth(this.self().COLUMNS.duration.pos, 70);
+ columnModel.getBehavior().setWidth(this.self().COLUMNS.status.pos, 70);
+ columnModel.getBehavior().setWidth(this.self().COLUMNS.cost.pos, 60);
+
+ if (!osparc.desktop.credits.Utils.areWalletsEnabled()) {
+ columnModel.setColumnVisible(this.self().COLUMNS.user.pos, false);
+ }
+ },
+
+ statics: {
+ COLUMNS: {
+ project: {
+ pos: 0,
+ title: osparc.product.Utils.getStudyAlias({firstUpperCase: true})
+ },
+ node: {
+ pos: 1,
+ title: qx.locale.Manager.tr("Node")
+ },
+ service: {
+ pos: 2,
+ title: qx.locale.Manager.tr("Service")
+ },
+ start: {
+ pos: 3,
+ title: qx.locale.Manager.tr("Start")
+ },
+ duration: {
+ pos: 4,
+ title: qx.locale.Manager.tr("Duration")
+ },
+ status: {
+ pos: 5,
+ title: qx.locale.Manager.tr("Status")
+ },
+ cost: {
+ pos: 6,
+ title: qx.locale.Manager.tr("Cost")
+ },
+ user: {
+ pos: 7,
+ title: qx.locale.Manager.tr("User")
+ }
+ },
+
+ respDataToTableRow: async function(data) {
+ const cols = this.COLUMNS;
+ const newData = [];
+ newData[cols["project"].pos] = data["project_name"] ? data["project_name"] : data["project_id"];
+ newData[cols["node"].pos] = data["node_name"] ? data["node_name"] : data["node_id"];
+ if (data["service_key"]) {
+ const parts = data["service_key"].split("/");
+ const serviceName = parts.pop();
+ newData[cols["service"].pos] = serviceName + ":" + data["service_version"];
+ }
+ if (data["started_at"]) {
+ const startTime = new Date(data["started_at"]);
+ newData[cols["start"].pos] = osparc.utils.Utils.formatDateAndTime(startTime);
+ if (data["stopped_at"]) {
+ const stopTime = new Date(data["stopped_at"]);
+ const durationTime = stopTime - startTime;
+ newData[cols["duration"].pos] = osparc.utils.Utils.formatMilliSeconds(durationTime);
+ }
+ }
+ newData[cols["status"].pos] = qx.lang.String.firstUp(data["service_run_status"].toLowerCase());
+ newData[cols["cost"].pos] = data["credit_cost"] ? data["credit_cost"] : "-";
+ const user = await osparc.store.Store.getInstance().getUser(data["user_id"]);
+ if (user) {
+ newData[cols["user"].pos] = user ? user["label"] : data["user_id"];
+ }
+ return newData;
+ },
+
+ respDataToTableData: async function(datas) {
+ const newDatas = [];
+ if (datas) {
+ for (const data of datas) {
+ const newData = await this.respDataToTableRow(data);
+ newDatas.push(newData);
+ }
+ }
+ return newDatas;
+ }
+ },
+
+ members: {
+ addData: async function(datas) {
+ const newDatas = await this.self().respDataToTableData(datas);
+ this.setData(newDatas);
+ }
+ }
+});
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/UserCenter.js b/services/static-webserver/client/source/class/osparc/desktop/credits/UserCenter.js
index 4ed000d1636..5d378b1d013 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/UserCenter.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/UserCenter.js
@@ -49,14 +49,19 @@ qx.Class.define("osparc.desktop.credits.UserCenter", {
tabViews.add(walletsPage);
}
+ if (this.__walletsEnabled) {
+ const paymentMethodsPage = this.__paymentMethodsPage = this.__getPaymentMethodsPage();
+ tabViews.add(paymentMethodsPage);
+ }
+
if (this.__walletsEnabled) {
const buyCreditsPage = this.__buyCreditsPage = this.__getBuyCreditsPage();
tabViews.add(buyCreditsPage);
}
if (this.__walletsEnabled) {
- const paymentMethodsPage = this.__paymentMethodsPage = this.__getPaymentMethodsPage();
- tabViews.add(paymentMethodsPage);
+ const activityPage = this.__activityPage = this.__getActivityPage();
+ tabViews.add(activityPage);
}
if (this.__walletsEnabled) {
@@ -133,17 +138,18 @@ qx.Class.define("osparc.desktop.credits.UserCenter", {
__walletsPage: null,
__buyCreditsPage: null,
__paymentMethodsPage: null,
+ __activityPage: null,
__transactionsPage: null,
__usageOverviewPage: null,
__buyCredits: null,
__transactionsTable: null,
__getOverviewPage: function() {
- const title = this.tr("Overview");
+ const title = this.tr("Summary");
const iconSrc = "@FontAwesome5Solid/table/22";
const page = new osparc.desktop.preferences.pages.BasePage(title, iconSrc);
page.showLabelOnTab();
- const overview = new osparc.desktop.credits.Overview();
+ const overview = new osparc.desktop.credits.Summary();
overview.set({
margin: 10
});
@@ -159,6 +165,7 @@ qx.Class.define("osparc.desktop.credits.UserCenter", {
}
});
overview.addListener("toWallets", () => this.openWallets());
+ overview.addListener("toActivity", () => this.__openActivity());
overview.addListener("toTransactions", () => this.__openTransactions());
overview.addListener("toUsageOverview", () => this.__openUsageOverview());
page.add(overview);
@@ -195,6 +202,19 @@ qx.Class.define("osparc.desktop.credits.UserCenter", {
return page;
},
+ __getPaymentMethodsPage: function() {
+ const title = this.tr("Payment Methods");
+ const iconSrc = "@FontAwesome5Solid/credit-card/22";
+ const page = new osparc.desktop.preferences.pages.BasePage(title, iconSrc);
+ page.showLabelOnTab();
+ const paymentMethods = new osparc.desktop.paymentMethods.PaymentMethods();
+ paymentMethods.set({
+ margin: 10
+ });
+ page.add(paymentMethods);
+ return page;
+ },
+
__getBuyCreditsPage: function() {
const title = this.tr("Buy Credits");
const iconSrc = "@FontAwesome5Solid/dollar-sign/22";
@@ -209,16 +229,16 @@ qx.Class.define("osparc.desktop.credits.UserCenter", {
return page;
},
- __getPaymentMethodsPage: function() {
- const title = this.tr("Payment Methods");
- const iconSrc = "@FontAwesome5Solid/credit-card/22";
+ __getActivityPage: function() {
+ const title = this.tr("Activity");
+ const iconSrc = "@FontAwesome5Solid/list/22";
const page = new osparc.desktop.preferences.pages.BasePage(title, iconSrc);
page.showLabelOnTab();
- const paymentMethods = new osparc.desktop.paymentMethods.PaymentMethods();
- paymentMethods.set({
+ const activity = new osparc.desktop.credits.Activity();
+ activity.set({
margin: 10
});
- page.add(paymentMethods);
+ page.add(activity);
return page;
},
@@ -240,7 +260,7 @@ qx.Class.define("osparc.desktop.credits.UserCenter", {
const iconSrc = "@FontAwesome5Solid/list/22";
const page = new osparc.desktop.preferences.pages.BasePage(title, iconSrc);
page.showLabelOnTab();
- const usageOverview = new osparc.resourceUsage.Overview();
+ const usageOverview = new osparc.desktop.credits.Usage();
usageOverview.set({
margin: 10
});
@@ -283,6 +303,10 @@ qx.Class.define("osparc.desktop.credits.UserCenter", {
this.__openPage(this.__buyCreditsPage);
},
+ __openActivity: function() {
+ this.__openPage(this.__activityPage);
+ },
+
__openTransactions: function(fetchTransactions = false) {
if (fetchTransactions) {
this.__transactionsTable.refetchData();
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/UserCenterWindow.js b/services/static-webserver/client/source/class/osparc/desktop/credits/UserCenterWindow.js
index 2c8bbf78aed..ca0f45da509 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/UserCenterWindow.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/UserCenterWindow.js
@@ -21,7 +21,7 @@ qx.Class.define("osparc.desktop.credits.UserCenterWindow", {
construct: function(walletsEnabled = false) {
this.base(arguments, "credits", this.tr("User Center"));
- const viewWidth = walletsEnabled ? 1050 : 800;
+ const viewWidth = walletsEnabled ? 1000 : 800;
const viewHeight = walletsEnabled ? 700 : 600;
this.set({
diff --git a/services/static-webserver/client/source/class/osparc/desktop/credits/Utils.js b/services/static-webserver/client/source/class/osparc/desktop/credits/Utils.js
index 897e78c8cf5..d4c16c399e5 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/credits/Utils.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/credits/Utils.js
@@ -64,6 +64,24 @@ qx.Class.define("osparc.desktop.credits.Utils", {
return walletSelector;
},
+ createWalletSelectorLayout: function(accessRight = "read") {
+ const layout = new qx.ui.container.Composite(new qx.ui.layout.HBox(10));
+
+ const label = new qx.ui.basic.Label(qx.locale.Manager.tr("Select Credit Account"));
+ layout.add(label);
+
+ const walletSelector = osparc.desktop.credits.Utils.createWalletSelector(accessRight);
+ layout.add(walletSelector);
+
+ if (osparc.desktop.credits.Utils.areWalletsEnabled() && walletSelector.getSelectables().length > 1) {
+ layout.show();
+ } else {
+ layout.exclude();
+ }
+
+ return layout;
+ },
+
autoSelectActiveWallet: function(walletSelector) {
// If there is only one active wallet, select it
const store = osparc.store.Store.getInstance();
@@ -89,6 +107,16 @@ qx.Class.define("osparc.desktop.credits.Utils", {
return null;
},
+ getMyWallets: function() {
+ const store = osparc.store.Store.getInstance();
+ const wallets = store.getWallets();
+ const myWallets = wallets.filter(wallet => wallet.getMyAccessRights()["write"]);
+ if (myWallets) {
+ return myWallets;
+ }
+ return [];
+ },
+
getPreferredWallet: function() {
const store = osparc.store.Store.getInstance();
const wallets = store.getWallets();
@@ -110,8 +138,7 @@ qx.Class.define("osparc.desktop.credits.Utils", {
};
promises.push(osparc.data.Resources.fetch("paymentMethods", "get", params));
} else {
- const wallets = osparc.store.Store.getInstance().getWallets();
- const myWallets = wallets.filter(wallet => wallet.getMyAccessRights()["write"]);
+ const myWallets = this.getMyWallets();
myWallets.forEach(myWallet => {
const params = {
url: {
diff --git a/services/static-webserver/client/source/class/osparc/desktop/paymentMethods/PaymentMethodListItem.js b/services/static-webserver/client/source/class/osparc/desktop/paymentMethods/PaymentMethodListItem.js
index cdf335d9a9b..61cb2b28f21 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/paymentMethods/PaymentMethodListItem.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/paymentMethods/PaymentMethodListItem.js
@@ -16,19 +16,21 @@
************************************************************************ */
qx.Class.define("osparc.desktop.paymentMethods.PaymentMethodListItem", {
- extend: osparc.ui.list.ListItemWithMenu,
+ extend: osparc.ui.list.ListItem,
construct: function() {
this.base(arguments);
const layout = this._getLayout();
layout.setSpacingX(15);
- layout.setColumnFlex(1, 0);
- layout.setColumnFlex(2, 0);
- layout.setColumnFlex(3, 0);
- layout.setColumnFlex(4, 0);
- layout.setColumnFlex(5, 1);
- layout.setColumnFlex(6, 0);
+ layout.setColumnFlex(this.self().GRID_POS.ICON, 0);
+ layout.setColumnFlex(this.self().GRID_POS.NAME, 0);
+ layout.setColumnFlex(this.self().GRID_POS.TYPE, 0);
+ layout.setColumnFlex(this.self().GRID_POS.MASKED_NUMBER, 0);
+ layout.setColumnFlex(this.self().GRID_POS.EXPIRATION_DATE, 1);
+ // buttons to the right
+ layout.setColumnFlex(this.self().GRID_POS.INFO_BUTTON, 0);
+ layout.setColumnFlex(this.self().GRID_POS.DELETE_BUTTON, 0);
this.getChildControl("thumbnail").setSource("@FontAwesome5Solid/credit-card/18");
@@ -49,16 +51,8 @@ qx.Class.define("osparc.desktop.paymentMethods.PaymentMethodListItem", {
converter: year => this.getExpirationMonth() + "/" + year
});
- const store = osparc.store.Store.getInstance();
- const walletName = this.getChildControl("wallet-name");
- this.bind("walletId", walletName, "value", {
- converter: walletId => {
- const found = store.getWallets().find(wallet => wallet.getWalletId() === walletId);
- return found ? found.getName() : this.tr("Unknown Credit Account");
- }
- });
-
- this.__getOptionsMenu();
+ this.getChildControl("details-button");
+ this.getChildControl("delete-button");
},
properties: {
@@ -110,6 +104,18 @@ qx.Class.define("osparc.desktop.paymentMethods.PaymentMethodListItem", {
"deletePaymentMethod": "qx.event.type.Data"
},
+ statics: {
+ GRID_POS: {
+ ICON: 0,
+ NAME: 1,
+ TYPE: 2,
+ MASKED_NUMBER: 3,
+ EXPIRATION_DATE: 4,
+ INFO_BUTTON: 5,
+ DELETE_BUTTON: 6
+ }
+ },
+
members: {
_createChildControlImpl: function(id) {
let control;
@@ -120,8 +126,7 @@ qx.Class.define("osparc.desktop.paymentMethods.PaymentMethodListItem", {
});
this._add(control, {
row: 0,
- column: 1,
- rowSpan: 2
+ column: this.self().GRID_POS.NAME
});
break;
case "card-type":
@@ -130,8 +135,7 @@ qx.Class.define("osparc.desktop.paymentMethods.PaymentMethodListItem", {
});
this._add(control, {
row: 0,
- column: 2,
- rowSpan: 2
+ column: this.self().GRID_POS.TYPE
});
break;
case "card-number-masked":
@@ -140,8 +144,7 @@ qx.Class.define("osparc.desktop.paymentMethods.PaymentMethodListItem", {
});
this._add(control, {
row: 0,
- column: 3,
- rowSpan: 2
+ column: this.self().GRID_POS.MASKED_NUMBER
});
break;
case "expiration-date":
@@ -150,75 +153,47 @@ qx.Class.define("osparc.desktop.paymentMethods.PaymentMethodListItem", {
});
this._add(control, {
row: 0,
- column: 4,
- rowSpan: 2
+ column: this.self().GRID_POS.EXPIRATION_DATE
});
break;
- case "wallet-name":
- control = new qx.ui.basic.Label().set({
- font: "text-14"
+ case "details-button":
+ control = new qx.ui.form.Button().set({
+ icon: "@FontAwesome5Solid/info/14"
});
+ control.addListener("execute", () => this.fireDataEvent("openPaymentMethodDetails", this.getKey()));
this._add(control, {
row: 0,
- column: 5,
- rowSpan: 2
+ column: this.self().GRID_POS.INFO_BUTTON
});
break;
- case "options-menu": {
- const iconSize = 26;
- control = new qx.ui.form.MenuButton().set({
- maxWidth: iconSize,
- maxHeight: iconSize,
- alignX: "center",
- alignY: "middle",
- icon: "@FontAwesome5Solid/ellipsis-v/"+(iconSize-11),
- focusable: false
+ case "delete-button":
+ control = new qx.ui.form.Button().set({
+ icon: "@FontAwesome5Solid/trash/14"
});
+ control.addListener("execute", () => this.__deletePressed());
this._add(control, {
row: 0,
- column: 6,
- rowSpan: 2
+ column: this.self().GRID_POS.DELETE_BUTTON
});
break;
- }
}
return control || this.base(arguments, id);
},
- // overridden
- __getOptionsMenu: function() {
- const optionsMenu = this.getChildControl("options-menu");
- optionsMenu.show();
-
- const menu = new qx.ui.menu.Menu().set({
- position: "bottom-right"
+ __deletePressed: function() {
+ const msg = this.tr("Are you sure you want to delete the Payment Method?");
+ const win = new osparc.ui.window.Confirmation(msg).set({
+ confirmText: this.tr("Delete"),
+ confirmAction: "delete"
+ });
+ win.center();
+ win.open();
+ win.addListener("close", () => {
+ if (win.getConfirmed()) {
+ this.fireDataEvent("deletePaymentMethod", this.getKey());
+ }
});
-
- const viewDetailsButton = new qx.ui.menu.Button(this.tr("View details..."));
- viewDetailsButton.addListener("execute", () => this.fireDataEvent("openPaymentMethodDetails", this.getKey()));
- menu.add(viewDetailsButton);
-
- const detelePMButton = new qx.ui.menu.Button(this.tr("Delete Payment Method"));
- detelePMButton.addListener("execute", () => {
- const msg = this.tr("Are you sure you want to delete the Payment Method?");
- const win = new osparc.ui.window.Confirmation(msg).set({
- confirmText: this.tr("Delete"),
- confirmAction: "delete"
- });
- win.center();
- win.open();
- win.addListener("close", () => {
- if (win.getConfirmed()) {
- this.fireDataEvent("deletePaymentMethod", this.getKey());
- }
- });
- }, this);
- menu.add(detelePMButton);
-
- optionsMenu.setMenu(menu);
-
- return menu;
}
}
});
diff --git a/services/static-webserver/client/source/class/osparc/desktop/paymentMethods/PaymentMethods.js b/services/static-webserver/client/source/class/osparc/desktop/paymentMethods/PaymentMethods.js
index bce3dbdaffe..e8c59ab2506 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/paymentMethods/PaymentMethods.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/paymentMethods/PaymentMethods.js
@@ -24,10 +24,13 @@ qx.Class.define("osparc.desktop.paymentMethods.PaymentMethods", {
this._setLayout(new qx.ui.layout.VBox(20));
this.getChildControl("intro-text");
- this.getChildControl("add-payment-methods-button");
+ const walletSelectorLayout = this.getChildControl("wallet-selector-layout");
+ const walletSelector = walletSelectorLayout.getChildren()[1];
this.getChildControl("payment-methods-list-layout");
+ this.getChildControl("add-payment-methods-button");
this.__fetchPaymentMethods();
+ walletSelector.addListener("changeSelection", () => this.__fetchPaymentMethods());
},
properties: {
@@ -47,13 +50,23 @@ qx.Class.define("osparc.desktop.paymentMethods.PaymentMethods", {
switch (id) {
case "intro-text":
control = new qx.ui.basic.Label().set({
- value: this.tr("Intro text about payment methods"),
+ value: this.tr("Credit cards used for payments in your personal Credit Account"),
font: "text-14",
rich: true,
wrap: true
});
this._add(control);
break;
+ case "wallet-selector-layout":
+ control = osparc.desktop.credits.Utils.createWalletSelectorLayout("write");
+ this._add(control);
+ break;
+ case "payment-methods-list-layout":
+ control = new qx.ui.container.Composite(new qx.ui.layout.VBox(10));
+ this._add(control, {
+ flex: 1
+ });
+ break;
case "add-payment-methods-button":
control = new qx.ui.form.Button().set({
appearance: "strong-button",
@@ -64,21 +77,22 @@ qx.Class.define("osparc.desktop.paymentMethods.PaymentMethods", {
control.addListener("execute", () => this.__addNewPaymentMethod(), this);
this._add(control);
break;
- case "payment-methods-list-layout":
- control = new qx.ui.container.Composite(new qx.ui.layout.VBox(10));
- this._add(control);
- break;
}
return control || this.base(arguments, id);
},
+ __getSelectedWalletId: function() {
+ const walletSelector = this.getChildControl("wallet-selector-layout").getChildren()[1];
+ const walletSelection = walletSelector.getSelection();
+ return walletSelection && walletSelection.length ? walletSelection[0].walletId : null;
+ },
+
__addNewPaymentMethod: function() {
- const wallets = osparc.store.Store.getInstance().getWallets();
- const myWallet = wallets.find(wallet => wallet.getMyAccessRights()["write"]);
- if (myWallet) {
+ const walletId = this.__getSelectedWalletId();
+ if (walletId) {
const params = {
url: {
- walletId: myWallet.getWalletId()
+ walletId: walletId
}
};
osparc.data.Resources.fetch("paymentMethods", "init", params)
@@ -90,32 +104,25 @@ qx.Class.define("osparc.desktop.paymentMethods.PaymentMethods", {
const listLayout = this.getChildControl("payment-methods-list-layout");
listLayout.removeAll();
- listLayout.add(new qx.ui.basic.Label().set({
- value: this.tr("Fetching Payment Methods"),
- font: "text-14",
- rich: true,
- wrap: true
- }));
-
- const promises = [];
- const wallets = osparc.store.Store.getInstance().getWallets();
- wallets.forEach(wallet => {
- if (wallet.getMyAccessRights() && wallet.getMyAccessRights()["write"]) {
- const params = {
- url: {
- walletId: wallet.getWalletId()
- }
- };
- promises.push(osparc.data.Resources.fetch("paymentMethods", "get", params));
- }
+ const fetchingLabel = new qx.ui.basic.Atom().set({
+ label: this.tr("Fetching Payment Methods"),
+ icon: "@FontAwesome5Solid/circle-notch/12",
+ font: "text-14"
});
- Promise.all(promises)
+ fetchingLabel.getChildControl("icon").getContentElement().addClass("rotate");
+ listLayout.add(fetchingLabel);
+
+ const walletId = this.__getSelectedWalletId();
+ const params = {
+ url: {
+ walletId: walletId
+ }
+ };
+ osparc.data.Resources.fetch("paymentMethods", "get", params)
.then(paymentMethods => {
- const allPaymentMethods = [];
- paymentMethods.forEach(paymentMethod => allPaymentMethods.push(...paymentMethod));
listLayout.removeAll();
- if (allPaymentMethods.length) {
- listLayout.add(this.__populatePaymentMethodsList(allPaymentMethods));
+ if (paymentMethods.length) {
+ listLayout.add(this.__populatePaymentMethodsList(paymentMethods));
} else {
listLayout.add(new qx.ui.basic.Label().set({
value: this.tr("No Payment Methods found"),
diff --git a/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletDetails.js b/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletDetails.js
index b6fbe4dd3e7..541b76a380c 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletDetails.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletDetails.js
@@ -94,8 +94,7 @@ qx.Class.define("osparc.desktop.wallets.WalletDetails", {
__openEditWallet: function() {
const wallet = this.__walletModel;
- const newWallet = false;
- const walletEditor = new osparc.desktop.wallets.WalletEditor(newWallet);
+ const walletEditor = new osparc.desktop.wallets.WalletEditor();
wallet.bind("walletId", walletEditor, "walletId");
wallet.bind("name", walletEditor, "name");
wallet.bind("description", walletEditor, "description");
@@ -127,14 +126,8 @@ qx.Class.define("osparc.desktop.wallets.WalletDetails", {
osparc.data.Resources.fetch("wallets", "put", params)
.then(() => {
osparc.FlashMessenger.getInstance().logAs(name + this.tr(" successfully edited"));
- osparc.store.Store.getInstance().invalidate("wallets");
- const store = osparc.store.Store.getInstance();
- store.reloadWallets();
- this.__walletModel.set({
- name: name,
- description: description,
- thumbnail: thumbnail || null
- });
+ const wallet = osparc.desktop.credits.Utils.getWallet(walletId);
+ wallet.set(params.data);
})
.catch(err => {
console.error(err);
diff --git a/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletEditor.js b/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletEditor.js
index 44ab7cf8d3a..299648f007b 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletEditor.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletEditor.js
@@ -18,7 +18,7 @@
qx.Class.define("osparc.desktop.wallets.WalletEditor", {
extend: qx.ui.core.Widget,
- construct: function(newWallet = true) {
+ construct: function() {
this.base(arguments);
this._setLayout(new qx.ui.layout.VBox(8));
@@ -29,7 +29,7 @@ qx.Class.define("osparc.desktop.wallets.WalletEditor", {
manager.add(title);
this.getChildControl("description");
this.getChildControl("thumbnail");
- newWallet ? this.getChildControl("create") : this.getChildControl("save");
+ this.getChildControl("save");
},
properties: {
@@ -63,7 +63,6 @@ qx.Class.define("osparc.desktop.wallets.WalletEditor", {
},
events: {
- "createWallet": "qx.event.type.Event",
"updateWallet": "qx.event.type.Event",
"cancel": "qx.event.type.Event"
},
@@ -108,20 +107,6 @@ qx.Class.define("osparc.desktop.wallets.WalletEditor", {
this._add(control);
break;
}
- case "create": {
- const buttons = this.getChildControl("buttonsLayout");
- control = new osparc.ui.form.FetchButton(this.tr("Create")).set({
- appearance: "strong-button"
- });
- control.addListener("execute", () => {
- if (this.__validator.validate()) {
- control.setFetching(true);
- this.fireEvent("createWallet");
- }
- }, this);
- buttons.addAt(control, 0);
- break;
- }
case "save": {
const buttons = this.getChildControl("buttonsLayout");
control = new osparc.ui.form.FetchButton(this.tr("Save"));
diff --git a/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletsList.js b/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletsList.js
index e94f4652f68..118f1f944d8 100644
--- a/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletsList.js
+++ b/services/static-webserver/client/source/class/osparc/desktop/wallets/WalletsList.js
@@ -38,13 +38,6 @@ qx.Class.define("osparc.desktop.wallets.WalletsList", {
flex: 1
});
- // Disabled until create a New Wallet is enabled
- /*
- const newWalletButton = this.__getCreateWalletSection();
- newWalletButton.setVisibility(osparc.data.Permissions.getInstance().canDo("user.wallets.create") ? "visible" : "excluded");
- this._add(newWalletButton);
- */
-
this.loadWallets();
},
@@ -109,25 +102,6 @@ qx.Class.define("osparc.desktop.wallets.WalletsList", {
return wallet;
},
- __getCreateWalletSection: function() {
- const createWalletBtn = new qx.ui.form.Button().set({
- appearance: "strong-button",
- label: this.tr("New Credit Account"),
- alignX: "center",
- icon: "@FontAwesome5Solid/plus/14",
- allowGrowX: false
- });
- createWalletBtn.addListener("execute", function() {
- const newWallet = true;
- const walletEditor = new osparc.desktop.wallets.WalletEditor(newWallet);
- const title = this.tr("Credit Account Details Editor");
- const win = osparc.ui.window.Window.popUpInWindow(walletEditor, title, 400, 250);
- walletEditor.addListener("createWallet", () => this.__createWallet(win, walletEditor.getChildControl("create"), walletEditor));
- walletEditor.addListener("cancel", () => win.close());
- }, this);
- return createWalletBtn;
- },
-
__getWalletsFilter: function() {
const filter = new osparc.filter.TextFilter("text", "walletsList").set({
allowStretchX: true,
@@ -211,8 +185,7 @@ qx.Class.define("osparc.desktop.wallets.WalletsList", {
return;
}
- const newWallet = false;
- const walletEditor = new osparc.desktop.wallets.WalletEditor(newWallet);
+ const walletEditor = new osparc.desktop.wallets.WalletEditor();
wallet.bind("walletId", walletEditor, "walletId");
wallet.bind("name", walletEditor, "name");
wallet.bind("description", walletEditor, "description");
@@ -225,38 +198,6 @@ qx.Class.define("osparc.desktop.wallets.WalletsList", {
walletEditor.addListener("cancel", () => win.close());
},
- __createWallet: function(win, button, walletEditor) {
- button.setFetching(true);
-
- const name = walletEditor.getName();
- const description = walletEditor.getDescription();
- const thumbnail = walletEditor.getThumbnail();
-
- const params = {
- data: {
- "name": name,
- "description": description || null,
- "thumbnail": thumbnail || null
- }
- };
- osparc.data.Resources.fetch("wallets", "post", params)
- .then(() => {
- const store = osparc.store.Store.getInstance();
- osparc.store.Store.getInstance().invalidate("wallets");
- store.reloadWallets()
- .then(() => this.loadWallets());
- })
- .catch(err => {
- console.error(err);
- const msg = err.message || this.tr("Something went wrong creating the Wallet");
- osparc.FlashMessenger.getInstance().logAs(msg, "ERROR");
- })
- .finally(() => {
- button.setFetching(false);
- win.close();
- });
- },
-
__updateWallet: function(win, button, walletEditor) {
button.setFetching(true);
@@ -280,9 +221,9 @@ qx.Class.define("osparc.desktop.wallets.WalletsList", {
};
osparc.data.Resources.fetch("wallets", "put", params)
.then(() => {
- osparc.store.Store.getInstance().invalidate("wallets");
- store.reloadWallets()
- .then(() => this.loadWallets());
+ osparc.FlashMessenger.getInstance().logAs(name + this.tr(" successfully edited"));
+ const wallet = osparc.desktop.credits.Utils.getWallet(walletId);
+ wallet.set(params.data);
})
.catch(err => {
console.error(err);
diff --git a/services/static-webserver/client/source/class/osparc/navigation/NavigationBar.js b/services/static-webserver/client/source/class/osparc/navigation/NavigationBar.js
index ac2f89f42e3..3631ca02d73 100644
--- a/services/static-webserver/client/source/class/osparc/navigation/NavigationBar.js
+++ b/services/static-webserver/client/source/class/osparc/navigation/NavigationBar.js
@@ -180,8 +180,8 @@ qx.Class.define("osparc.navigation.NavigationBar", {
case "logo-powered":
control = new osparc.ui.basic.PoweredByOsparc().set({
padding: 3,
- paddingTop: 1,
- maxHeight: this.self().HEIGHT - 2
+ paddingTop: 2,
+ maxHeight: this.self().HEIGHT - 5
});
this.getChildControl("left-items").add(control);
break;
diff --git a/services/static-webserver/client/source/class/osparc/navigation/UserMenuButton.js b/services/static-webserver/client/source/class/osparc/navigation/UserMenuButton.js
index 944ea5855f5..e4be559d903 100644
--- a/services/static-webserver/client/source/class/osparc/navigation/UserMenuButton.js
+++ b/services/static-webserver/client/source/class/osparc/navigation/UserMenuButton.js
@@ -100,10 +100,7 @@ qx.Class.define("osparc.navigation.UserMenuButton", {
break;
case "po-center":
control = new qx.ui.menu.Button(this.tr("PO Center"));
- control.addListener("execute", () => {
- const poCenterWindow = osparc.po.POCenterWindow.openWindow();
- poCenterWindow.openInvitations();
- }, this);
+ control.addListener("execute", () => osparc.po.POCenterWindow.openWindow(), this);
this.getMenu().add(control);
break;
case "preferences":
diff --git a/services/static-webserver/client/source/class/osparc/node/UpdateResourceLimitsView.js b/services/static-webserver/client/source/class/osparc/node/UpdateResourceLimitsView.js
index e4d9f83c04b..d17108534ab 100644
--- a/services/static-webserver/client/source/class/osparc/node/UpdateResourceLimitsView.js
+++ b/services/static-webserver/client/source/class/osparc/node/UpdateResourceLimitsView.js
@@ -100,7 +100,7 @@ qx.Class.define("osparc.node.UpdateResourceLimitsView", {
if (resourceKey === "RAM") {
value = osparc.utils.Utils.bytesToGB(value);
}
- const spinner = new qx.ui.form.Spinner(0, value, 200).set({
+ const spinner = new qx.ui.form.Spinner(0, value, 512).set({
singleStep: 0.1
});
const nf = new qx.util.format.NumberFormat();
diff --git a/services/static-webserver/client/source/class/osparc/po/Invitations.js b/services/static-webserver/client/source/class/osparc/po/Invitations.js
index 87e54f65668..28d59bf7010 100644
--- a/services/static-webserver/client/source/class/osparc/po/Invitations.js
+++ b/services/static-webserver/client/source/class/osparc/po/Invitations.js
@@ -104,7 +104,7 @@ qx.Class.define("osparc.po.Invitations", {
maximum: 1000,
value: 0
});
- form.add(extraCreditsInUsd, this.tr("Welcome Credits (US $)"));
+ form.add(extraCreditsInUsd, this.tr("Welcome Credits (US$)"));
const withExpiration = new qx.ui.form.CheckBox().set({
value: false
diff --git a/services/static-webserver/client/source/class/osparc/po/POCenter.js b/services/static-webserver/client/source/class/osparc/po/POCenter.js
index 37fa5ec69b0..9619b506847 100644
--- a/services/static-webserver/client/source/class/osparc/po/POCenter.js
+++ b/services/static-webserver/client/source/class/osparc/po/POCenter.js
@@ -27,45 +27,46 @@ qx.Class.define("osparc.po.POCenter", {
padding: 10
});
- const tabViews = this.__tabsView = new qx.ui.tabview.TabView().set({
+ const tabViews = new qx.ui.tabview.TabView().set({
barPosition: "left",
contentPadding: 0
});
tabViews.getChildControl("bar").add(osparc.desktop.credits.UserCenter.createMiniProfileView());
- const invitationsPage = this.__invitationsPage = this.__getInvitationsPage();
+ const invitationsPage = this.__getInvitationsPage();
tabViews.add(invitationsPage);
+ const productPage = this.__getProductPage();
+ tabViews.add(productPage);
+
this._add(tabViews);
},
members: {
- __tabsView: null,
- __invitationsPage: null,
-
__getInvitationsPage: function() {
const title = this.tr("Invitations");
const iconSrc = "@FontAwesome5Solid/envelope/22";
const page = new osparc.desktop.preferences.pages.BasePage(title, iconSrc);
page.showLabelOnTab();
- const overview = new osparc.po.Invitations();
- overview.set({
+ const invitations = new osparc.po.Invitations();
+ invitations.set({
margin: 10
});
- page.add(overview);
+ page.add(invitations);
return page;
},
- __openPage: function(page) {
- if (page) {
- this.__tabsView.setSelection([page]);
- }
- },
-
- openInvitations: function() {
- if (this.__invitationsPage) {
- this.__openPage(this.__invitationsPage);
- }
+ __getProductPage: function() {
+ const title = this.tr("Product Info");
+ const iconSrc = "@FontAwesome5Solid/info/22";
+ const page = new osparc.desktop.preferences.pages.BasePage(title, iconSrc);
+ page.showLabelOnTab();
+ const productInfo = new osparc.po.ProductInfo();
+ productInfo.set({
+ margin: 10
+ });
+ page.add(productInfo);
+ return page;
}
}
});
diff --git a/services/static-webserver/client/source/class/osparc/po/POCenterWindow.js b/services/static-webserver/client/source/class/osparc/po/POCenterWindow.js
index cc0e6cb0307..3302626089a 100644
--- a/services/static-webserver/client/source/class/osparc/po/POCenterWindow.js
+++ b/services/static-webserver/client/source/class/osparc/po/POCenterWindow.js
@@ -35,7 +35,7 @@ qx.Class.define("osparc.po.POCenterWindow", {
appearance: "service-window"
});
- const poCenter = this.__poCenter = new osparc.po.POCenter();
+ const poCenter = new osparc.po.POCenter();
this.add(poCenter);
},
@@ -46,13 +46,5 @@ qx.Class.define("osparc.po.POCenterWindow", {
accountWindow.open();
return accountWindow;
}
- },
-
- members: {
- __poCenter: null,
-
- openInvitations: function() {
- this.__poCenter.openInvitations();
- }
}
});
diff --git a/services/static-webserver/client/source/class/osparc/po/ProductInfo.js b/services/static-webserver/client/source/class/osparc/po/ProductInfo.js
new file mode 100644
index 00000000000..b84be372555
--- /dev/null
+++ b/services/static-webserver/client/source/class/osparc/po/ProductInfo.js
@@ -0,0 +1,49 @@
+/* ************************************************************************
+
+ osparc - the simcore frontend
+
+ https://osparc.io
+
+ Copyright:
+ 2023 IT'IS Foundation, https://itis.swiss
+
+ License:
+ MIT: https://opensource.org/licenses/MIT
+
+ Authors:
+ * Odei Maiz (odeimaiz)
+
+************************************************************************ */
+
+qx.Class.define("osparc.po.ProductInfo", {
+ extend: qx.ui.core.Widget,
+
+ construct: function() {
+ this.base(arguments);
+
+ this._setLayout(new qx.ui.layout.VBox(10));
+
+ this.__fetchInfo();
+ },
+
+ members: {
+ __fetchInfo: function() {
+ const params = {
+ url: {
+ productName: osparc.product.Utils.getProductName()
+ }
+ };
+ osparc.data.Resources.fetch("productMetadata", "get", params)
+ .then(respData => {
+ const invitationRespViewer = new osparc.ui.basic.JsonTreeWidget(respData, "product-metadata");
+ const container = new qx.ui.container.Scroll().set({
+ maxHeight: 500
+ });
+ container.add(invitationRespViewer);
+ this._add(container, {
+ flex: 1
+ });
+ });
+ }
+ }
+});
diff --git a/services/static-webserver/client/source/class/osparc/product/Utils.js b/services/static-webserver/client/source/class/osparc/product/Utils.js
index 94d24468a2e..062bdfe8616 100644
--- a/services/static-webserver/client/source/class/osparc/product/Utils.js
+++ b/services/static-webserver/client/source/class/osparc/product/Utils.js
@@ -129,7 +129,7 @@ qx.Class.define("osparc.product.Utils", {
const product = qx.core.Environment.get("product.name");
switch (product) {
case "s4l":
- logosPath = lightLogo ? "osparc/s4l_logo_short_white.svg" : "osparc/s4l_logo_short_black.svg";
+ logosPath = lightLogo ? "osparc/s4l_logo_white_short.svg" : "osparc/s4l_logo_black_short.svg";
break;
case "s4llite":
logosPath = lightLogo ? "osparc/s4llite-white.png" : "osparc/s4llite-black.png";
diff --git a/services/static-webserver/client/source/class/osparc/resourceUsage/OverviewTable.js b/services/static-webserver/client/source/class/osparc/resourceUsage/OverviewTable.js
deleted file mode 100644
index c5e317b7043..00000000000
--- a/services/static-webserver/client/source/class/osparc/resourceUsage/OverviewTable.js
+++ /dev/null
@@ -1,135 +0,0 @@
-/* ************************************************************************
-
- osparc - the simcore frontend
-
- https://osparc.io
-
- Copyright:
- 2023 IT'IS Foundation, https://itis.swiss
-
- License:
- MIT: https://opensource.org/licenses/MIT
-
- Authors:
- * Odei Maiz (odeimaiz)
-
-************************************************************************ */
-
-qx.Class.define("osparc.resourceUsage.OverviewTable", {
- extend: osparc.ui.table.Table,
-
- construct: function() {
- const model = this.__model = new qx.ui.table.model.Simple();
- const cols = this.self().COLUMNS;
- const colNames = [];
- Object.entries(cols).forEach(([key, data]) => {
- if (["wallet", "user"].includes(key) && !osparc.desktop.credits.Utils.areWalletsEnabled()
- ) {
- return;
- }
- colNames.push(data.title);
- });
- model.setColumns(colNames);
-
- this.base(arguments, model, {
- tableColumnModel: obj => new qx.ui.table.columnmodel.Resize(obj),
- statusBarVisible: false
- });
- this.makeItLoose();
-
- const columnModel = this.getTableColumnModel();
- columnModel.getBehavior().setWidth(this.self().COLUMNS.duration.pos, 70);
- columnModel.getBehavior().setWidth(this.self().COLUMNS.status.pos, 70);
- if (osparc.desktop.credits.Utils.areWalletsEnabled()) {
- columnModel.getBehavior().setWidth(this.self().COLUMNS.wallet.pos, 100);
- }
- columnModel.getBehavior().setWidth(this.self().COLUMNS.cost.pos, 60);
- },
-
- statics: {
- COLUMNS: {
- project: {
- pos: 0,
- title: osparc.product.Utils.getStudyAlias({firstUpperCase: true})
- },
- node: {
- pos: 1,
- title: qx.locale.Manager.tr("Node")
- },
- service: {
- pos: 2,
- title: qx.locale.Manager.tr("Service")
- },
- start: {
- pos: 3,
- title: qx.locale.Manager.tr("Start")
- },
- duration: {
- pos: 4,
- title: qx.locale.Manager.tr("Duration")
- },
- status: {
- pos: 5,
- title: qx.locale.Manager.tr("Status")
- },
- wallet: {
- pos: 6,
- title: qx.locale.Manager.tr("Credit Account")
- },
- cost: {
- pos: 7,
- title: qx.locale.Manager.tr("Cost")
- },
- user: {
- pos: 8,
- title: qx.locale.Manager.tr("User")
- }
- },
-
- respDataToTableData: async function(datas) {
- const newDatas = [];
- if (datas) {
- const cols = this.COLUMNS;
- for (const data of datas) {
- const newData = [];
- newData[cols["project"].pos] = data["project_name"] ? data["project_name"] : data["project_id"];
- newData[cols["node"].pos] = data["node_name"] ? data["node_name"] : data["node_id"];
- if (data["service_key"]) {
- const parts = data["service_key"].split("/");
- const serviceName = parts.pop();
- newData[cols["service"].pos] = serviceName + ":" + data["service_version"];
- }
- if (data["started_at"]) {
- const startTime = new Date(data["started_at"]);
- newData[cols["start"].pos] = osparc.utils.Utils.formatDateAndTime(startTime);
- if (data["stopped_at"]) {
- const stopTime = new Date(data["stopped_at"]);
- const durationTime = stopTime - startTime;
- newData[cols["duration"].pos] = osparc.utils.Utils.formatMilliSeconds(durationTime);
- }
- }
- newData[cols["status"].pos] = qx.lang.String.firstUp(data["service_run_status"].toLowerCase());
- if (osparc.desktop.credits.Utils.areWalletsEnabled()) {
- newData[cols["wallet"].pos] = data["wallet_name"] ? data["wallet_name"] : "-";
- }
- newData[cols["cost"].pos] = data["credit_cost"] ? data["credit_cost"] : "-";
- if (osparc.desktop.credits.Utils.areWalletsEnabled()) {
- const user = await osparc.store.Store.getInstance().getUser(data["user_id"]);
- newData[cols["user"].pos] = user ? user["label"] : data["user_id"];
- }
- newDatas.push(newData);
- }
- }
- return newDatas;
- }
- },
-
- members: {
- __model: null,
-
- addData: async function(datas) {
- const newDatas = await this.self().respDataToTableData(datas);
- this.setData(newDatas);
- }
- }
-});
diff --git a/services/static-webserver/client/source/class/osparc/store/Store.js b/services/static-webserver/client/source/class/osparc/store/Store.js
index 36efd809eb9..8b04e221502 100644
--- a/services/static-webserver/client/source/class/osparc/store/Store.js
+++ b/services/static-webserver/client/source/class/osparc/store/Store.js
@@ -129,6 +129,11 @@ qx.Class.define("osparc.store.Store", {
nullable: true,
event: "changeCreditPrice"
},
+ productMetadata: {
+ check: "Object",
+ init: {},
+ nullable: true
+ },
permissions: {
check: "Array",
init: []
diff --git a/services/static-webserver/client/source/class/osparc/ui/basic/JsonTreeWidget.js b/services/static-webserver/client/source/class/osparc/ui/basic/JsonTreeWidget.js
index 61e26e4a33e..24453fe0bf8 100644
--- a/services/static-webserver/client/source/class/osparc/ui/basic/JsonTreeWidget.js
+++ b/services/static-webserver/client/source/class/osparc/ui/basic/JsonTreeWidget.js
@@ -39,7 +39,8 @@ qx.Class.define("osparc.ui.basic.JsonTreeWidget", {
const prettyJson = JSON.stringify(data, null, " ").replace(/\n/ig, "
");
this.base(arguments, prettyJson);
this.set({
- rich: true
+ rich: true,
+ selectable: true
});
}
});
diff --git a/services/static-webserver/client/source/class/osparc/ui/basic/PoweredByOsparc.js b/services/static-webserver/client/source/class/osparc/ui/basic/PoweredByOsparc.js
index 3dbb4b4fcec..bee9711f0d2 100644
--- a/services/static-webserver/client/source/class/osparc/ui/basic/PoweredByOsparc.js
+++ b/services/static-webserver/client/source/class/osparc/ui/basic/PoweredByOsparc.js
@@ -57,8 +57,8 @@ qx.Class.define("osparc.ui.basic.PoweredByOsparc", {
break;
case "logo": {
control = new qx.ui.basic.Image().set({
- width: 42,
- height: 33,
+ width: 32,
+ height: 24,
scale: true
});
const logoContainer = new qx.ui.container.Composite(new qx.ui.layout.HBox().set({
diff --git a/services/static-webserver/client/source/class/osparc/utils/LibVersions.js b/services/static-webserver/client/source/class/osparc/utils/LibVersions.js
index defb19d5feb..f560acbf1fb 100644
--- a/services/static-webserver/client/source/class/osparc/utils/LibVersions.js
+++ b/services/static-webserver/client/source/class/osparc/utils/LibVersions.js
@@ -67,7 +67,7 @@ qx.Class.define("osparc.utils.LibVersions", {
return {
name: name,
- version: commitId.substring(0, 7),
+ version: commitId ? commitId.substring(0, 7) : "",
url: remoteUrl
};
},
diff --git a/services/static-webserver/client/source/resource/osparc/s4l_logo_short_black.svg b/services/static-webserver/client/source/resource/osparc/s4l_logo_black_short.svg
similarity index 100%
rename from services/static-webserver/client/source/resource/osparc/s4l_logo_short_black.svg
rename to services/static-webserver/client/source/resource/osparc/s4l_logo_black_short.svg
diff --git a/services/static-webserver/client/source/resource/osparc/s4l_logo_short_white.svg b/services/static-webserver/client/source/resource/osparc/s4l_logo_white_short.svg
similarity index 100%
rename from services/static-webserver/client/source/resource/osparc/s4l_logo_short_white.svg
rename to services/static-webserver/client/source/resource/osparc/s4l_logo_white_short.svg
diff --git a/services/web/server/src/simcore_service_webserver/projects/db.py b/services/web/server/src/simcore_service_webserver/projects/db.py
index d700f69d5db..45e1c1f0d54 100644
--- a/services/web/server/src/simcore_service_webserver/projects/db.py
+++ b/services/web/server/src/simcore_service_webserver/projects/db.py
@@ -31,9 +31,6 @@
from servicelib.aiohttp.application_keys import APP_DB_ENGINE_KEY
from servicelib.logging_utils import get_log_record_extra, log_context
from simcore_postgres_database.errors import UniqueViolation
-from simcore_postgres_database.models.projects_node_to_pricing_unit import (
- projects_node_to_pricing_unit,
-)
from simcore_postgres_database.models.projects_nodes import projects_nodes
from simcore_postgres_database.models.projects_to_products import projects_to_products
from simcore_postgres_database.models.wallets import wallets
@@ -807,27 +804,14 @@ async def get_project_node_pricing_unit_id(
project_uuid: ProjectID,
node_uuid: NodeID,
) -> PricingPlanAndUnitIdsTuple | None:
+ project_nodes_repo = ProjectNodesRepo(project_uuid=project_uuid)
async with self.engine.acquire() as conn:
- result = await conn.execute(
- sa.select(
- projects_node_to_pricing_unit.c.pricing_plan_id,
- projects_node_to_pricing_unit.c.pricing_unit_id,
- )
- .select_from(
- projects_nodes.join(
- projects_node_to_pricing_unit,
- projects_nodes.c.project_node_id
- == projects_node_to_pricing_unit.c.project_node_id,
- )
- )
- .where(
- (projects_nodes.c.project_uuid == f"{project_uuid}")
- & (projects_nodes.c.node_id == f"{node_uuid}")
- )
+ output = await project_nodes_repo.get_project_node_pricing_unit_id(
+ conn, node_uuid=node_uuid
)
- row = await result.fetchone()
- if row:
- return PricingPlanAndUnitIdsTuple(row[0], row[1])
+ if output:
+ pricing_plan_id, pricing_unit_id = output
+ return PricingPlanAndUnitIdsTuple(pricing_plan_id, pricing_unit_id)
return None
async def connect_pricing_unit_to_project_node(
@@ -838,32 +822,13 @@ async def connect_pricing_unit_to_project_node(
pricing_unit_id: PricingUnitId,
) -> None:
async with self.engine.acquire() as conn:
- result = await conn.scalar(
- sa.select(projects_nodes.c.project_node_id).where(
- (projects_nodes.c.project_uuid == f"{project_uuid}")
- & (projects_nodes.c.node_id == f"{node_uuid}")
- )
- )
- project_node_id = parse_obj_as(int, result) if result else 0
-
- insert_stmt = pg_insert(projects_node_to_pricing_unit).values(
- project_node_id=project_node_id,
+ project_nodes_repo = ProjectNodesRepo(project_uuid=project_uuid)
+ await project_nodes_repo.connect_pricing_unit_to_project_node(
+ conn,
+ node_uuid=node_uuid,
pricing_plan_id=pricing_plan_id,
pricing_unit_id=pricing_unit_id,
- created=sa.func.now(),
- modified=sa.func.now(),
)
- on_update_stmt = insert_stmt.on_conflict_do_update(
- index_elements=[
- projects_node_to_pricing_unit.c.project_node_id,
- ],
- set_={
- "pricing_plan_id": insert_stmt.excluded.pricing_plan_id,
- "pricing_unit_id": insert_stmt.excluded.pricing_unit_id,
- "modified": sa.func.now(),
- },
- )
- await conn.execute(on_update_stmt)
#
# Project ACCESS RIGHTS/PERMISSIONS