feat(cloud): complete Workspace settings navigation

This commit is contained in:
dadachann
2026-07-25 13:51:37 +08:00
parent 64e772e32d
commit d3f08a90b1
11 changed files with 222 additions and 36 deletions
@@ -106,12 +106,21 @@ class WorkspacesRouterGroup(group.RouterGroup):
if account is None:
return self.http_status(401, 'invalid_authentication', 'Account not found')
workspace = await self.ap.workspace_service.get_workspace(request_context.workspace_uuid)
plan_name: str | None = None
resolver = getattr(self.ap, 'entitlement_resolver', None)
if workspace.source == WorkspaceSource.CLOUD_PROJECTION.value and resolver is not None:
entitlement = await resolver.resolve(
workspace.uuid,
minimum_revision=request_context.entitlement_revision,
)
plan_name = entitlement.plan_name
return self.success(
data={
'workspace': _workspace_payload(workspace),
'membership': _membership_payload(membership, email=account.user),
'permissions': sorted(request_context.workspace.permissions),
'placement_generation': request_context.placement_generation,
'plan_name': plan_name,
}
)
+7 -3
View File
@@ -18,10 +18,11 @@ class EntitlementUnavailableError(RuntimeError):
class EntitlementSnapshot(pydantic.BaseModel):
"""Plan-agnostic capability projection consumed by open-source Core.
"""Capability projection consumed by open-source Core.
Product and billing names deliberately do not appear here. The closed
Control Plane maps subscriptions to these generic features and limits.
Admission and quota decisions use normalized features/limits rather than
product plan names. ``plan_name`` is signed display metadata for Cloud UI
only and must never drive authorization or quota enforcement.
"""
model_config = pydantic.ConfigDict(frozen=True, extra='forbid')
@@ -34,6 +35,9 @@ class EntitlementSnapshot(pydantic.BaseModel):
expires_at: int = pydantic.Field(gt=0)
features: dict[str, bool] = pydantic.Field(default_factory=dict)
limits: dict[str, int] = pydantic.Field(default_factory=dict)
# Signed display metadata for Cloud UI only. Admission and quota decisions
# must continue to use generic ``features`` and ``limits`` exclusively.
plan_name: str | None = pydantic.Field(default=None, min_length=1, max_length=128)
@pydantic.field_validator('features')
@classmethod
@@ -281,6 +281,17 @@ class WorkspaceCollaborationService:
*,
session: AsyncSession | None = None,
) -> list[WorkspaceMemberView]:
if session is None:
current_session = getattr(self.ap.persistence_mgr, 'current_session', lambda: None)()
tenant_uow: typing.Any = getattr(self.ap.persistence_mgr, 'tenant_uow', None)
if current_session is None and callable(tenant_uow):
async with tenant_uow(workspace_uuid) as workspace_uow:
return await self.list_members(
workspace_uuid,
actor,
session=workspace_uow.session,
)
async def operation(active_session: AsyncSession) -> list[WorkspaceMemberView]:
await self._load_actor(active_session, workspace_uuid, actor)
statement = (