Applied via scripts/rebrand across UI labels, page title, and all
locale strings (en/zh/ja/ru/tr). Functional references untouched:
npm package, URLs, paths, JS identifiers, GPUSTACK_ constants, and the
X-GPUStack-Model header. The backend-contract startsWith string in
llmodels/hooks keeps "GPUStack server" to stay matchable. Logo images
intentionally left unchanged for now.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the plugin-fed billing price-badge slots to the model cards and the
gpu-instance-type / storage cards so the enterprise plugin can surface
charge / pay indicators inline.
Reserve a stable block for the empty/loading state (matching the NoResult
height) and fade the empty state in, so entering a list page no longer jumps
between spinner, empty result, and rows. Apply scroll-table to the antd main
lists and pass emptyMinHeight to the SealTable-based lists.
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.
Resource names are unique per owning principal, so different users
can legally create same-name GPU instances, templates, SSH public
keys, storage volumes and storage types — but the admin's
cross-tenant lists and the instance-create template picker rendered
them indistinguishably.
- Group the template picker by owning scope: Your Templates, Global
Templates, then — admin cross-tenant view only — one group per
owner. A single group renders flat without headers. A plugin can
take over grouping via hooks.useTemplateOwnerGroups when its
principal model scopes templates beyond USER owners; a plugin
without the hook keeps the flat list.
- Add an admin-only Creator column to the Instances, SSH Public
Keys, Storage and Storage Types tables, sitting right before
Created (mirroring the API Keys page convention). Resolves
creator_id to a username via the user directory, falling back to
owner_principal_id for legacy rows — a personal-scope row's
creator IS its owner.
- Tag template cards with their owner scope (Global / username) for
admins.
The Creator column and the card tag defer to a plugin that registers
the page's list columns / the OwnerScopeTag slot. Requires the
gpustack server change that records creator_id on templates, SSH
public keys and storage types.
Add `usePluginListColumns(pageKey)` so a registered plugin can splice
extra columns into a list page's columns hook keyed by page id. The
slot is wired into 11 list pages (Models, Model Routes, Clusters,
Cloud Credentials, MaaS Providers, Model Files, Model Instances,
GPU Service Instances / Public Keys / Storage / Storage Types) so any
consumer of the seam reaches every org-scoped list without per-page
plumbing.
The slot accepts either a static array or a hook function — the hook
form lets the registrant use React hooks to decide visibility without
the host having to evaluate it. Columns carry a `placement`:
`after-name` is the default (sits next to the row's identifying
column); `before-time` / `before-operation` are kept for back-compat
with the existing per-page `modelRoutes.extraColumns` slot.
SealTable-grid pages (Models / Model Routes / Clusters) absorb the
plugin column's span by shrinking the widest right-side columns so
the 24-unit grid stays balanced. Model Routes' `CREATE_TIME_MIN_SPAN`
drops from 3 to 2 because with two plugin columns active the grid
was 1 unit over and the action dropdown wrapped onto a new row; the
date string ellipsizes cleanly at the lower min.
ListItem types for Model Instances, MaaS Providers, Cloud Credentials,
and Model Files gain an optional `owner_principal_id` field — the
backend already emits it (denormalized from the parent resource), the
TS types just hadn't declared it.
The GPU Instances page splices the plugin column right before the
Cluster column (rather than after Name); the two read together since
Cluster narrows down to one Org.
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.
`${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.
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.