The "GPU Instance Service" toggle on the cluster create form only said
what it enables. The exclusion side — once flipped, the cluster drops
out of the model-deploy picker — was invisible until the user went
looking for the cluster elsewhere. Extend the tip to spell that out
in all five locales, using the canonical "GPU instance service" /
"model service" product terms.
The deploy form's cluster dropdown fetched every visible cluster, so
clusters configured for GPU-service (k8s_options.gpu_instance_options
set) showed up as deployable targets even though deployments cannot
run on them.
Pass gpu_instance_enabled=false on the cluster-list query so the
backend returns only model-deployment clusters. GPU-instance creation
does not pick a cluster directly (it is derived from the instance
type), so no symmetric change is needed there.
The host's access function is memoized on `initialState` and runs
exactly once per commit. Plugins that maintain identity-scoped
caches the access predicate reads from (e.g. an org context cache)
had no way to seed those caches synchronously before the caller's
`setInitialState({currentUser: ...})` fired — any post-commit hydrate
couldn't widen the predicate, leaving the sidebar in a stale view
until the next identity change.
Add `LoginPlugin.onUserFetched(userInfo, ctx)` and call it inside
`fetchUserInfo` after the server confirms identity but before
returning. Also commit the identity to `userAtom` storage here so
localStorage's identity marker is in lockstep with whatever caches
the plugin seeds — the predicate's first evaluation then sees a
consistent view rather than the prior session's data.
Errors thrown from the hook are swallowed and logged; they never
block fetchUserInfo.
Add an Organizations menu entry (admin-only) that routes to an upsell
page explaining the multi-tenancy module, and surface disabled
IP Access Control / Quota Limit items in the API Key dropdown with a
tooltip pointing at the enterprise edition. Both placeholders are
shadowed by the enterprise plugin at build time: the route merger
removes the OSS Organizations entry by name, and the dropdown skips
each placeholder when the same key is contributed via configActions.
initClusterId picked the platform default cluster without considering
the form's organization_id, so opening the catalog deploy form in the
admin all-scope view could seed a cluster that the (org-filtered)
dropdown then hides — submit failed with "Cluster not found".
Mirror the same scope-aware selection deploy-modal already does:
filter clusterList by owner_principal_id when organization_id is set
before picking default/ready/first.
Return number | undefined honestly (the picked org may own no
clusters, or clusterList may still be loading) and guard the
open-handler caller so undefined doesn't flow into fetchSpecData /
getGPUOptionList — the user resolves the empty state by picking an
org that owns clusters.
Add the Usage page with Summary / Tokens / GPU Instances / Storage / Resource
Events tabs over the new metering endpoints: per-resource breakdowns with
date / scope / user / resource filters, trend charts, server-side sortable
tables (GPU-Hours, Instance-Hours, GB-Days, GB-Hours), Excel export with an
in-dialog preview, and KPI cards with help tooltips explaining each metric.
MaaS-only users (no Kubernetes cluster and no resource events) get a
tokens-only view with the tab bar dropped; GPU Service / the full page unlock
for admins, cluster owners, or anyone who has run a resource. Instance-type
rows reuse the GPU Instances list styling, and deleted users / instances /
volumes are flagged in breakdowns and filters.
Replace the raw flavor slug in the GPU Instances list with the product name
("<product> x <count>") plus an info-icon popover that breaks the spec down by
category (GPU / CPU / Memory / Disk): per-card VRAM, whole-instance CPU/RAM,
system / ephemeral / persistent disks (persistent size resolved from the
referenced PV). Extract the cell into a shared InstanceTypeCell and centralize
memory formatting in formatMemoryDisplay so the GPU Instances list and the
Usage tab render identical sizes.
The "Filter by creator" dropdown was listing every user in the
system. For an Org owner viewing an Org-scoped key list, picking
a user outside the Org always produced an empty result. Pass
scope=current_org to `/user-directory` so the picker matches the
list's actual scope. BE drops the param when the request has no
Org context, so callers without an Org are unchanged.
Plugins can supply `LoginPlugin.resolveDefaultPath` to override the
post-login redirect target. checkDefaultPage consults the hook and
uses the returned path when non-null; otherwise falls back to the
existing admin/non-admin defaults. The lookup runs in parallel with
the IS_FIRST_LOGIN read. Applies to both first-login and
subsequent-login flows.
`${values.spec?.resources?.cpu}` turned an unset cpu into the literal
string "undefined", which fails k8s quantity validation. Omit cpu when
it has no value instead.
- bar-chart: barMinWidth so hour-granularity bars stay visible with gaps.
- pie-chart: single legend-beside-pie layout, value (percent%) tooltip,
2-decimal rounding for the Summary donut.
The data layer the resource tabs build on:
- apis/resource.ts: adapter over the unified metered_usage read API
(resource/gpu-instances/storage/summary/events breakdowns), flattening
the server's generic shape into the per-tab item shape.
- hooks/use-resource-meta.ts: loads creators/instances/volumes filter
options for the current scope.
- utils/time-buckets.ts: day/week/month/hour bucket keys + range fill.
- utils/export-breakdown.ts: derive Excel columns from antd table specs.
Switch the gate from `currentUser.is_admin` to `access.canSeeOrgAdmin`
so Org owners get the same all-keys view (Creator column, creator
filter, `user_id: '*'` default) that platform admins have. Mirrors
the BE's "platform admin OR current-Org owner" gate on listing every
key in scope.
The user picker now fetches `/user-directory` instead of the
admin-only `/users` endpoint, which would 403 Org owners. Rename the
filter placeholder from the misnamed `models.table.filterByName` to
`common.filter.byCreator`.
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.
Add the organization picker to the benchmark create form (platform-admin
"all organizations" view). Fetch the chosen org's clusters and keep only
those it owns; refetch the org-scoped model / instance list on org change;
clear a stale cluster or target when switching to an org with no clusters.
Add the organization picker to the model deployment and model-file
download forms (platform-admin "all organizations" view). Filter the
cluster / worker pickers to clusters the chosen org owns, so the created
resource's owner stays aligned with where it runs and a cross-org cluster
can't be selected. Carry owner ids on the cluster / worker options to
drive the filter.
In the platform-admin "all organizations" view, add an organization picker
(the CreateOrgScopeField slot) to the SSH public key, storage type,
storage, GPU instance and instance-template create forms, placed below the
name / display-name fields. Drop the hidden owner field and let the owner
derive from the request context, matching the model-route form.
For GPU instances, scope the instance-type list to clusters the chosen org
owns (client-side, by cluster owner) so an instance can't be scheduled onto
another org's cluster; when the org owns none, show "no instance type
available" and clear the selection, cluster and CPU/memory fields. Instance
templates gain a Global level (NULL owner) and an owner tag on the card.
Add `extraResponseInterceptors` next to the existing request-interceptor
seam and wire it into the request config, so build-time tooling can react
to responses (e.g. clear a one-shot request-scoped hint after a write
completes). Default is an empty list — no behavior change by default.