Managers start in a loading state and gate the empty state on the
cluster/worker fetch, so the 'no clusters/workers' guidance no longer
flashes before the real infra data lands.
My Models empty state now guides managers to create a cluster/worker or
deploy a model (queries gated to canSeeAdmin/canSeeOrgAdmin); plain users
keep the consumer copy. Add an Experimental badge to the GPU-instance
cluster type card.
Remove the cluster-detail route and the name-link navigation; the
cluster name is now plain text. Mount the plugin-provided
clusterDetail.AccessDrawer so the enterprise row action can open it.
The GPU Instances / Storage tabs built the filter object inline (in
baseRequest and again in the extra tab's props), duplicating the
conditional-spread logic and handing the enterprise Organization tab a fresh
object every render — which refired its fetch effect on any state change.
Hoist it into one memoized ``breakdownFilters`` shared by the chart request
and the extra tab, mirroring the Tokens tab's stable-filters shape.
- change the breakdown extra-tab contract from a `useVisible()` hook to a plain
`isVisible(ctx)` function so the host no longer calls hooks inside a loop
(token breakdown-tabs + resource GPU/Storage tabs)
- resource export: drop the time portion of the date bucket by slicing the ISO
string instead of re-parsing with dayjs (avoids a timezone day-shift)
- token & resource filter bars render a plugin slot (UsageFilterBar /
ResourceUsageFilterBar) for the organization / user-group filters
- breakdown tabs read plugin-provided extra sub-tabs (breakdownExtraTabs /
resourceBreakdownExtraTabs) so the enterprise plugin can inject the
Organization sub-tab
- thread the org / user-group filter state through the token filter hook and
the resource tabs / breakdown tables / summary / export dialog
- standardize the organization label on the principal name; resource export
date formatted to date-only
The basic form seeds a default cluster on open, which fired the full cluster-change handler and triggered an evaluate request before any model was picked. Split off a seed callback that refreshes scoped options without evaluating, and guard the evaluate handler with a model-selected check as a backstop.
Read creator_id / creator_name / creator_deleted from the breakdown item
root into the flat user_* fields, so the GPU Instances / Storage chart
export marks a deleted owner independently of the grouped instance/volume.
Deleted users / models / API keys / instances / volumes now sink below the
live entries in each usage tab's filter, while the current account stays on
top and live entries keep their order (stable sort).
Group the GPU Instances and Storage chart exports by date+instance /
date+volume, add Instance/Storage and (org-admin only) User columns, and
mark deleted instances/volumes and their owner user independently in both
the preview and the exported sheet.
Cluster pickers listed every visible cluster, which includes clusters
shared with all authenticated users (e.g. the default org's "shared with
everyone" clusters). A member of a custom org could then pick another
org's cluster for a model deployment or a new worker.
Request only the current org's own clusters (mine=true) for:
- the model deploy picker (models page),
- the deploy-from-model-file picker (model files page), and
- the add-worker picker.
These resources are owner-scoped on the backend, so the model list and
the worker table (including its cluster-name column) only reference
own-org clusters. The add-model-file worker cascader is already own-org
via the owner-scoped worker list. Platform admin in the "All" view
bypasses mine and is scoped instead by the org picker.
The storage create form fetched persistent-volume types once with no
tenant scope, so in the platform-admin "All" view the dropdown listed
every org's types and never re-scoped when the create-scope picker
retargeted the form — letting an org with no cluster-access grant pick
another org's types. The inline "add storage" sub-drawer in the instance
create form had the same gap plus a redundant org picker that could
retarget the storage away from the instance's org.
Both surfaces now pin the storage-type list to the chosen org (via the
X-Organization-Id header); the sub-drawer inherits the instance's scope
and hides its own picker. Page/table listings stay unscoped so every
row's type label still renders.
Add a Clone action to instance template cards. It reuses the create
flow prefilled from the source, dropping the source id and ownership so
the backend assigns a fresh id and scopes the copy to the caller's own
principal. Clone is offered on every visible template — including
admin-curated Global rows a non-admin can otherwise only view — so
users can fork a Global preset into their own organization without
admin involvement.
The prefilled name defaults to a `-clone` suffix (trimmed to the
63-char limit) so cloning within the same scope doesn't collide on the
unique name. Edit/Delete remain gated on ownership; Clone is always
available.
The Instance Templates management page requested the `mine` list scope,
which drops admin-curated Global templates for non-admin callers. The
Inference Backend page uses the default scope and shows Global + own
rows, so the two pages behaved inconsistently.
Use the default scope on the templates page so Global templates are
visible alongside the caller's own, and gate Edit/Delete on the card so
Global rows a non-admin can't modify render read-only.
Replace unreliable Form.useWatch on the unregistered gpuInstanceOptions
path with explicit shared clusterType state. Fixes the type selector
being unclickable and flickering, and ensures a model cluster no longer
submits a stale gpuInstanceOptions payload.
The deploy form's cluster select could open blank even when a valid
(default) cluster exists for the current scope. The open handler seeds
the cluster once, but that seed can be empty (options not loaded yet) or
point outside the resolved scope (the scope id and the option list both
settle after mount, and switching scope re-scopes the list), and nothing
re-picked afterwards — so the field stayed empty.
Make the form enforce the invariant directly: whenever the scope or the
available options change, if the current selection is empty or not among
the options, fall back to the scope's default cluster (then a Ready one,
then the first). A still-valid selection is left untouched.
Switch all echarts-based chart imports to the dedicated core-ui charts
entry so echarts stays out of the synchronous entry bundle and loads
only on chart routes. Requires @gpustack/core-ui with the ./charts export.