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 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 CAS / OIDC / SAML callbacks now redirect to `/login?error=<code>`
on failure instead of letting the browser land on a raw JSON error
page, so the actionable copy reaches the user. Two codes are
recognised:
* `source_conflict` — incoming SSO username collides with an existing
account from a different source. Message points the user at an
administrator to link or convert.
* `auth_failed` — anything else (bad ticket, expired state, IdP
unreachable, malformed response). Generic message: try again or
contact the administrator.
On mount the login form picks up the `?error=` query param, maps it
through a small `messageIdByCode` table to an i18n key, and routes
the result through the existing auth-error toast. Unknown codes are
silently ignored so a future server release adding a code doesn't
render a bare key. The query param is cleared via
`history.replaceState` so a refresh doesn't re-fire the toast.
Strings added to all five locales.
The add / edit user drawer now exposes a Source select (Local / OIDC /
SAML / CAS) so an admin can flip an existing account between Local
password and an external IdP without touching the database. Mirrors
the matching `PUT /v1/users/{id}` change on the backend.
Password field follows the selected source:
* Hidden when source != Local — those users authenticate via the IdP
and a local password row would be a /login bypass.
* Required when CREATE-with-Local, or when EDIT is switching an SSO
user back to Local (the backend rejects SSO -> Local without a
fresh password to avoid locking the user out of /login).
* Optional when editing an already-Local user, matching today's
behaviour.
A switch in EDIT mode surfaces a tip explaining the side effect
(password cleared / new password required) so the consequence isn't
hidden. The Source select is disabled on self-edit — same guard the
role column already uses — so an admin can't lock themselves out by
flipping their own row to an external source.
Strings are added to all five locales; the IdP protocol acronyms
(OIDC / SAML / CAS) render verbatim and don't need translation keys.
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.
Compact pill (GitHub icon | star count) placed left of the version
display. Star count is fetched from the GitHub API once per 24h and
cached in localStorage; the button hides entirely when no count is
available (e.g. air-gapped deployments), matching the existing update-
check graceful-degradation pattern. Fetch is bounded by a 4s abort
timeout to avoid hanging requests offline.
- Event type: trim the dropdown to the four types that are actually emitted
(Created / Started / Stopped / Deleted) — updated/attached/detached were
never recorded — and actually send the selection (event_types) to the API,
which previously dropped it so the filter did nothing.
- Add a resource-name text filter (debounced, case-insensitive substring).
- Remove the "filter by user" select: the events list has no User column.
Scope still applies (members see only their own events).
Replace the GPU Service switch (with the small "cannot be used for
model service" caption) with a two-card radio selector that makes the
choice between Model Service and GPU Service explicit at a glance.
- Render two cards styled to match the existing SwitchCard (same
border, radius, 12px/14px padding, and label/description typography)
so the selector blends in with the surrounding form fields.
- Underlying form state is unchanged: picking GPU Service still seeds
k8s_options.gpuInstanceOptions = {}, picking Model Service clears
it — so the static-address field, the EDIT-mode change watcher, and
the API payload all keep working as before.
- Align the GPU Service label with the top-level menu entry
(menu.gpuService) — drop "Instance" / "实例" from the card title
and the static access address label.
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.
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.
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`.
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.
Rename the api-keys list column from User Name to Creator for clarity and reorder it to sit immediately before the Created column. Add the common.table.creator locale key across all supported languages.
Surface the top-level system_default_container_registry as a dedicated
input (both Docker and K8s), coerce blank to null, and drop its
redundant worker_config YAML hint.
Align the Kubernetes cluster create/edit page with the backend's
promotion of operator/K8s knobs out of worker_config and the removal
of gpuVendorOverrides.
- k8s_options: drop gpuVendorOverrides; add operatorImage, namespace,
and gpuInstanceOptions (presence = GPU instances enabled, carrying an
optional gpuInstancesAccessStaticAddress).
- Add a top-level system_default_container_registry to the cluster type
(promoted out of worker_config on the backend).
- Group all k8s_options fields into a new top-level "K8s Deployment
Options" collapsible section (sibling of Advanced, rendered above it),
with namespace first followed by volume mounts, image credentials,
node selector, operator image, and GPU instances.
- Drive the GPU instances toggle from local state instead of
Form.useWatch (an unregistered nested path never re-rendered, leaving
the switch unresponsive); use antd's borderless Switch and keep the
label/switch grouped together. Namespace gains a gpustack-system
placeholder.
- Remove the gpuVendorOverrides validation from the form and the stale
operator_image / namespace / gpu_instances_access_static_address hints
from the worker_config YAML template and JSON schema.
- Add-worker GPU picker: always allow multi-select for K8s clusters
(runtime node selectors are now auto-derived), dropping the
override-gating, cluster fetch, and single-only hint.
- Update locales (en/zh/ja/ru/tr) for the removed and added keys.