Opening an org-scoped deployment in the Playground pre-selected the bare
model name (e.g. `qwen3-0.6b`), which never matched the org-namespaced
option the `/v1/models` dropdown actually lists (`org1/qwen3-0.6b`), so
the selection showed an unmatched value.
Resolve the owning org from the route row's `owner_principal_id` (falling
back to the org the caller is currently acting under for the admin "All"
view) and reconstruct the same `{org}/{name}` id the server reports. The
platform org carries no prefix, matching the server's behaviour.
- user.ts: add `getOrgById`/`getCurrentOrg` that scan both org caches
(`organizationList` + admin-only `allOrganizations`) with string-
normalised id comparison; `getOrgNameById` is now a thin wrapper.
Helpers accept `undefined` so optional row fields type-check.
- use-open-playground: build the prefix from the resolved org record,
keying the skip-prefix decision off `is_platform`.
- RouteItem: declare the `owner_principal_id` the list API returns.
- RouteTargetFormItem: drop the duplicate `overridden_model_name`
declaration that TS flagged as a duplicate identifier.
Two follow-ups to the GPU Service gating:
- In Personal-Org view the access extension was stripping
canSeeAdmin/canSeeOrgAdmin but leaving canSeeGpuService at its
admin-shortcut value, so platform admins switched into Personal
still saw the menu even though Personal scope can't host a K8s
cluster. Mirror the probe result through sessionStorage so the
extension can fall back to the strict cluster-availability signal
in that branch.
- The empty-state CTA now reads 'Add a Kubernetes Cluster' and, on
click, opens the cluster-create flow with Kubernetes preselected
via clusterSession.providerHint. ClusterCreate consumes the hint
on mount: it seeds extraData.provider and starts at the configure
step instead of the provider catalog, so the user lands one click
closer to the form they actually need.
OwnerPrincipalIdField was reading currentClusterAtom, which no callers
ever wrote — so every form submission ended up with
owner_principal_id=null and the backend rejected non-admins with
"Only platform admin can create global ...". Pin the field to
currentOrganizationId instead (the Org the caller is acting under).
Cluster ownership is the wrong source: a cluster_access grant lets one
Org schedule on another Org's cluster, but the new resource is still
owned by the caller's Org, and the backend enforces
owner_principal_id == ctx.current_principal_id.
Also drops the dead currentClusterAtom.
Tracks the gpustack identity-consolidation effort: the unified
``principals`` table's URL-safe identifier column was renamed
``slug`` → ``name`` (with the legacy display ``name`` → ``display_name``).
The enterprise plugin's persisted org caches (``organizationList``,
``allOrganizations`` in localStorage) now write ``name`` instead of
``slug`` on each Organization row.
``getCurrentOrgNamespace`` reads those caches to compose the k8s
namespace (``gpustack-{name}``) for GPU-instance / storage CRDs.
Updated to read ``item.name`` so namespace resolution stays in sync
with what the enterprise plugin writes — otherwise every gpu-service
write would fall through to ``gpustack-default`` even when the user
has an Org context.
Stale ``slug`` references in surrounding comments also retitled to
``name`` to avoid divergence between code and prose. The namespace
format itself (``gpustack-{...}``) is unchanged — only the column it
sources is.
The legacy ``getCurrentOrganizationId`` helper returned the raw numeric
org id from localStorage, which the GPU-service / storage call sites
then used as the K8s namespace path segment — producing requests like
``/v2/clusters/1/proxy/.../namespaces/5/instances``. The backend
creates a namespace named ``gpustack-{slug}`` (matching
``get_namespace_name``), so the request hit a namespace that doesn't
exist.
Replace the helper with ``getCurrentOrgNamespace`` that:
- prefers the Org the caller is acting under (numeric
``currentOrganizationId`` from localStorage, slug looked up in the
persisted ``organizationList`` or ``allOrganizations`` cache);
- falls back to the selected cluster's owner Org slug for the admin
"All" view, where there is no explicit Org context but the resource
still has to land in some Org's namespace — call sites pass
``currentCluster?.owner_principal_id`` through;
- falls back to ``gpustack-default`` only if neither path resolves a
slug (first load before any cache hydrates, etc.).
``ClusterListItem`` is widened with the optional ``owner_principal_id``
field so TypeScript accepts the fallback argument; the backend has
been returning it via ``ClusterPublic`` all along.