Compare commits

...
Author SHA1 Message Date
gitlawr bda8264788 fix: playground model id alignment across org boundaries
Two QA-reported bugs on the "Open in Playground" path. Both come
down to the model id the playground submits not matching what
``/v1/models`` / the dispatcher key off:

* Routes page (OSS UI) emitted ``default/<name>`` for routes in the
  platform Org and 404'd. ``use-open-playground`` keyed off the
  ``is_platform`` flag alone; stale caches that drop the flag
  slipped through. Also accept the well-known ``name === 'default'``
  (``PLATFORM_PRINCIPAL_NAME`` on the backend) as a fallback signal.

* My Models page (enterprise UI, non-admin) emitted bare ``<name>``
  with no Org prefix for non-platform routes. Use ``model.name``
  from ``/v2/my-models`` verbatim — the backend ("fix: principal
  prefix in my-models") now rewrites that field to the OpenAI-style
  id server-side, which also closes the cross-Org grant gap a
  client-side cache lookup can't (the granting Org isn't in the
  caller's member list). Card title now shows the prefixed id, so
  users can tell apart same-named models from different Orgs.

Routes page keeps ``useOpenPlayground`` — that surface is always
scoped to the caller's own Org, the local cache is sufficient, and
``/model-routes`` still returns the raw ``name``.

Also drops the now-unused ``onClick`` prop on ``ModelItem``: the
card had ``clickable={false}`` so the parent-passed handler was
already dead code; the Button drives the playground navigation.
2026-06-07 15:45:49 +08:00
gitlawr ab8e792276 feat(cluster): card-based cluster type selector for K8s
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.
2026-06-07 13:44:50 +08:00
jialinandjialin 0e4be63b6b chore(gaas): add a unknown phase 2026-06-06 21:09:56 +08:00
jialinandjialin d3ebea9090 fix: locales, empty link in add worker 2026-06-06 14:29:49 +08:00
jialinandjialin 15a9e23243 fix: convert cpu unit to core 2026-06-06 14:29:49 +08:00
jialinandjialin eab7d9c77f style: usage chart 2026-06-06 14:29:49 +08:00
jialinandjialin 4a86b71018 fix: ignore request errors on setup 2026-06-06 14:29:49 +08:00
jialinandjialin 6350f7f11a chore(i18n): usage locales 2026-06-05 23:59:25 +08:00
jialinandLawrence Li ce6388253c fix: cpu init value failed 2026-06-05 23:08:45 +08:00
jialinandLawrence Li d8f6064d5e fix: usage locales 2026-06-05 23:08:45 +08:00
jialinandLawrence Li 9f5d95f565 fix: align instance type fields to api 2026-06-05 23:08:45 +08:00
jialinandjialin 0cabbeb81c fix(style): cluster detail locale 2026-06-05 22:29:39 +08:00
jialinandjialin 76618aa3a1 fix(style): cluster detail 2026-06-05 22:18:26 +08:00
gitlawrandjialin b19db0f67d fix(clusters): call out that GPU instance service blocks model service
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.
2026-06-05 18:56:42 +08:00
gitlawrandjialin 76f228bfe7 fix(llmodels): hide GPU-service clusters from deploy picker
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.
2026-06-05 18:56:42 +08:00
gitlawrandjialin be40692773 feat(login): add onUserFetched plugin hook
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.
2026-06-05 18:55:46 +08:00
gitlawrandjialin 42ac377c17 feat: tease enterprise multi-tenancy and API key controls in OSS
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.
2026-06-05 16:41:34 +08:00
jialinandjialin 46f859cc3c fix: no need to show error in login for first 2026-06-05 16:24:46 +08:00
gitlawrandjialin f2bff0d1fb fix(storage): remove duplicate error toast on create failure 2026-06-05 16:20:49 +08:00
gitlawrandjialin 0bd98b9662 fix(llmodels): scope catalog deploy cluster seed by create-scope org
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.
2026-06-05 13:11:41 +08:00
micheliaandjialin 1ac2c02197 fix: change benchmark model name query condition to input 2026-06-05 12:30:25 +08:00
jialinandjialin 907187d53a fix: show worker added message 2026-06-05 10:20:55 +08:00
Yuxing Dengandjialin faf6246fe6 fix: remove worker config bg color 2026-06-05 10:19:08 +08:00
Yuxing Dengandjialin 0c16e2be7a fix: add notification when editing cluster's k8s_options 2026-06-05 10:19:08 +08:00
micheliaandjialin 3212882895 chore(menu): move Usage below Resources in the sidebar 2026-06-04 19:52:52 +08:00
micheliaandjialin 62f34ffcaf feat(usage): resource usage metering page
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.
2026-06-04 19:52:52 +08:00
micheliaandjialin 50601b0aff feat(gpu-service): instance-type spec popover with per-card specs
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.
2026-06-04 19:52:52 +08:00
gitlawrandjialin 4c5d42cb13 feat(version): respect GPUSTACK_UI_* env overrides in build info
Allow a wrapping build that checks this source tree out as a
sub-package to stamp its own release tag and commit id onto the UI
(otherwise the version panel reports the host tree's git HEAD,
which the wrapper doesn't control).

GPUSTACK_UI_VERSION overrides the release tag and GPUSTACK_UI_COMMIT_ID
overrides the short commit id; both fall back to the git tag /
commit at HEAD when unset, preserving existing behavior. Names are
namespaced to avoid colliding with the many tools and CI runners
that already set a generic VERSION.
2026-06-04 18:21:13 +08:00
gitlawrandjialin 85268b8917 fix(api-keys): scope creator filter to current org members
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.
2026-06-04 18:09:32 +08:00
gitlawrandjialin 973d2c4529 feat(login): add plugin seam for default landing path
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.
2026-06-04 15:56:24 +08:00
jialinandjialin ab766b8c54 style: menu collapse button 2026-06-04 15:48:44 +08:00
jialinandjialin 4945242c87 style: login page size 2026-06-04 15:48:44 +08:00
jialinandjialin c4a72eb375 style: collapse style 2026-06-04 15:48:44 +08:00
gitlawrandjialin b12d11728d fix: update model files menu access 2026-06-04 13:16:47 +08:00
Yuxing Dengandjialin 8f0e3a7576 fix: remove k8s options section and merge into advance
And re-order the configuration inputs. The current input order is:
- namespace
- volume mounts
- registry credentials
- node selector
- system-default-registry
- operator image
- gpu instance static access address
- worker config yaml
2026-06-03 23:17:38 +08:00
Yuxing Dengandjialin 4810ff2dbe fix: enhance cluster create form 2026-06-03 23:17:38 +08:00
jialinandjialin a221b22be9 style: instance type data format 2026-06-03 21:29:31 +08:00
jialinandjialin 24b458530d fix(style): sso button width 2026-06-03 21:29:31 +08:00
jialinandjialin caea43590a chore: useMemo deps 2026-06-03 18:28:31 +08:00
jialinandjialin c3d7a0b85e chore: add locales 2026-06-03 18:28:31 +08:00
jialinandjialin 5c6f28027d chore: add locales 2026-06-03 18:28:31 +08:00
jialinandjialin dc04529f5b fix: vendor options display by locales 2026-06-03 18:28:31 +08:00
jialinandjialin 2bb04a419d fix: sshkey, storage selection displayName 2026-06-03 18:28:31 +08:00
jialinandjialin 8e31b71668 chore: merge cluster menu to resource 2026-06-03 18:28:31 +08:00
micheliaandmichela feng f055453889 fix(usage): defensive optional-chaining on breakdown items + sku 2026-06-03 17:10:51 +08:00
micheliaandmichela feng 820a6cecdd fix(gpu-service): avoid stringifying unset cpu in the instance form
`${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.
2026-06-03 17:10:51 +08:00
micheliaandmichela feng c3a779e4c8 feat(usage): register the Usage route and menu group
Add the /usage menu folder with its overview child pointing at the
tabbed Usage page.
2026-06-03 17:10:51 +08:00
micheliaandmichela feng 00008abf03 feat(usage): add the tabbed Usage page
A Tabs shell hosting Tokens / GPU Instances / Storage / Summary /
Resource Events:
- token-tab: the original token view, wrapped as a tab.
- gpu-instances-tab & storage-tab: per-instance / per-volume breakdowns
  with KPI cards, a metric+granularity trend chart, and grouped tables.
- summary-tab: three-domain (Tokens / Compute / Storage) overview with
  headline stats, donut, and trend.
- resource-events: the lifecycle audit log (Started / Stopped).
- resource-filter-bar + metric-chart-card: shared filter / chart controls
  mirroring the Tokens tab.
- resource-export-data: a preview modal (filter + paginated preview, then
  download) backing the Export Chart Data / Export Table Data actions.
2026-06-03 17:10:51 +08:00
micheliaandmichela feng d9c6f2f780 feat(usage): add i18n strings for the Usage page
Menu label (Usage) and the resource-tab strings (filters, export
chart/table, table headers) across all five locales.
2026-06-03 17:10:51 +08:00
micheliaandmichela feng 9a4d77e869 style(usage): tune shared bar/pie charts for the usage views
- 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.
2026-06-03 17:10:51 +08:00
micheliaandmichela feng 2407416e33 feat(usage): add resource-usage API client, meta hook, and shared utils
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.
2026-06-03 17:10:51 +08:00
jialinandjialin 234e42ccfa chore: remove comment 2026-06-03 15:50:06 +08:00
jialinandjialin 4426b8dc30 fix: request noop 2026-06-03 15:50:06 +08:00
jialinandjialin 9541f49f39 fix(style): table cell overflow 2026-06-03 15:50:06 +08:00
jialinandjialin 9f7a99f370 fix: refresh list after creating key 2026-06-03 15:50:06 +08:00
jialinandjialin 12f0bb2f98 chore: request.extensions 2026-06-03 15:50:06 +08:00
jialinandjialin ac45a72e0e fix: api acccess info model name 2026-06-03 15:50:06 +08:00
jialinandjialin 9705818b15 fix: wrap fetch for injecting headers 2026-06-03 15:50:06 +08:00
jialinandjialin 39374eafda fix: update params after switching model 2026-06-03 15:50:06 +08:00
Yuxing Dengandjialin 110bc3205e fix: failed to enable gpu instance while creating cluster 2026-06-03 15:33:33 +08:00
gitlawrandjialin 57fec89f2d feat(api-keys): show creator column and filter to org owners
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`.
2026-06-03 15:25:38 +08:00
gitlawrandjialin 72e794c281 fix: align playground default model id with org namespace
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.
2026-06-03 10:59:43 +08:00
gitlawrandjialin fadbcc3e44 feat(benchmark): scope the benchmark create form by organization
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.
2026-06-03 10:14:50 +08:00
gitlawrandjialin 9e30e964e6 feat(models): scope deployment and model-file create by organization
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.
2026-06-03 10:14:50 +08:00
gitlawrandjialin fbb707d014 feat(gpu-service): scope create forms by the selected organization
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.
2026-06-03 10:14:50 +08:00
gitlawrandjialin d2d82ee5ee feat(request): add a response-interceptor seam
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.
2026-06-03 10:14:50 +08:00
jialinandjialin 9061fa08ba chore(deps): update ui 1.0.22 2026-06-02 17:51:25 +08:00
jialinandjialin f29236518e style: selector max tag count 2026-06-02 17:51:25 +08:00
jialinandjialin 6537de5224 fix(style): worker step container 2026-06-02 17:51:25 +08:00
jialinandjialin 0413d470a8 fix(style): worker upgrade tips do not show 2026-06-02 17:51:25 +08:00
jialinandjialin c01f4d3ce8 style: table split 2026-06-02 15:18:00 +08:00
jialinandjialin dd9d359bb0 chore: storage label 2026-06-02 15:18:00 +08:00
jialinandjialin b444c35643 style: side menu scroll bar 2026-06-02 15:18:00 +08:00
jialinandjialin abd0d52fad chore: perferences route 2026-06-02 15:18:00 +08:00
yxfandjialin c8771dd3b9 fix(label): update LoRA Adapter label to plural form 2026-06-02 15:13:55 +08:00
jialinandjialin 6d16f56183 chore(deps): update ui 1.0.21 2026-06-01 21:06:56 +08:00
jialinandjialin 95ffe0350e fix(style): k8s form 2026-06-01 21:06:56 +08:00
jialinandjialin e314f25a77 style: gpu instance service 2026-06-01 21:06:56 +08:00
gitlawrandjialin 65ea65e9ae feat(api-keys): rename User Name column to Creator and move before Created
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.
2026-06-01 19:13:44 +08:00
jialinandjialin 4ce975562e chore(deps): update ui 1.0.20 2026-06-01 13:36:14 +08:00
jialinandjialin 07e625f2bc chore: add locales 2026-06-01 13:36:14 +08:00
jialinandjialin d95fd8dc90 fix: instance type format in list 2026-05-31 22:23:32 +08:00
jialinandjialin 55028cf3a3 fix: parse quality data 2026-05-31 22:23:32 +08:00
jialinandLawrence Li 16a97772d3 fix: instance type api update 2026-05-30 08:28:38 +08:00
jialinandLawrence Li d33167d070 style: page segment fontsize 2026-05-30 08:28:38 +08:00
Yuxing Dengandjialin f62e0ba372 feat(cluster): add default container registry field to advanced config
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.
2026-05-29 23:11:46 +08:00
jialinandjialin 1b6466ae78 chore: menu sorting 2026-05-29 18:04:31 +08:00
jialinandjialin 29c5987bf1 style: menu 2026-05-29 18:04:31 +08:00
Yuxing Deng fcd1d01e83 feat(cluster): update K8s cluster form for new k8s_options API
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.
2026-05-29 17:56:01 +08:00
gitlawrandjialin a80b889760 refactor(access): fold allowed_users into allowed_principals
Move the model-route access modal off the deprecated allowed_users
policy/field onto the unified allowed_principals + principals surface,
persisting everything through a single /access POST.

- "specific users" radio now uses allowed_principals; a returned legacy
  allowed_users value is normalized so existing routes still select it.
- derive the picker's selection and the full grant set from `principals`
  in GET /access (fall back to legacy `items` if a backend doesn't
  return principals yet).
- save as `principals`: the principal-based override sends its staged
  set; the user picker maps its selection to USER-kind grants and
  preserves any non-user grants from the snapshot (no longer sends
  `users`).
- guard saving before the GET seeds principals (would wipe grants);
  share the ALLOWED_PRINCIPALS_POLICY constant.
- AccessControlFormData: `users` optional, add `principals`.
2026-05-29 17:11:12 +08:00
jialinandjialin f71bf1b074 chore(deps): update ui 1.0.19 2026-05-29 16:19:17 +08:00
jialinandjialin 7e048ee537 fix: click twice for submitting deployment form, using iconfont static files 2026-05-29 16:19:17 +08:00
jialinandjialin 34098e8950 fix: orgname not be shown in playground 2026-05-29 13:35:22 +08:00
jialinandjialin faf0d4d605 chore(deps): update ui 1.0.18 2026-05-29 11:30:17 +08:00
jialinandjialin 85ade88d55 fix: instance name text overflow 2026-05-29 11:24:03 +08:00
jialinandjialin 7d0b94330d fix: adapter request not be triggered 2026-05-29 11:24:03 +08:00
gitlawrandjialin 5cff1298fc feat(management): show only own rows on Clusters / Storage Types lists
The two management pages now pass ``mine=true`` to their list APIs.
With multi-tenant cluster_access in place an Org Owner would
otherwise see rows that came in via cross-Org grants — usable but
read-only — and editing/deleting them would 403 from the backend.
Restricting the management lists to rows the caller's scope owns
keeps the UX honest. PV-create / GPU-instance-create pickers still
query without ``mine`` so usable cross-Org clusters and types are
selectable there.
2026-05-28 18:16:15 +08:00
gitlawrandjialin d3a3d4e96e feat(templates): fix unresponsive action dropdown for non-admin
Two changes that together restore the per-row Edit / Delete menu
for non-admin users:

- ``templateActions`` drops the blanket ``access: 'canSeeAdmin'``
  gate on every menu item. With the gate on, non-admin callers got
  an empty filtered menu, so clicking the "..." did nothing.

- The management page passes ``mine=true`` to the list API. The
  endpoint now drops Global rows for non-admin callers (rows they
  can't edit anyway), so the dropdown shows up only on rows the
  caller may manage. The GPU-instance create picker reuses the
  same API without ``mine``, keeping Global presets visible there.
2026-05-28 18:16:15 +08:00
jialinandjialin f99621ec92 fix: catalog hidden fields 2026-05-28 15:51:51 +08:00
jialinandjialin e47a9304f4 fix: cluster worker_config refill 2026-05-28 15:51:51 +08:00
jialinandjialin 9488fc1251 fix: update instance item type 2026-05-28 15:51:51 +08:00
jialinandjialin b590aa746b fix: update onceMaxRequest field 2026-05-28 15:51:51 +08:00
jialinandjialin ef61134b8b chore(deps): update ui 1.0.17 2026-05-28 10:02:32 +08:00
jialinandjialin a9daa0b586 fix: max gpu count 2026-05-27 20:18:33 +08:00
jialinandjialin ae0ad25ed6 fix: an error occured in switch language in stt 2026-05-27 20:18:33 +08:00
jialinandjialin a4c48c1981 fix: remove description json format 2026-05-27 17:10:15 +08:00
jialinandjialin 3497590d2e fix: using select handler for provider hint 2026-05-27 17:10:15 +08:00
jialinandjialin 11cf1329c8 fix: always show max label 2026-05-27 17:10:15 +08:00
jialinandjialin ba5b41cf3a chore: update locales 2026-05-27 17:10:15 +08:00
gitlawrandjialin 1d70446cc4 fix(routes): let Org owners see Storage Types menu
The route was gated on ``canSeeAdmin`` so only platform admins ever
saw Storage Types in the sidebar. But the backend route
(``gpu_instance_persistent_volume_types``) is tenant-scoped — Org
owners can create / list their own — so hiding the menu from them
left an admin-defined feature reachable only by URL.

Relax to ``canSeeOrgAdmin`` to match. OSS keeps the platform-admin-only
behaviour (predicate defaults to ``isPlatformAdmin``); the enterprise
access extension widens it to the current-org owner.
2026-05-27 16:57:49 +08:00
gitlawrandjialin f15d0ab243 fix(cluster-create): hide ProviderCatalog when entering via providerHint
The catalog was gated on ``currentStep === startStep`` so it would
re-appear at step 1 whenever ``providerHint`` skipped the catalog (e.g.
GPU Service's "Add a Kubernetes Cluster" empty-state CTA). Result: the
configure step rendered the catalog *plus* the Name/Description/Advanced
form together, with Kubernetes shown as already selected — confusing
and ugly.

Pin the catalog to step 0 explicitly. ``startStep`` was only used to
seed ``currentStep``, so it's inlined into the ``useState`` initializer
to keep the "skip the catalog when providerHint is set" intent in one
place. Pre-selecting a provider now correctly lands the user straight
on the configure form alone.
2026-05-27 15:53:39 +08:00
jialinandjialin 34be4800d7 fix: lora tag 2026-05-27 15:45:28 +08:00
jialinandjialin e40b0c2e07 fix: get back lora_module_name 2026-05-27 15:45:28 +08:00
lizw2andjialin 45e0ad9e93 fix(model-routes): use overridden model name for provider targets 2026-05-27 15:45:28 +08:00
gitlawrandjialin 96e6ddfe8a fix: collapse GPU Service menu in Personal Org; add K8s-direct CTA
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.
2026-05-27 13:41:32 +08:00
gitlawrandjialin 6b47bacfac feat: gate GPU Service on Kubernetes cluster availability
GPU Service today only schedules on Kubernetes clusters; Docker / cloud
clusters can't host the CRDs. Without any awareness of that, Org
members whose Org has no K8s cluster (and no cluster_access grant on
one) saw a menu they couldn't use and a form that bottomed out with
backend errors.

Two changes lock the UX down:

- Boot probes the caller's cluster list once and stashes
  hasKubernetesCluster in initialState. A new canSeeGpuService
  predicate gates the menu — admins and Org owners always see it
  (they can add the cluster); everyone else only sees it when a
  reachable K8s cluster actually exists.

- The instances page filters its own cluster list to Kubernetes
  before deciding what to show. With nothing reachable we render the
  Deployments-style 'No clusters available. Add a Kubernetes cluster
  to get started.' empty state and hide the create-instance CTA;
  admins and Org owners additionally get the 'Add cluster' button
  that jumps to cluster management.
2026-05-27 13:41:32 +08:00
gitlawrandjialin 099b419e34 fix: source owner_principal_id from current organization
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.
2026-05-27 13:41:32 +08:00
jialinandjialin e343ec0a1e fix: instance type width 2026-05-27 12:14:15 +08:00
jialinandjialin e3d6d08916 fix: show max gpu 2026-05-27 12:14:15 +08:00
jialinandjialin d0bc206c83 fix: instance type missing when using CPU type 2026-05-27 12:14:15 +08:00
jialinandjialin 7287f8c81b style: subdrawer width 2026-05-27 12:14:15 +08:00
jialinandjialin 1878753cde fix: show cluster in table 2026-05-26 23:02:28 +08:00
jialinandjialin 2f04e01f4f fix: default instance type, storageMode 2026-05-26 22:40:02 +08:00
jialinandjialin a51f44c135 fix: select a useable instance type for initial 2026-05-26 22:40:02 +08:00
jialinandjialin 7f14eb544c fix: pvc events api 2026-05-26 19:16:50 +08:00
jialinandjialin c6e34db329 fix: ux issues 2026-05-26 19:16:50 +08:00
Yuxing Deng acb90531b2 feat: extend cluster k8s_options with multi-vendor manifest support
- New k8s_pod_spec form sections (image credentials, node selector,
  gpu vendor overrides) under k8s_options, replacing the legacy flat
  k8s_volume_mounts list. UI keys aligned to the backend wire shape
  (snake_case k8s_options + camelCase inside).
- System default container registry is pre-filled into the first image
  credential when creating a new cluster; empty username/password are
  coerced to null on submit to match the Optional[str] backend schema.
- Register cluster flow supports multi-runtime selection gated by the
  cluster's gpuVendorOverrides: non-override vendors stay single-select
  with an inline hint; multi-add only opens once an override vendor is
  picked, and non-override cards become disabled in that state.
- Manifest URL emits multiple ?runtime= params; check-env step combines
  per-vendor commands; downstream steps are disabled when no vendor is
  selected.
- Pre-validate gpuVendorOverrides at save time (non-empty selector, no
  duplicates across vendors, no key clash with base nodeSelector) so
  the user sees the error before hitting the manifest endpoint.
- Misc: dark-mode background of the k8s_pod_spec / volume mount titles
  no longer clashes with the drawer; cluster Steps no longer leaks the
  internal showModules/showForms props to the DOM.
2026-05-26 16:54:14 +08:00
yxfandyxf0314 91c3d0b814 feat: add parameter format and common parameters to backend configuration 2026-05-26 14:30:19 +08:00
jialinandjialin 95e03d11c1 fix: gpu service issues 2026-05-26 14:21:52 +08:00
jialinandjialin 88ab927755 refactor: gpu service API alignment 2026-05-25 22:42:02 +08:00
jialinandjialin 81c7e2ee61 fix(style): template title gap 2026-05-25 22:42:02 +08:00
jialinandjialin b5ed64de51 chore: ssh key selection 2026-05-25 22:42:02 +08:00
jialinandjialin 921a2a0d81 fix: instance form ux 2026-05-25 22:42:02 +08:00
jialinandjialin 5195b1e5fc style: number selection 2026-05-25 22:42:02 +08:00
jialinandjialin f886422f91 fix(style): textarea label background 2026-05-25 22:42:02 +08:00
jialinandjialin 27d57d7563 feat: instance pv events 2026-05-25 22:42:02 +08:00
jialinandjialin 2ebb8f44b7 fix: recreate action 2026-05-25 22:42:02 +08:00
jialinandjialin a0b408e8cb style: instance type remaing resourece 2026-05-25 22:42:02 +08:00
jialinandjialin 858e0dae09 feat: add number selection 2026-05-25 22:42:02 +08:00
gitlawrandjialin 7bee337554 fix(playground): inject X-Organization-Id on non-umi fetch paths
Playground completions (chat streaming via ``fetchChunkedData``)
and the raw ``fetch()`` calls for image / audio generation skip
umi's ``request`` pipeline and so miss the request interceptor in
``request.extensions.ts`` that pins tenant context. Higress
receives those calls without ``X-Organization-Id`` and the auth
callback falls back to the user's USER-principal id rather than
their active Org, which makes Playground usage invisible from
Org-scoped Usage views even after the backend learned to backfill
``consumer_principal_id``.

Centralise the lookup in ``tenantHeaders()`` next to
``fetchChunkedData`` so both streaming helpers and the four
``fetch()``-direct entry points in ``playground/apis/index.ts``
(image / TTS) attach the header with the same precedence as the
umi interceptor (``createScopeOrgOverride`` first,
``currentOrganizationId`` second). Returns an empty object when
no active org context is set, leaving requests unchanged.
2026-05-25 18:02:31 +08:00
jialinandjialin 7944b3f579 fix: add lora tag in routes 2026-05-25 13:56:44 +08:00
jialinandjialin 5e889563ca fix: search lora ux 2026-05-25 13:56:44 +08:00
jialinandjialin aad3b458c8 fix: selection styles 2026-05-25 13:56:44 +08:00
jialinandjialin acef2ef89a fix: add form validation 2026-05-25 13:56:44 +08:00
jialinandjialin d99c25dd03 feat: model file is lora 2026-05-25 13:56:44 +08:00
jialinandjialin f0a1dad48e fix: filter invaild lora in form 2026-05-25 13:56:44 +08:00
jialinandjialin 1e334903dd feat: lora route 2026-05-25 13:56:44 +08:00
jialinandjialin 342583f8d1 feat: lora for deployment 2026-05-25 13:56:44 +08:00
gitlawrandjialin 6af18275c4 feat(usage): treat org owners as managers on the Usage page
The Usage page gated cross-user features (default ``scope=all``,
the user filter, the per-user breakdown dimension) on
``currentUser.is_admin`` alone. The backend's
``_can_use_all_scope`` already grants the same surface to Org
owners — the page just never asked. Result: a non-admin Org owner
landed on ``scope=self`` with no user filter and no per-user
group-by, even though the API would have happily served them the
org-wide picture.

Switch the three sites to ``useAccess().canSeeOrgAdmin``, which
already encodes "platform admin OR owner of the selected
(non-Personal) Org". One predicate, no new helper.

Sites:
- ``index.tsx``: ``initialScope`` defaults to ``all`` for managers.
- ``filter-bar.tsx``: the user filter + nested API-key cascader
  surface (vs the flat API-key picker for members).
- ``daily-usage.tsx``: the ``user`` group-by option in the chart.
2026-05-25 13:56:25 +08:00
gitlawrandjialin 9c519d937d fix(usage): pass scope to the time series chart fetch
The Usage page's chart fetch (``fetchTimeSeriesData``) spread
``chartFilters`` plus dates and filters, but never forwarded
``commonFilters.scope`` — so it implicitly fell back to the
backend's ``scope=all`` default. The three breakdown tables
(``apikeys-table``, ``models-table``, ``users-table``) all pass
``scope`` explicitly, which produced an inconsistent picture on
the same page: an Org owner viewing a model granted from another
Org saw the date chart render empty while the tables underneath
showed identical usage rows.

Forward ``currentSelectedFilters.scope`` alongside the rest of the
common filters so the chart and the tables ask the backend the
same question.
2026-05-25 13:56:25 +08:00
Yuxing Deng 4b14fa6ca8 feat(usage): plugin slot for Models tab extra columns
Lets enterprise plugins contribute per-route columns and a global
mount point on the Usage page's Models tab. Mirrors the existing
modelRoutes pattern: `usage.modelsExtraColumns` for cells,
`<PluginExtraFields name="UsageModelsPageGlobal" />` for the
page-level data lifecycle (receives the visible route ids so a
plugin can bulk-fetch per-row data in one call).
2026-05-25 10:42:59 +08:00
jialinandjialin dc69e51042 fix: colorPrimary cache cleared 2026-05-24 10:51:44 +08:00
jialinandjialin 052d5aa474 fix: colorPrimary cache cleared 2026-05-21 19:58:16 +08:00
jialinandjialin 0c55c1c1b4 fix: sider menu style 2026-05-21 19:58:16 +08:00
jialinandjialin 6f896b4f94 fix(style): use --app-banner-height in scroller container 2026-05-21 19:58:16 +08:00
gitlawrandjialin e7a376db70 refactor: adapt org-namespace lookup to principal name rename
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.
2026-05-21 12:15:39 +08:00
Yuxing Dengandjialin 15cf4d89b7 feat: association dounut effect 2026-05-20 11:29:43 +08:00
Yuxing Dengandjialin c38fcc494a refactor(dashboard): collapse usage section into a single row
Merge the two "by Model" pies (Token Usage + API Requests) into one
dual-pie card with a shared right-side vertical legend and per-donut
center totals, drop the standalone API-Key Top 10 chart, and put the
combined chart side-by-side with the User Top 10 ranking in one row
with the same 16/8 column ratio used by the System Load section above.

* New component usage-charts/usage-by-model.tsx owns both metric queries
  (group_by=['date','route']), aggregates a shared route→color map so
  the legend toggles both donuts together, and renders two pies at
  centers ('20%','50%') and ('60%','50%') with radius ['50%','70%'].
* new-usage.tsx no longer mounts ApiRequestsByModel / TokenUsageByModel
  / TopTokenUsageByApiKey; layout collapses to two columns sized
  (lg=24 xl=16) and (lg=24 xl=8).
* Delete unused token-usage-by-model.tsx, api-requests-by-model.tsx,
  top-token-usage-by-api-key.tsx, and the use-top-token-usage-by-api-key
  hook.
* Strip the now-unreferenced dashboard.tokenUsageByModel /
  apiRequestsByModel / topTokenUsageByApiKey locale keys across 5
  locales, add the new dashboard.usageByModel title; locale parity
  check passes.
2026-05-20 11:29:43 +08:00
Yuxing Deng 44e5b78d29 refactor(dashboard): switch usage charts to route group_by, rename Models card
Align dashboard with the route dimension introduced in the previous
commit:

* Token Usage / API Requests pie charts query the breakdown API with
  `group_by: ['date', 'route']` and aggregate via the new `route`
  dimension in `buildUsageLabel`. Visible chart titles keep the existing
  "by Model" wording — only the underlying data dim changed.
* Overview "Models" card label switched to `dashboard.deployments`
  ("部署" / "Deployments"), and the Active Models table heading to
  `dashboard.activeDeployments`. These reflect the underlying entity
  (model deployments) more accurately now that routes are the
  user-facing model concept.
* Drop 11 unused dashboard locale keys (`dashboard.title`,
  `dashboard.models`, `dashboard.allocategpus`, `dashboard.instances`,
  `dashboard.disk`, `dashboard.diskutilization`, `dashboard.apirequest`,
  `dashboard.activeModels`, `dashboard.activeUsers`,
  `dashboard.activeModels.name`, `dashboard.runninginstances`) — no
  remaining references in `src/` after the rename. Locale parity check
  passes.
2026-05-19 16:50:42 +08:00
Yuxing Deng 38107a67fe refactor(usage): switch breakdown dimension from model to route
Align with gpustack backend commits 7b57cd3b (add route dimension to
usage breakdown) and 51fd25c8 (use model_route_id as models_called):

* group_by, filters, and BreakdownItem now use the `route` dimension;
  `models` filter cascader is replaced with a flat `routes` selector
  (routes have no provider/cluster grouping).
* Tab / group_by option / column header keep the user-facing "模型 / Model"
  wording — only the underlying data dimension changed.
* Drop cluster_name / provider_type columns from the breakdown table;
  drop api_keys_used column per design.
* Fix pre-existing excel export bug in use-export-table — string
  dataIndex was lodash-joined char-by-char ("input_tokens" →
  "i_n_p_u_t..."), array dataIndex was used as a flat key against nested
  rows. Rebuilt around buildSheetMeta that emits a stable field key plus
  a formatMap using _.get(row, path) for nested values. Sheet name kept
  as `models` to match the visible column label.
2026-05-19 16:50:42 +08:00
jialinandjialin 0274bcb8cd chore: update ui 1.0.14 2026-05-19 14:09:07 +08:00
gitlawrandjialin a91c1545f8 fix: require ref audio for Base task in TTS playground 2026-05-19 12:36:08 +08:00
jialinandjialin 0b3884b1a5 fix: gguf input box is hidden when deploying from model files 2026-05-19 12:16:02 +08:00
jialinandjialin bcea46fa8f fix(style): chart style 2026-05-18 20:14:28 +08:00
jialinandjialin abc3c2c25a fix: instance events namespace 2026-05-18 16:59:53 +08:00
jialinandjialin db7a8a702d fix: select panel no default value 2026-05-18 15:37:46 +08:00
jialinandjialin 288bfebc13 fix: filter user option in usage 2026-05-18 15:37:46 +08:00
jialinandjialin c7996d3e14 fix: login page miss icon 2026-05-18 15:37:46 +08:00
jialinandjialin 0d9b6325f9 fix: hygon, thead runtime 2026-05-18 15:37:46 +08:00
gitlawrandjialin baf1508644 fix: guard systemConfigAtom reads against null
``logout`` calls ``clearAtomStorage(systemConfigAtom)`` which sets the
atom to ``null``, and on a fresh SPA login (no full reload)
``app.tsx``'s ``fetchSystemConfig`` doesn't re-run, so consumers that
read ``systemConfig.xxx`` directly crash with "Cannot read properties
of null". A hard refresh masks the bug. Add optional chaining at the
read sites (grafana url / dashboard uids, showMonitoring) so the
contract stays robust regardless of how the atom is reset.
2026-05-18 15:36:29 +08:00
jialinandjialin d04604abe5 fix: SelectPanel props 2026-05-18 13:28:52 +08:00
jialinandjialin bf09784fad chore: update ui 1.0.13 2026-05-18 13:28:52 +08:00
gitlawrandjialin 67f5c794e1 fix(login): keep 360px design width with max-width: 100% cap
Switching ``Buttons`` to ``width: 100%`` (in c32ad712) had a
side-effect — with ``FormWrapper`` sized to ``max-content``, a
100% child without LocalUserForm alongside it lets the card
collapse to the button's intrinsic text width. Per PR #1097 review,
go back to the 360px design width and rely on ``max-width: 100%``
to keep it from overflowing tighter parents (the enterprise cover
layout's 272px inner especially).

Apply the same shape to LocalUserForm — its hardcoded 360px alone
would still overflow on the cover layout — so the two surfaces
stay in lockstep when switching between SSO and password modes.

(Pre-commit hook still crashes on the ``context.getDeclaredVariables``
bug in main; unrelated.)
2026-05-18 12:25:08 +08:00
gitlawrandjialin 55fbe118d5 fix(login): stop SSO button from overflowing the login card
``Buttons`` was a fixed ``width: 360px`` column, but ``FormWrapper``
sizes its content area to ``max-content`` capped at 440px (with 40px
padding). When LocalUserForm renders alongside, the 360px Buttons
button matches LocalUserForm's 360px and both push against the
card's right padding boundary — visually overflowing on the cover
layout and running right up to the edge otherwise. Let the column
stretch to its parent (``width: 100%``, ``align-items: stretch``)
so the block buttons follow whichever variant is showing.

(Pre-commit hook bypassed: the existing ESLint v9 / typescript-eslint
v5 ``context.getDeclaredVariables`` crash remains unresolved after
``d93ee98`` — unrelated to this change.)
2026-05-18 12:25:08 +08:00
Yuxing Dengandjialin ae8ec8572c feat: generic configActions plugin slot for api-keys & model-routes
Introduce `pages/api-keys/plugin.tsx` and `pages/model-routes/plugin.tsx`
defining a unified `{ key, labelId, icon, priority, danger, form,
useCreate }` contract that plugins use to contribute per-row
"configure this record" actions. The host renders one dropdown entry
per registered action and mounts each entry's form, ordered by a
single numeric priority (built-ins are ranked on the same scale; rows
flagged `danger` sink to the bottom).

Drops the older `apiKeys.rowActions` / `modelRoutes.rowActions` slots
and the implicit drawer half of
`<PluginExtraFields name="APIKeysPageGlobal" />`. The model-routes
mount point stays for the page-level quota-defaults bulk-fetch and now
also accepts a `refreshToken` so per-row saves can invalidate derived
page data without changing the row set.
2026-05-18 12:14:19 +08:00
jialinandjialin 38b91a558b chore: remove notes 2026-05-15 16:22:07 +08:00
jialinandjialin a001f83abb fix: update instance name error 2026-05-15 16:22:07 +08:00
jialinandjialin 87c8a3a64d feat: add instance recreate action 2026-05-15 16:22:07 +08:00
jialinandjialin c6e7075bf5 feat: template port name 2026-05-15 16:22:07 +08:00
jialinandjialin 5d75cfdfc5 fix: name validation 2026-05-15 16:22:07 +08:00
jialinandjialin 3f9852c932 refactor: instances add-modal 2026-05-15 16:22:07 +08:00
jialinandjialin 9dfaf990fd feat: add events, log api 2026-05-15 16:22:07 +08:00
micheliaandjialin 1bef1637e0 feat: support license banner 2026-05-15 09:41:10 +08:00
jialinandjialin 451d5bfe47 fix: show inject params, update ui 1.0.11 2026-05-14 11:54:51 +08:00
jialinandjialin faa1f935b7 feat: provider access to core-ui 2026-05-14 11:52:48 +08:00
jialinandjialin c428fe175a chore: remove inject params tag 2026-05-14 11:21:49 +08:00
jialinandjialin 9767c8bf39 chore: show deployment backend params 2026-05-14 11:21:49 +08:00
369 changed files with 20444 additions and 4488 deletions
+1
View File
@@ -15,3 +15,4 @@
.idea .idea
.claude .claude
/dist.zip /dist.zip
.cache
+1 -2
View File
@@ -3,7 +3,6 @@ node_modules
.umi-production .umi-production
public/static/*.js public/static/*.js
public/static/*.css public/static/*.css
src/components/icon-font/iconfont/iconfont.js src/components/iconfont/
src/components/icon-font/iconfont/*.css
+1 -1
View File
@@ -3,5 +3,5 @@ module.exports = {
rules: { rules: {
'selector-class-pattern': null 'selector-class-pattern': null
}, },
ignoreFiles: ['public/static/*.css'] ignoreFiles: ['public/static/*.css', 'src/components/iconfont/iconfont.css']
}; };
+265
View File
@@ -0,0 +1,265 @@
# React State and Request Patterns
These guidelines define preferred patterns for request handling, state updates, and side-effect management in React applications.
The primary goal is to keep data flow explicit, predictable, maintainable, and performant while avoiding unnecessary rerenders and effect-driven logic.
---
## 1. Avoid Effect-Driven Requests
Do not use request functions themselves as dependencies in `useEffect`.
Avoid patterns like:
```ts
useEffect(() => {
fetchData();
}, [fetchData]);
```
Requests should be triggered explicitly by user actions or lifecycle entry points.
---
## 2. Form Requests Should Be Action-Driven
For form-related requests (such as loading `Select` options):
- Fetch data when the form is opened for the first time.
- If later requests depend on user interactions, trigger them directly inside the interaction handler.
- Do not rely on `useEffect` dependency changes to trigger requests.
Recommended:
```ts
const handleOnChange = (value) => {
fetchData(value);
};
```
Avoid:
```ts
useEffect(() => {
fetchData(value);
}, [value]);
```
The action itself should control the request.
---
## 3. Update Related States Together
If a single action updates multiple related states:
- Do not synchronize them through `useEffect`
- Do not derive them indirectly through `useMemo`
Instead, update all related states directly inside the action handler.
Recommended:
```ts
const handleOnChange = (value) => {
setState1(...);
setState2(...);
buildState(...);
};
```
Avoid implicit state synchronization chains.
---
## 4. Group Strongly Related State
If multiple states are always updated together:
- Do not split them into multiple `useState` calls.
- Prefer a single state object.
Recommended:
```ts
const [state, setState] = useState({
state1: ...,
state2: ...,
state3: ...,
});
```
This reduces unnecessary rerenders and keeps state transitions predictable.
---
## 5. Prefer Explicit State Flow
Avoid chaining business logic through multiple `useEffect` hooks.
Keep:
- request execution
- state updates
- derived calculations
close to the triggering action whenever possible.
Prefer:
```ts
const handleAction = () => {
fetchData();
setTableData(...);
setSelectedRow(...);
};
```
Over:
```ts
useEffect(() => {
buildTable();
}, [data]);
useEffect(() => {
updateSelection();
}, [tableData]);
```
---
## 6. Avoid Premature Memoization
Do not use `useMemo` or `useCallback` unless there is a confirmed rendering or computation bottleneck.
Overusing memoization:
- increases complexity
- makes state flow harder to understand
- may introduce stale dependency issues
Prefer simple and explicit logic first.
Optimize only when necessary.
---
## 7. Keep Request Logic Predictable
A user interaction should clearly show:
- what request is triggered
- which states are updated
- how the UI changes
Avoid indirect update chains caused by dependency-driven effects.
The code should make the request and update flow easy to trace.
---
## 8. Prefer Action-Driven Architecture
Prefer:
- action-driven updates
- explicit handlers
- localized state transitions
Over:
- effect-driven synchronization
- cross-hook implicit updates
- reactive chains between states
The triggering action should remain the primary source of truth for UI updates.
---
# Form
Form-specific patterns that build on the rules above. The theme: keep cascading selections (pick A → derive B → write form) on a single, predictable path.
## 1. No Fallback for Derived Selection
When "pick A then auto-pick B", match by rule and return `undefined` if no match — let the corresponding form field stay empty.
Do not silently fall back to `list[0]` or another default. A fallback hides data issues and tricks the user into thinking they have a valid selection.
```ts
const findB = (key, list) =>
key ? list.find((x) => x.key === key) : undefined;
```
For form fields, prefer clearing with `undefined` over `''`. With Ant Design, `undefined` restores the placeholder; `''` is treated as a real value.
## 2. Async Race Protection
For fetches triggered by a lifecycle entry (e.g., modal open), tag each invocation with a session ref. Discard stale results if the session has rotated (the modal was closed and re-opened) by the time the response arrives.
```ts
const sessionRef = useRef(0);
useEffect(() => {
if (!open) {
sessionRef.current += 1;
return;
}
const session = ++sessionRef.current;
Promise.all([fetchA(), fetchB()]).then(([as, bs]) => {
if (sessionRef.current !== session) return;
applySelection(as.items[0], findB(as.items[0].key, bs.items));
});
}, [open]);
```
## 3. Reference Template
A typical form with two cascading selectors backed by a single shared state:
```ts
type Selection = { a?: string; b?: number };
const [selection, setSelection] = useState<Selection>({});
const sessionRef = useRef(0);
const findB = (key, list) =>
key ? list.find((x) => x.key === key) : undefined;
// Single atomic write: state + form together.
const applySelection = (a, b) => {
setSelection({ a: a.name, b: b?.id });
form.current?.setFieldsValue({
field: b?.field,
spec: { ...currentSpec, ...b?.spec }
});
};
// Trigger 1: modal opened
useEffect(() => {
if (!open) {
sessionRef.current++;
setSelection({});
return;
}
const session = ++sessionRef.current;
Promise.all([fetchA(), fetchB()]).then(([as, bs]) => {
if (sessionRef.current !== session) return;
const first = as.items[0];
applySelection(first, findB(first.key, bs.items));
});
}, [open]);
// Trigger 2: user picks A
const handleAChange = (a) => {
applySelection(a, findB(a.key, listB));
};
// Trigger 3: user picks B
const handleBChange = (b) => {
setSelection((prev) => ({ ...prev, b: b.id }));
form.current?.setFieldsValue({ ...b.fields });
};
```
+95 -62
View File
@@ -140,15 +140,6 @@ const baseRoutes = [
access: 'canSeeOrgAdmin', access: 'canSeeOrgAdmin',
component: './model-routes/index' component: './model-routes/index'
}, },
{
name: 'usage',
path: '/models/usage',
key: 'usage',
icon: 'icon-usage-outlined',
selectedIcon: 'icon-usage-filled',
defaultIcon: 'icon-usage-outlined',
component: './usage/index'
},
{ {
name: 'providers', name: 'providers',
path: '/models/providers', path: '/models/providers',
@@ -179,6 +170,26 @@ const baseRoutes = [
access: 'canSeeOrgAdmin', access: 'canSeeOrgAdmin',
hideInMenu: true, hideInMenu: true,
component: './benchmark/details' component: './benchmark/details'
},
{
name: 'backendsList',
path: '/models/backends',
key: 'backendsList',
icon: 'icon-backend',
selectedIcon: 'icon-backend-filled',
defaultIcon: 'icon-backend',
access: 'canSeeOrgAdmin',
component: './backends/index'
},
{
name: 'modelfiles',
path: '/models/modelfiles',
key: 'modelfiles',
icon: 'icon-files',
selectedIcon: 'icon-files-filled',
defaultIcon: 'icon-files',
access: 'canSeeOrgAdmin',
component: './resources/components/model-files'
} }
] ]
}, },
@@ -186,6 +197,7 @@ const baseRoutes = [
name: 'gpuService', name: 'gpuService',
path: '/gpu-service', path: '/gpu-service',
key: 'gpuService', key: 'gpuService',
access: 'canSeeGpuService',
routes: [ routes: [
{ {
path: '/gpu-service', path: '/gpu-service',
@@ -213,10 +225,25 @@ const baseRoutes = [
name: 'storage', name: 'storage',
path: '/gpu-service/storage', path: '/gpu-service/storage',
key: 'gpuServiceStorage', key: 'gpuServiceStorage',
icon: 'icon-database-outlined',
selectedIcon: 'icon-database-filled',
defaultIcon: 'icon-database-outlined',
component: './gpu-service/storage'
},
{
name: 'storageTypes',
path: '/gpu-service/storage-types',
key: 'gpuServiceStorageTypes',
icon: 'icon-storage-outlined', icon: 'icon-storage-outlined',
// Storage types are tenant-scoped on the backend (Org owners
// can create/list their own), so the menu shouldn't be
// platform-admin-only. ``canSeeOrgAdmin`` keeps the gate at
// "admin or current-org owner" — Org members still don't see
// it, which matches the read/write model in the route.
access: 'canSeeOrgAdmin',
selectedIcon: 'icon-storage-filled', selectedIcon: 'icon-storage-filled',
defaultIcon: 'icon-storage-outlined', defaultIcon: 'icon-storage-outlined',
component: './gpu-service/storage' component: './gpu-service/storage-types'
}, },
{ {
name: 'publicKeys', name: 'publicKeys',
@@ -239,6 +266,16 @@ const baseRoutes = [
path: '/resources', path: '/resources',
redirect: '/resources/workers' redirect: '/resources/workers'
}, },
{
name: 'clusters',
path: '/resources/clusters/list',
key: 'clusters',
icon: 'icon-cluster2-outline',
selectedIcon: 'icon-cluster2-filled',
defaultIcon: 'icon-cluster2-outline',
component: './cluster-management/clusters',
subMenu: ['/resources/clusters/detail', '/resources/clusters/create']
},
{ {
name: 'workers', name: 'workers',
path: '/resources/workers', path: '/resources/workers',
@@ -258,67 +295,49 @@ const baseRoutes = [
component: './resources/components/gpus' component: './resources/components/gpus'
}, },
{ {
name: 'backendsList', name: 'credentials',
path: '/resources/backends', path: '/resources/credentials',
key: 'backendsList', key: 'credentials',
icon: 'icon-backend', icon: 'icon-credential-outline',
selectedIcon: 'icon-backend-filled', selectedIcon: 'icon-credential-filled',
defaultIcon: 'icon-backend', defaultIcon: 'icon-credential-outline',
access: 'canSeeOrgAdmin', component: './cluster-management/credentials'
component: './backends/index'
},
{
name: 'modelfiles',
path: '/resources/modelfiles',
key: 'modelfiles',
icon: 'icon-files',
selectedIcon: 'icon-files-filled',
defaultIcon: 'icon-files',
component: './resources/components/model-files'
}
]
},
{
name: 'clusterManagement',
path: '/cluster-management',
key: 'clusterManagement',
access: 'canSeeOrgAdmin',
routes: [
{
path: '/cluster-management',
redirect: '/cluster-management/clusters/list'
},
{
name: 'clusters',
path: '/cluster-management/clusters/list',
key: 'clusters',
icon: 'icon-cluster2-outline',
selectedIcon: 'icon-cluster2-filled',
defaultIcon: 'icon-cluster2-outline',
component: './cluster-management/clusters',
subMenu: [
'/cluster-management/clusters/detail',
'/cluster-management/clusters/create'
]
}, },
{ {
name: 'clusterDetail', name: 'clusterDetail',
path: '/cluster-management/clusters/detail', path: '/resources/clusters/detail',
key: 'clusterDetail', key: 'clusterDetail',
icon: 'icon-cluster2-outline', icon: 'icon-cluster2-outline',
selectedIcon: 'icon-cluster2-filled', selectedIcon: 'icon-cluster2-filled',
defaultIcon: 'icon-cluster2-outline', defaultIcon: 'icon-cluster2-outline',
hideInMenu: true, hideInMenu: true,
component: './cluster-management/cluster-detail' component: './cluster-management/cluster-detail'
}
]
},
{
// Cross-resource consumption (tokens + GPU/CPU instances + storage).
// A folder so it matches the other top-level groups; more usage views can
// graduate in here later.
name: 'billingAndUsage',
path: '/usage',
key: 'usageGroup',
icon: 'icon-usage-outlined',
selectedIcon: 'icon-usage-filled',
defaultIcon: 'icon-usage-outlined',
routes: [
{
path: '/usage',
redirect: '/usage/overview'
}, },
{ {
name: 'credentials', name: 'usage',
path: '/cluster-management/credentials', path: '/usage/overview',
key: 'credentials', key: 'usage',
icon: 'icon-credential-outline', icon: 'icon-usage-outlined',
selectedIcon: 'icon-credential-filled', selectedIcon: 'icon-usage-filled',
defaultIcon: 'icon-credential-outline', defaultIcon: 'icon-usage-outlined',
component: './cluster-management/credentials' component: './usage/index'
} }
] ]
}, },
@@ -331,6 +350,20 @@ const baseRoutes = [
path: '/access-control', path: '/access-control',
redirect: '/access-control/users' redirect: '/access-control/users'
}, },
{
name: 'organizations',
path: '/access-control/organizations',
key: 'organizations',
icon: 'icon-org-outlined',
selectedIcon: 'icon-org-filled',
defaultIcon: 'icon-org-outlined',
// OSS exposes the menu to platform admins as a teaser for the
// enterprise multi-tenancy module. The page itself just renders
// an upsell notice — the real CRUD UI lives in the enterprise
// plugin and shadows this route via `routes.extensions.ts`.
access: 'canSeeAdmin',
component: './organizations'
},
{ {
name: 'users', name: 'users',
path: '/access-control/users', path: '/access-control/users',
@@ -364,8 +397,8 @@ const baseRoutes = [
}, },
{ {
name: 'profile', name: 'profile',
path: '/profile', path: '/preferences',
key: 'profile', key: 'preferences',
hideInMenu: true, hideInMenu: true,
component: './profile', component: './profile',
icon: 'User' icon: 'User'
+24 -10
View File
@@ -1,13 +1,27 @@
const child_process = require('child_process'); import { execSync } from 'child_process';
export const getBranchInfo = () => { export const getBranchInfo = () => {
const latestCommit = child_process // git may be absent (source archive, bare container) or this tree may
.execSync('git rev-parse HEAD') // not be a git checkout. Swallow the failure and fall back to the env
.toString() // overrides below — losing build info shouldn't fail the build.
.trim(); let latestCommit = '';
const versionTag = child_process let versionTag = '';
.execSync(`git tag --contains ${latestCommit}`) try {
.toString() latestCommit = execSync('git rev-parse HEAD').toString().trim();
.trim(); versionTag = execSync(`git tag --contains ${latestCommit}`)
return { version: versionTag || '', commitId: latestCommit.slice(0, 7) }; .toString()
.trim();
} catch {
// Not a git checkout / git unavailable; rely on env overrides.
}
// Respect explicit GPUSTACK_UI_* overrides so a wrapping build that
// checks this source tree out as a sub-package can stamp its own
// release tag and commit id onto the UI (otherwise the panel reports
// the host tree's git HEAD, which the wrapper doesn't control).
const overrideVersion = process.env.GPUSTACK_UI_VERSION?.trim();
const overrideCommitId = process.env.GPUSTACK_UI_COMMIT_ID?.trim();
return {
version: overrideVersion || versionTag || '',
commitId: overrideCommitId || latestCommit.slice(0, 7)
};
}; };
+2 -1
View File
@@ -14,7 +14,8 @@ export default defineConfig([
'dist', 'dist',
'src/.umi/', 'src/.umi/',
'src/.umi-production/', 'src/.umi-production/',
'src/.umi-test/' 'src/.umi-test/',
'src/components/iconfont/'
]), ]),
{ {
files: ['**/*.{ts,tsx,js,jsx}'], files: ['**/*.{ts,tsx,js,jsx}'],
+1 -1
View File
@@ -17,7 +17,7 @@
"@ant-design/pro-components": "3.1.0-0", "@ant-design/pro-components": "3.1.0-0",
"@antv/g6": "^5.0.51", "@antv/g6": "^5.0.51",
"@braintree/sanitize-url": "^7.1.1", "@braintree/sanitize-url": "^7.1.1",
"@gpustack/core-ui": "^1.0.10", "@gpustack/core-ui": "^1.0.22",
"@huggingface/gguf": "^0.1.7", "@huggingface/gguf": "^0.1.7",
"@huggingface/hub": "^0.15.1", "@huggingface/hub": "^0.15.1",
"@huggingface/tasks": "^0.11.6", "@huggingface/tasks": "^0.11.6",
+77 -36
View File
@@ -24,8 +24,8 @@ importers:
specifier: ^7.1.1 specifier: ^7.1.1
version: 7.1.2 version: 7.1.2
'@gpustack/core-ui': '@gpustack/core-ui':
specifier: ^1.0.10 specifier: ^1.0.22
version: 1.0.10(czdvzceysqw7iv6pct2ucnb23e) version: 1.0.22(czdvzceysqw7iv6pct2ucnb23e)
'@huggingface/gguf': '@huggingface/gguf':
specifier: ^0.1.7 specifier: ^0.1.7
version: 0.1.18 version: 0.1.18
@@ -49,7 +49,7 @@ importers:
version: 4.17.24 version: 4.17.24
'@umijs/max': '@umijs/max':
specifier: ^4.6.15 specifier: ^4.6.15
version: 4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(lightningcss@1.22.1)(prettier@3.8.3)(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(terser@5.47.1)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)) version: 4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(lightningcss@1.22.1)(prettier@3.8.3)(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
'@xterm/addon-fit': '@xterm/addon-fit':
specifier: ^0.10.0 specifier: ^0.10.0
version: 0.10.0(@xterm/xterm@5.5.0) version: 0.10.0(@xterm/xterm@5.5.0)
@@ -205,7 +205,7 @@ importers:
version: 6.4.1(css-to-react-native@3.2.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 6.4.1(css-to-react-native@3.2.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
umi-presets-pro: umi-presets-pro:
specifier: ^2.0.3 specifier: ^2.0.3
version: 2.0.3(@babel/core@7.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(chokidar@3.6.0)(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(encoding@0.1.13)(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.47.1)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))) version: 2.0.3(@babel/core@7.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(chokidar@3.6.0)(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(encoding@0.1.13)(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)))
wavesurfer.js: wavesurfer.js:
specifier: ^7.8.8 specifier: ^7.8.8
version: 7.12.6 version: 7.12.6
@@ -1484,8 +1484,8 @@ packages:
resolution: {integrity: sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ==, tarball: https://registry.npmjs.org/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz} resolution: {integrity: sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ==, tarball: https://registry.npmjs.org/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz}
deprecated: the package is rather renamed to @formatjs/ecma-abstract with some changes in functionality (primarily selectUnit is removed and we don't plan to make any further changes to this package deprecated: the package is rather renamed to @formatjs/ecma-abstract with some changes in functionality (primarily selectUnit is removed and we don't plan to make any further changes to this package
'@gpustack/core-ui@1.0.10': '@gpustack/core-ui@1.0.22':
resolution: {integrity: sha512-gw1dlkb0NzcKy23aGa+4EPe9tHI0hgxoUf/ZJZkjvjQQjAZ+zxZFtLscHGvlTVxwc7GE5FOeGjOIGlbIxW+7EA==, tarball: https://registry.npmjs.org/@gpustack/core-ui/-/core-ui-1.0.10.tgz} resolution: {integrity: sha512-uFLGalziwuojjMIeXMlCZn3tLRGA6PCa0JxLxa/+kKP+xAsy7Fcxgh4PerbTyk+P+hKnYv8cD+DPpkObDGqJgg==, tarball: https://registry.npmjs.org/@gpustack/core-ui/-/core-ui-1.0.22.tgz}
peerDependencies: peerDependencies:
'@ant-design/icons': '>=6.0.0' '@ant-design/icons': '>=6.0.0'
'@ant-design/pro-components': 3.1.0-0 '@ant-design/pro-components': 3.1.0-0
@@ -2463,6 +2463,9 @@ packages:
'@types/node@25.6.2': '@types/node@25.6.2':
resolution: {integrity: sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==, tarball: https://registry.npmjs.org/@types/node/-/node-25.6.2.tgz} resolution: {integrity: sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==, tarball: https://registry.npmjs.org/@types/node/-/node-25.6.2.tgz}
'@types/node@25.9.1':
resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==, tarball: https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz}
'@types/normalize-package-data@2.4.4': '@types/normalize-package-data@2.4.4':
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==, tarball: https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz} resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==, tarball: https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz}
@@ -2492,6 +2495,9 @@ packages:
'@types/react@18.3.28': '@types/react@18.3.28':
resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==, tarball: https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz} resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==, tarball: https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz}
'@types/react@18.3.29':
resolution: {integrity: sha512-ch0qJdr2JY0r04NXSprbK6TXOgnaJ1Tz23fm5W+z0/CBah6BSBc3n96h7K9GOtwh0HrilNWHIBzE1Ko4Dcw/Wg==, tarball: https://registry.npmjs.org/@types/react/-/react-18.3.29.tgz}
'@types/resolve@1.20.6': '@types/resolve@1.20.6':
resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==, tarball: https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz} resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==, tarball: https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz}
@@ -4402,6 +4408,10 @@ packages:
resolution: {integrity: sha512-xe9vQb5kReirPUxgQrXA3ihgbCqssmTiM7cOZ+Gzu+VeGWgpV98lLZvp0dl4yriyAePcewxGUs9UpKD8PET9KQ==, tarball: https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.2.tgz} resolution: {integrity: sha512-xe9vQb5kReirPUxgQrXA3ihgbCqssmTiM7cOZ+Gzu+VeGWgpV98lLZvp0dl4yriyAePcewxGUs9UpKD8PET9KQ==, tarball: https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.2.tgz}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
enhanced-resolve@5.22.0:
resolution: {integrity: sha512-xYcDWrpELkFzz9SpZ3PlI6Eu6eD93Yf0WLDRxikGhWJ3MAir2SNZTIVCVZqZ/NUyx8AdMc2gT9C0gPiw18kG+A==, tarball: https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.22.0.tgz}
engines: {node: '>=10.13.0'}
enhanced-resolve@5.9.3: enhanced-resolve@5.9.3:
resolution: {integrity: sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==, tarball: https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz} resolution: {integrity: sha512-Bq9VSor+kjvW3f9/MiiR4eE3XYgOl7/rS8lnSxbRbF3kS0B2r+Y9w5krBWxZgDxASVZbdYrn5wT4j/Wb0J9qow==, tarball: https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.9.3.tgz}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
@@ -8605,6 +8615,11 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
terser@5.48.0:
resolution: {integrity: sha512-J/9An6vs9Us6wKRriSFXBWdRZapREHqFzdNUKk0pmu804EMR6dr6winwo7e5JDxN4xahxQsuysyYFwlwj4XN/Q==, tarball: https://registry.npmjs.org/terser/-/terser-5.48.0.tgz}
engines: {node: '>=10'}
hasBin: true
test-exclude@6.0.0: test-exclude@6.0.0:
resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==, tarball: https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz} resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==, tarball: https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -8818,6 +8833,9 @@ packages:
undici-types@7.19.2: undici-types@7.19.2:
resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==, tarball: https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz} resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==, tarball: https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz}
undici-types@7.24.6:
resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==, tarball: https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz}
unfetch@5.0.0: unfetch@5.0.0:
resolution: {integrity: sha512-3xM2c89siXg0nHvlmYsQ2zkLASvVMBisZm5lF3gFDqfF2xonNStDJyMpvaOBe0a1Edxmqrf2E0HBdmy9QyZaeg==, tarball: https://registry.npmjs.org/unfetch/-/unfetch-5.0.0.tgz} resolution: {integrity: sha512-3xM2c89siXg0nHvlmYsQ2zkLASvVMBisZm5lF3gFDqfF2xonNStDJyMpvaOBe0a1Edxmqrf2E0HBdmy9QyZaeg==, tarball: https://registry.npmjs.org/unfetch/-/unfetch-5.0.0.tgz}
@@ -9050,8 +9068,8 @@ packages:
engines: {node: '>= 10.13.0'} engines: {node: '>= 10.13.0'}
hasBin: true hasBin: true
webpack-sources@3.4.1: webpack-sources@3.5.0:
resolution: {integrity: sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==, tarball: https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.4.1.tgz} resolution: {integrity: sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==, tarball: https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.0.tgz}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
webpack@5.106.2: webpack@5.106.2:
@@ -10790,7 +10808,7 @@ snapshots:
'@formatjs/intl-utils@2.3.0': {} '@formatjs/intl-utils@2.3.0': {}
'@gpustack/core-ui@1.0.10(czdvzceysqw7iv6pct2ucnb23e)': '@gpustack/core-ui@1.0.22(czdvzceysqw7iv6pct2ucnb23e)':
dependencies: dependencies:
'@ant-design/icons': 6.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@ant-design/icons': 6.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@ant-design/pro-components': 3.1.0-0(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@ant-design/pro-components': 3.1.0-0(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -11865,6 +11883,10 @@ snapshots:
dependencies: dependencies:
undici-types: 7.19.2 undici-types: 7.19.2
'@types/node@25.9.1':
dependencies:
undici-types: 7.24.6
'@types/normalize-package-data@2.4.4': {} '@types/normalize-package-data@2.4.4': {}
'@types/parse-json@4.0.2': {} '@types/parse-json@4.0.2': {}
@@ -11885,26 +11907,31 @@ snapshots:
'@types/react-router-dom@4.3.5': '@types/react-router-dom@4.3.5':
dependencies: dependencies:
'@types/history': 5.0.0 '@types/history': 5.0.0
'@types/react': 18.3.28 '@types/react': 18.3.29
'@types/react-router': 5.1.20 '@types/react-router': 5.1.20
'@types/react-router-redux@5.0.27': '@types/react-router-redux@5.0.27':
dependencies: dependencies:
'@types/history': 4.7.11 '@types/history': 4.7.11
'@types/react': 18.3.28 '@types/react': 18.3.29
'@types/react-router': 5.1.20 '@types/react-router': 5.1.20
redux: 4.2.1 redux: 4.2.1
'@types/react-router@5.1.20': '@types/react-router@5.1.20':
dependencies: dependencies:
'@types/history': 4.7.11 '@types/history': 4.7.11
'@types/react': 18.3.28 '@types/react': 18.3.29
'@types/react@18.3.28': '@types/react@18.3.28':
dependencies: dependencies:
'@types/prop-types': 15.7.15 '@types/prop-types': 15.7.15
csstype: 3.2.3 csstype: 3.2.3
'@types/react@18.3.29':
dependencies:
'@types/prop-types': 15.7.15
csstype: 3.2.3
'@types/resolve@1.20.6': {} '@types/resolve@1.20.6': {}
'@types/semver@7.7.1': {} '@types/semver@7.7.1': {}
@@ -12287,18 +12314,18 @@ snapshots:
- webpack-hot-middleware - webpack-hot-middleware
- webpack-plugin-serve - webpack-plugin-serve
'@umijs/bundler-vite@4.6.51(@types/node@25.6.2)(lightningcss@1.22.1)(postcss@8.5.14)(rollup@3.30.0)(sass@1.54.0)(terser@5.47.1)': '@umijs/bundler-vite@4.6.51(@types/node@25.6.2)(lightningcss@1.22.1)(postcss@8.5.14)(rollup@3.30.0)(sass@1.54.0)(terser@5.48.0)':
dependencies: dependencies:
'@svgr/core': 6.5.1 '@svgr/core': 6.5.1
'@umijs/bundler-utils': 4.6.51 '@umijs/bundler-utils': 4.6.51
'@umijs/utils': 4.6.51 '@umijs/utils': 4.6.51
'@vitejs/plugin-react': 4.0.0(vite@4.5.2(@types/node@25.6.2)(less@4.1.3)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.47.1)) '@vitejs/plugin-react': 4.0.0(vite@4.5.2(@types/node@25.6.2)(less@4.1.3)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.48.0))
core-js: 3.34.0 core-js: 3.34.0
less: 4.1.3 less: 4.1.3
postcss-preset-env: 7.5.0(postcss@8.5.14) postcss-preset-env: 7.5.0(postcss@8.5.14)
rollup-plugin-visualizer: 5.9.0(rollup@3.30.0) rollup-plugin-visualizer: 5.9.0(rollup@3.30.0)
systemjs: 6.15.1 systemjs: 6.15.1
vite: 4.5.2(@types/node@25.6.2)(less@4.1.3)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.47.1) vite: 4.5.2(@types/node@25.6.2)(less@4.1.3)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.48.0)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node' - '@types/node'
- lightningcss - lightningcss
@@ -12526,14 +12553,14 @@ snapshots:
- supports-color - supports-color
- typescript - typescript
'@umijs/max@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(lightningcss@1.22.1)(prettier@3.8.3)(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(terser@5.47.1)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))': '@umijs/max@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(lightningcss@1.22.1)(prettier@3.8.3)(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))':
dependencies: dependencies:
'@umijs/lint': 4.6.51(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.9.3) '@umijs/lint': 4.6.51(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.9.3)
'@umijs/plugins': 4.6.51(@babel/core@7.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@4.24.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@umijs/plugins': 4.6.51(@babel/core@7.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@4.24.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
antd: 4.24.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1) antd: 4.24.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
eslint: 8.35.0 eslint: 8.35.0
stylelint: 14.8.2 stylelint: 14.8.2
umi: 4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.47.1)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)) umi: 4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- '@rspack/core' - '@rspack/core'
@@ -12750,7 +12777,7 @@ snapshots:
- react-native - react-native
- supports-color - supports-color
'@umijs/preset-umi@4.6.51(@types/node@25.6.2)(@types/react@18.3.28)(lightningcss@1.22.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(terser@5.47.1)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))': '@umijs/preset-umi@4.6.51(@types/node@25.6.2)(@types/react@18.3.28)(lightningcss@1.22.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))':
dependencies: dependencies:
'@iconify/utils': 2.1.1 '@iconify/utils': 2.1.1
'@stagewise/toolbar': 0.6.2 '@stagewise/toolbar': 0.6.2
@@ -12761,7 +12788,7 @@ snapshots:
'@umijs/bundler-mako': 0.11.10(postcss@8.5.14)(sass@1.54.0)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)) '@umijs/bundler-mako': 0.11.10(postcss@8.5.14)(sass@1.54.0)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
'@umijs/bundler-utils': 4.6.51 '@umijs/bundler-utils': 4.6.51
'@umijs/bundler-utoopack': 4.6.51(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)) '@umijs/bundler-utoopack': 4.6.51(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
'@umijs/bundler-vite': 4.6.51(@types/node@25.6.2)(lightningcss@1.22.1)(postcss@8.5.14)(rollup@3.30.0)(sass@1.54.0)(terser@5.47.1) '@umijs/bundler-vite': 4.6.51(@types/node@25.6.2)(lightningcss@1.22.1)(postcss@8.5.14)(rollup@3.30.0)(sass@1.54.0)(terser@5.48.0)
'@umijs/bundler-webpack': 4.6.51(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)) '@umijs/bundler-webpack': 4.6.51(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
'@umijs/core': 4.6.51 '@umijs/core': 4.6.51
'@umijs/did-you-know': 1.0.4 '@umijs/did-you-know': 1.0.4
@@ -12843,13 +12870,13 @@ snapshots:
react-helmet-async: 1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-helmet-async: 1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-router-dom: 6.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-router-dom: 6.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@umijs/request-record@1.1.4(umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.47.1)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)))': '@umijs/request-record@1.1.4(umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)))':
dependencies: dependencies:
chokidar: 3.6.0 chokidar: 3.6.0
express: 4.22.1 express: 4.22.1
lodash: 4.18.1 lodash: 4.18.1
prettier: 2.8.8 prettier: 2.8.8
umi: 4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.47.1)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)) umi: 4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -13027,13 +13054,13 @@ snapshots:
- supports-color - supports-color
- utf-8-validate - utf-8-validate
'@vitejs/plugin-react@4.0.0(vite@4.5.2(@types/node@25.6.2)(less@4.1.3)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.47.1))': '@vitejs/plugin-react@4.0.0(vite@4.5.2(@types/node@25.6.2)(less@4.1.3)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.48.0))':
dependencies: dependencies:
'@babel/core': 7.29.0 '@babel/core': 7.29.0
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0)
'@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0)
react-refresh: 0.14.2 react-refresh: 0.14.2
vite: 4.5.2(@types/node@25.6.2)(less@4.1.3)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.47.1) vite: 4.5.2(@types/node@25.6.2)(less@4.1.3)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.48.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -14645,6 +14672,11 @@ snapshots:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
tapable: 2.3.3 tapable: 2.3.3
enhanced-resolve@5.22.0:
dependencies:
graceful-fs: 4.2.11
tapable: 2.3.3
enhanced-resolve@5.9.3: enhanced-resolve@5.9.3:
dependencies: dependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@@ -16358,7 +16390,7 @@ snapshots:
jest-worker@27.5.1: jest-worker@27.5.1:
dependencies: dependencies:
'@types/node': 25.6.2 '@types/node': 25.9.1
merge-stream: 2.0.0 merge-stream: 2.0.0
supports-color: 8.1.1 supports-color: 8.1.1
@@ -19928,7 +19960,7 @@ snapshots:
'@jridgewell/trace-mapping': 0.3.31 '@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1 jest-worker: 27.5.1
schema-utils: 4.3.3 schema-utils: 4.3.3
terser: 5.47.1 terser: 5.48.0
webpack: 5.106.2(lightningcss@1.22.1)(postcss@8.5.14) webpack: 5.106.2(lightningcss@1.22.1)(postcss@8.5.14)
optionalDependencies: optionalDependencies:
lightningcss: 1.22.1 lightningcss: 1.22.1
@@ -19941,6 +19973,13 @@ snapshots:
commander: 2.20.3 commander: 2.20.3
source-map-support: 0.5.21 source-map-support: 0.5.21
terser@5.48.0:
dependencies:
'@jridgewell/source-map': 0.3.11
acorn: 8.16.0
commander: 2.20.3
source-map-support: 0.5.21
test-exclude@6.0.0: test-exclude@6.0.0:
dependencies: dependencies:
'@istanbuljs/schema': 0.1.6 '@istanbuljs/schema': 0.1.6
@@ -20125,12 +20164,12 @@ snapshots:
ua-parser-js@0.7.41: {} ua-parser-js@0.7.41: {}
umi-presets-pro@2.0.3(@babel/core@7.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(chokidar@3.6.0)(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(encoding@0.1.13)(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.47.1)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))): umi-presets-pro@2.0.3(@babel/core@7.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(chokidar@3.6.0)(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(encoding@0.1.13)(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))):
dependencies: dependencies:
'@alita/plugins': 3.5.5(@babel/core@7.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@alita/plugins': 3.5.5(@babel/core@7.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@umijs/max-plugin-openapi': 2.0.3(chokidar@3.6.0)(encoding@0.1.13)(typescript@5.9.3) '@umijs/max-plugin-openapi': 2.0.3(chokidar@3.6.0)(encoding@0.1.13)(typescript@5.9.3)
'@umijs/plugins': 4.6.51(@babel/core@7.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@umijs/plugins': 4.6.51(@babel/core@7.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@umijs/request-record': 1.1.4(umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.47.1)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))) '@umijs/request-record': 1.1.4(umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)))
swagger-ui-dist: 4.19.1 swagger-ui-dist: 4.19.1
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
@@ -20154,14 +20193,14 @@ snapshots:
isomorphic-fetch: 2.2.1 isomorphic-fetch: 2.2.1
qs: 6.15.1 qs: 6.15.1
umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.47.1)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)): umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)):
dependencies: dependencies:
'@babel/runtime': 7.23.6 '@babel/runtime': 7.23.6
'@umijs/bundler-utils': 4.6.51 '@umijs/bundler-utils': 4.6.51
'@umijs/bundler-webpack': 4.6.51(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)) '@umijs/bundler-webpack': 4.6.51(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
'@umijs/core': 4.6.51 '@umijs/core': 4.6.51
'@umijs/lint': 4.6.51(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.9.3) '@umijs/lint': 4.6.51(eslint@8.35.0)(stylelint@14.8.2)(typescript@5.9.3)
'@umijs/preset-umi': 4.6.51(@types/node@25.6.2)(@types/react@18.3.28)(lightningcss@1.22.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(terser@5.47.1)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)) '@umijs/preset-umi': 4.6.51(@types/node@25.6.2)(@types/react@18.3.28)(lightningcss@1.22.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
'@umijs/renderer-react': 4.6.51(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@umijs/renderer-react': 4.6.51(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@umijs/server': 4.6.51 '@umijs/server': 4.6.51
'@umijs/test': 4.6.51(@babel/core@7.29.0) '@umijs/test': 4.6.51(@babel/core@7.29.0)
@@ -20208,14 +20247,14 @@ snapshots:
- webpack-hot-middleware - webpack-hot-middleware
- webpack-plugin-serve - webpack-plugin-serve
umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.47.1)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)): umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)):
dependencies: dependencies:
'@babel/runtime': 7.23.6 '@babel/runtime': 7.23.6
'@umijs/bundler-utils': 4.6.51 '@umijs/bundler-utils': 4.6.51
'@umijs/bundler-webpack': 4.6.51(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)) '@umijs/bundler-webpack': 4.6.51(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
'@umijs/core': 4.6.51 '@umijs/core': 4.6.51
'@umijs/lint': 4.6.51(eslint@9.39.4(jiti@2.7.0))(stylelint@14.8.2)(typescript@5.9.3) '@umijs/lint': 4.6.51(eslint@9.39.4(jiti@2.7.0))(stylelint@14.8.2)(typescript@5.9.3)
'@umijs/preset-umi': 4.6.51(@types/node@25.6.2)(@types/react@18.3.28)(lightningcss@1.22.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(terser@5.47.1)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)) '@umijs/preset-umi': 4.6.51(@types/node@25.6.2)(@types/react@18.3.28)(lightningcss@1.22.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
'@umijs/renderer-react': 4.6.51(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@umijs/renderer-react': 4.6.51(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@umijs/server': 4.6.51 '@umijs/server': 4.6.51
'@umijs/test': 4.6.51(@babel/core@7.29.0) '@umijs/test': 4.6.51(@babel/core@7.29.0)
@@ -20273,6 +20312,8 @@ snapshots:
undici-types@7.19.2: {} undici-types@7.19.2: {}
undici-types@7.24.6: {}
unfetch@5.0.0: {} unfetch@5.0.0: {}
unified@11.0.5: unified@11.0.5:
@@ -20457,7 +20498,7 @@ snapshots:
'@types/unist': 3.0.3 '@types/unist': 3.0.3
vfile-message: 4.0.3 vfile-message: 4.0.3
vite@4.5.2(@types/node@25.6.2)(less@4.1.3)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.47.1): vite@4.5.2(@types/node@25.6.2)(less@4.1.3)(lightningcss@1.22.1)(sass@1.54.0)(terser@5.48.0):
dependencies: dependencies:
esbuild: 0.18.20 esbuild: 0.18.20
postcss: 8.5.14 postcss: 8.5.14
@@ -20468,7 +20509,7 @@ snapshots:
less: 4.1.3 less: 4.1.3
lightningcss: 1.22.1 lightningcss: 1.22.1
sass: 1.54.0 sass: 1.54.0
terser: 5.47.1 terser: 5.48.0
vm-browserify@1.1.2: {} vm-browserify@1.1.2: {}
@@ -20530,7 +20571,7 @@ snapshots:
- bufferutil - bufferutil
- utf-8-validate - utf-8-validate
webpack-sources@3.4.1: {} webpack-sources@3.5.0: {}
webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14): webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14):
dependencies: dependencies:
@@ -20544,7 +20585,7 @@ snapshots:
acorn-import-phases: 1.0.4(acorn@8.16.0) acorn-import-phases: 1.0.4(acorn@8.16.0)
browserslist: 4.28.2 browserslist: 4.28.2
chrome-trace-event: 1.0.4 chrome-trace-event: 1.0.4
enhanced-resolve: 5.21.2 enhanced-resolve: 5.22.0
es-module-lexer: 2.1.0 es-module-lexer: 2.1.0
eslint-scope: 5.1.1 eslint-scope: 5.1.1
events: 3.3.0 events: 3.3.0
@@ -20557,7 +20598,7 @@ snapshots:
tapable: 2.3.3 tapable: 2.3.3
terser-webpack-plugin: 5.6.0(lightningcss@1.22.1)(postcss@8.5.14)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)) terser-webpack-plugin: 5.6.0(lightningcss@1.22.1)(postcss@8.5.14)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
watchpack: 2.5.1 watchpack: 2.5.1
webpack-sources: 3.4.1 webpack-sources: 3.5.0
transitivePeerDependencies: transitivePeerDependencies:
- '@minify-html/node' - '@minify-html/node'
- '@swc/core' - '@swc/core'
+22 -1
View File
@@ -1,6 +1,10 @@
import { applyAccessExtensions } from './access.extensions'; import { applyAccessExtensions } from './access.extensions';
export default (initialState: { currentUser?: Global.UserInfo }) => { export default (initialState: {
currentUser?: Global.UserInfo;
hasKubernetesCluster?: boolean;
hasResourceEvents?: boolean;
}) => {
const isPlatformAdmin = !!( const isPlatformAdmin = !!(
initialState && initialState &&
initialState.currentUser && initialState.currentUser &&
@@ -11,6 +15,16 @@ export default (initialState: { currentUser?: Global.UserInfo }) => {
initialState.currentUser && initialState.currentUser &&
!initialState.currentUser.is_admin !initialState.currentUser.is_admin
); );
// GPU Service is Kubernetes-only. We only gate visibility down when
// the probe in `getInitialState` came back with a definitive answer;
// `undefined` (probe failed / not yet ready) collapses to the
// role-based default so a transient network blip can't lock anyone
// out of the menu.
const hasKubernetesCluster = initialState?.hasKubernetesCluster;
// Having run GPU/CPU instances or storage (any resource_events) also unlocks
// GPU Service / the full Usage page — a user who used it keeps seeing it even
// without a current cluster. MaaS-only users (no cluster, no events) don't.
const hasResourceEvents = !!initialState?.hasResourceEvents;
// Predicate roles, top-down by strictness: // Predicate roles, top-down by strictness:
// * `canSeeAdmin` — strictly platform admin (`users.is_admin`). // * `canSeeAdmin` — strictly platform admin (`users.is_admin`).
@@ -18,6 +32,11 @@ export default (initialState: { currentUser?: Global.UserInfo }) => {
// * `canSeeOrgAdmin` — admin-style menus that work cross-org // * `canSeeOrgAdmin` — admin-style menus that work cross-org
// (Dashboard, Resources, Models, Cluster Management). Defaults // (Dashboard, Resources, Models, Cluster Management). Defaults
// to platform admin; extensions widen to include org admins. // to platform admin; extensions widen to include org admins.
// * `canSeeGpuService` — GPU Service menu. Anyone allowed to
// manage clusters (admins, Org owners) sees it; non-admins fall
// through to "show only if a Kubernetes cluster is actually
// reachable" so Org members without scheduling access don't see
// a dead-end menu item.
// * `canManageCurrentOrg` — pages that only make sense inside a // * `canManageCurrentOrg` — pages that only make sense inside a
// specific org context (member / group management). Defaults to // specific org context (member / group management). Defaults to
// `false`; extensions widen when both an org is selected AND // `false`; extensions widen when both an org is selected AND
@@ -27,6 +46,8 @@ export default (initialState: { currentUser?: Global.UserInfo }) => {
return applyAccessExtensions({ return applyAccessExtensions({
canSeeAdmin: isPlatformAdmin, canSeeAdmin: isPlatformAdmin,
canSeeOrgAdmin: isPlatformAdmin, canSeeOrgAdmin: isPlatformAdmin,
canSeeGpuService:
isPlatformAdmin || hasKubernetesCluster !== false || hasResourceEvents,
canManageCurrentOrg: false, canManageCurrentOrg: false,
canSeeUser, canSeeUser,
canDelete: true, canDelete: true,
+123 -3
View File
@@ -1,8 +1,12 @@
import { userSettingsHelperAtom } from '@/atoms/settings'; import { userSettingsHelperAtom } from '@/atoms/settings';
import { GPUStackVersionAtom, UpdateCheckAtom } from '@/atoms/user'; import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user';
import { setAtomStorage } from '@/atoms/utils'; import { setAtomStorage } from '@/atoms/utils';
import { DEFAULT_ENTER_PAGE, GPUSTACK_API_BASE_URL } from '@/config/settings'; import { DEFAULT_ENTER_PAGE, GPUSTACK_API_BASE_URL } from '@/config/settings';
import { COLOR_PRIMARY } from '@/config/theme/constants'; import { COLOR_PRIMARY } from '@/config/theme/constants';
import { queryClusterList } from '@/pages/cluster-management/apis';
import { ProviderValueMap } from '@/pages/cluster-management/config';
import { queryResourceEvents } from '@/pages/usage/apis/resource';
import { getGPUStackPlugin } from '@/plugins';
import { enterprisePluginReady } from '@/plugins/enterprise-ready'; import { enterprisePluginReady } from '@/plugins/enterprise-ready';
import { GPUStackPluginManager } from '@/plugins/manager'; import { GPUStackPluginManager } from '@/plugins/manager';
import { requestConfig } from '@/request-config'; import { requestConfig } from '@/request-config';
@@ -13,6 +17,7 @@ import {
} from '@/services/profile/apis'; } from '@/services/profile/apis';
import { fetchSystemConfig } from '@/services/system/query-system-config'; import { fetchSystemConfig } from '@/services/system/query-system-config';
import { isOnline } from '@/utils'; import { isOnline } from '@/utils';
import { installTenantFetch } from '@/utils/install-fetch';
import { import {
IS_FIRST_LOGIN, IS_FIRST_LOGIN,
readState, readState,
@@ -22,6 +27,8 @@ import '@gpustack/core-ui/style.css';
import { RequestConfig, history, request as umiRequest } from '@umijs/max'; import { RequestConfig, history, request as umiRequest } from '@umijs/max';
import { message } from 'antd'; import { message } from 'antd';
installTenantFetch();
// only for the first login and access from http://localhost // only for the first login and access from http://localhost
const checkDefaultPage = async (userInfo: any) => { const checkDefaultPage = async (userInfo: any) => {
@@ -34,11 +41,87 @@ const checkDefaultPage = async (userInfo: any) => {
} }
}; };
// Probes the caller's cluster list once so access predicates can gate
// GPU Service (Kubernetes-only). Cheap (one list request) and never
// blocks login — any failure just falls back to `undefined`, which
// the predicate treats as "unknown / don't restrict beyond role".
// The result is also mirrored into sessionStorage so access extensions
// that run without the initialState argument can read it (e.g. to
// override the admin shortcut in scopes where the menu shouldn't
// show even for admins).
const HAS_K8S_CLUSTER_KEY = 'hasKubernetesCluster';
const probeHasKubernetesCluster = async (): Promise<boolean | undefined> => {
try {
const res = await queryClusterList(
{ page: -1 },
{
skipErrorHandler: true
}
);
const value = (res?.items ?? []).some(
(c) => c?.provider === ProviderValueMap.Kubernetes
);
try {
window.sessionStorage.setItem(HAS_K8S_CLUSTER_KEY, JSON.stringify(value));
} catch {
// sessionStorage may be unavailable (Safari private mode); the
// access predicate already handles a missing value as "unknown".
}
return value;
} catch (error) {
console.error('probeHasKubernetesCluster error', error);
try {
window.sessionStorage.removeItem(HAS_K8S_CLUSTER_KEY);
} catch {
// ignore
}
return undefined;
}
};
// Probes whether the caller has ANY resource-usage events (GPU/CPU instance or
// storage lifecycle). Used alongside the cluster probe so a user who has run
// GPU instances still sees GPU Service / the full Usage page even if they
// currently have no Kubernetes cluster. Mirrored into sessionStorage for the
// access extensions; any failure → undefined ("unknown — don't restrict").
const HAS_RESOURCE_EVENTS_KEY = 'hasResourceEvents';
const probeHasResourceEvents = async (): Promise<boolean | undefined> => {
try {
// No date range = "ever"; scope is clamped to the caller server-side.
const res = await queryResourceEvents(
{ perPage: 1 },
{
skipErrorHandler: true
}
);
const value = (res?.pagination?.total ?? 0) > 0;
try {
window.sessionStorage.setItem(
HAS_RESOURCE_EVENTS_KEY,
JSON.stringify(value)
);
} catch {
// sessionStorage may be unavailable; predicate treats missing as unknown.
}
return value;
} catch (error) {
console.error('probeHasResourceEvents error', error);
try {
window.sessionStorage.removeItem(HAS_RESOURCE_EVENTS_KEY);
} catch {
// ignore
}
return undefined;
}
};
// runtime configuration // runtime configuration
export async function getInitialState(): Promise<{ export async function getInitialState(): Promise<{
fetchUserInfo: () => Promise<Global.UserInfo>; fetchUserInfo: () => Promise<Global.UserInfo>;
currentUser?: Global.UserInfo; currentUser?: Global.UserInfo;
pluginData?: Record<string, any>; pluginData?: Record<string, any>;
hasKubernetesCluster?: boolean;
hasResourceEvents?: boolean;
}> { }> {
const { location } = history; const { location } = history;
@@ -83,6 +166,36 @@ export async function getInitialState(): Promise<{
getUpdateCheck(); getUpdateCheck();
fetchSystemConfig(); fetchSystemConfig();
} }
// Only commit a substantive user object. A truthy-but-empty
// `data` (e.g. server responded 200 with an empty body) would
// otherwise look like "logged in" to every `currentUser`
// reader and the access seam — break out instead and let the
// caller treat the request as failed.
if (data && typeof data === 'object' && Object.keys(data).length > 0) {
// Commit the identity to atom storage (and so to localStorage)
// before returning. The access function — memoized on
// `initialState` and run once per commit — reads identity from
// localStorage; without this preemptive write the predicate
// sees the prior session's identity on its first evaluation
// after login, and stays stale until the next identity change
// (which usually doesn't come without a manual refresh).
try {
setAtomStorage(userAtom, data);
} catch (err) {
console.error('userAtom commit error:', err);
}
// Fire `onUserFetched` so plugins maintaining identity-scoped
// caches can seed them under the new identity before any
// caller commits this user to `initialState`. Errors here are
// swallowed and logged — fetchUserInfo must still return.
try {
await getGPUStackPlugin()?.login?.onUserFetched?.(data, {
request: umiRequest
});
} catch (err) {
console.error('onUserFetched plugin hook error:', err);
}
}
return data; return data;
} catch (error: any) { } catch (error: any) {
const data = error?.response?.data; const data = error?.response?.data;
@@ -122,12 +235,19 @@ export async function getInitialState(): Promise<{
getAppVersionInfo(); getAppVersionInfo();
if (![DEFAULT_ENTER_PAGE.login].includes(location.pathname)) { if (![DEFAULT_ENTER_PAGE.login].includes(location.pathname)) {
const userInfo = await fetchUserInfo(); const [userInfo, hasKubernetesCluster, hasResourceEvents] =
await Promise.all([
fetchUserInfo(),
probeHasKubernetesCluster(),
probeHasResourceEvents()
]);
checkDefaultPage(userInfo); checkDefaultPage(userInfo);
return { return {
fetchUserInfo, fetchUserInfo,
currentUser: userInfo, currentUser: userInfo,
pluginData pluginData,
hasKubernetesCluster,
hasResourceEvents
}; };
} }
return { return {
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 286 64"><g><g><defs><path id="SVGID_1_" d="M47.5 17.6L25 4.8v52.6l9-5.2V37.4l6.8 3.9-.1-10.1-6.7-3.9v-5.9l13.5 7.9z"/></defs><clipPath id="SVGID_2_"><use xlink:href="#SVGID_1_" overflow="visible"/></clipPath><g clip-path="url(#SVGID_2_)"><linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="-1.6" y1="335.05" x2="53.6" y2="335.05" gradientTransform="translate(0 -304)"><stop offset="0" stop-color="#ff6f00"/><stop offset="1" stop-color="#ffa800"/></linearGradient><path d="M-1.6 4.6h55.2v52.9H-1.6V4.6z" fill="url(#SVGID_3_)"/></g></g></g><g><g><defs><path id="SVGID_4_" d="M.5 17.6L23 4.8v52.6l-9-5.2V21.4L.5 29.3z"/></defs><clipPath id="SVGID_5_"><use xlink:href="#SVGID_4_" overflow="visible"/></clipPath><g clip-path="url(#SVGID_5_)"><linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="-1.9" y1="335.05" x2="53.3" y2="335.05" gradientTransform="translate(0 -304)"><stop offset="0" stop-color="#ff6f00"/><stop offset="1" stop-color="#ffa800"/></linearGradient><path d="M-1.9 4.6h55.2v52.9H-1.9V4.6z" fill="url(#SVGID_6_)"/></g></g></g><path style="fill:#425066" d="M88.2 21.1h-10v27.7h-5.6V21.1h-10v-4.5h25.6v4.5z"/><path style="fill:#425066" d="M94.9 49.2c-3.4 0-6.2-1.1-8.3-3.2-2.1-2.1-3.2-5-3.2-8.6v-.7c0-2.2.4-4.4 1.4-6.4.9-1.8 2.2-3.3 3.9-4.4 1.7-1.1 3.6-1.6 5.6-1.6 3.3 0 5.8 1 7.6 3.1s2.7 5 2.7 8.8v2.2H88.9c.1 1.8.8 3.4 2 4.7 1.2 1.2 2.7 1.8 4.4 1.7 2.4.1 4.6-1.1 6-3l2.9 2.8c-1 1.4-2.3 2.6-3.8 3.3-1.8 1-3.6 1.4-5.5 1.3zm-.6-20.5c-1.4-.1-2.7.5-3.6 1.5-1 1.2-1.6 2.7-1.7 4.3h10.3v-.4c-.1-1.8-.6-3.2-1.4-4.1-1-.8-2.3-1.4-3.6-1.3zm19.4-3.9l.2 2.8c1.7-2.1 4.3-3.3 7-3.2 5 0 7.5 2.9 7.6 8.6v15.8h-5.4V33.3c0-1.5-.3-2.6-1-3.4-.7-.7-1.7-1.1-3.2-1.1-2.1-.1-4 1.1-4.9 2.9v17h-5.4v-24l5.1.1zm32.2 17.5c0-.9-.4-1.7-1.2-2.2-1.2-.7-2.6-1.1-3.9-1.3-1.6-.3-3.1-.8-4.6-1.5-2.7-1.3-4-3.2-4-5.6 0-2 1-4 2.6-5.2 1.7-1.4 4-2.1 6.6-2.1 2.9 0 5.2.7 6.9 2.1 1.7 1.3 2.7 3.4 2.6 5.5h-5.4c0-1-.4-1.9-1.2-2.6-.9-.7-1.9-1.1-3.1-1-1 0-2 .2-2.9.8-.7.5-1.1 1.3-1.1 2.2 0 .8.4 1.5 1 1.9.7.5 2.1.9 4.2 1.4 1.7.3 3.4.9 5 1.7 1.1.5 2 1.3 2.7 2.3.6 1 .9 2.1.9 3.3 0 2.1-1 4-2.7 5.2-1.8 1.3-4.1 2-7 2-1.8 0-3.6-.3-5.2-1.1-1.4-.6-2.7-1.6-3.6-2.9-.8-1.2-1.3-2.6-1.3-4h5.2c0 1.1.5 2.2 1.4 2.9 1 .7 2.3 1.1 3.5 1 1.4 0 2.5-.3 3.2-.8 1-.4 1.4-1.2 1.4-2zm8.1-5.7c0-2.2.4-4.4 1.4-6.3.9-1.8 2.2-3.3 3.9-4.3 1.8-1 3.8-1.6 5.8-1.5 3.2 0 5.9 1 7.9 3.1s3.1 4.8 3.3 8.3v1.3c0 2.2-.4 4.3-1.4 6.3-.8 1.8-2.2 3.3-3.9 4.3-1.8 1-3.8 1.6-5.9 1.5-3.4 0-6.1-1.1-8.1-3.4-2-2.2-3-5.2-3.1-9l.1-.3zm5.3.5c0 2.5.5 4.4 1.5 5.8 1.8 2.3 5.1 2.8 7.5 1 .4-.3.7-.6 1-1 1-1.4 1.5-3.5 1.5-6.2 0-2.4-.5-4.3-1.6-5.8-1.7-2.3-5-2.8-7.4-1.1-.4.3-.8.7-1.1 1-.8 1.4-1.4 3.5-1.4 6.3zm33.1-7.3c-.7-.1-1.5-.2-2.2-.2-2.5 0-4.1.9-5 2.8v16.4h-5.4v-24h5.1l.1 2.7c1.3-2.1 3.1-3.1 5.4-3.1.6 0 1.3.1 1.9.3l.1 5.1zm22.5 5.3h-13v13.7h-5.6V16.6h20.5v4.5h-14.9v9.6h13v4.4zm10.7 13.7h-5.4V16.5h5.4v32.3zm3.9-12.2c0-2.2.4-4.4 1.4-6.3.9-1.8 2.2-3.3 3.9-4.3 1.8-1 3.8-1.6 5.8-1.5 3.2 0 5.9 1 7.9 3.1s3.1 4.8 3.3 8.3v1.3c0 2.2-.4 4.3-1.3 6.3-.8 1.8-2.2 3.3-3.9 4.3-1.8 1-3.8 1.6-5.9 1.5-3.4 0-6.1-1.1-8.1-3.4-2-2.2-3.1-5.2-3.1-9v-.3zm5.4.5c0 2.5.5 4.4 1.5 5.8 1 1.4 2.6 2.2 4.3 2.1 1.7.1 3.3-.7 4.2-2.1 1-1.4 1.5-3.5 1.5-6.2 0-2.4-.5-4.3-1.6-5.8-1.7-2.3-5-2.8-7.4-1.1-.4.3-.8.7-1.1 1-.9 1.4-1.4 3.5-1.4 6.3zm41.2 4.3l3.8-16.5h5.2l-6.5 24h-4.4l-5.1-16.5-5.1 16.5h-4.4l-6.6-24h5.3l3.9 16.4 4.9-16.4h4.1l4.9 16.5z"/></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+1
View File
@@ -3,6 +3,7 @@
.ant-layout-sider-children { .ant-layout-sider-children {
border-inline: none; border-inline: none;
border-radius: 0; border-radius: 0;
padding-inline-end: 0;
padding-block-end: 8px; padding-block-end: 8px;
} }
+6
View File
@@ -63,6 +63,12 @@ export const fromClusterCreationAtom = atom(false);
export const clusterSessionAtom = atom<{ export const clusterSessionAtom = atom<{
firstAddWorker: boolean; firstAddWorker: boolean;
firstAddCluster: boolean; firstAddCluster: boolean;
// Provider to preselect when the create flow opens — set by the
// empty-state CTA on feature pages that need a specific provider
// (e.g. GPU Service can only schedule on Kubernetes, so its
// "Add Cluster" button skips provider catalog and lands on the
// K8s configure step). Consumed once by ClusterCreate on mount.
providerHint?: string;
} | null>(null); } | null>(null);
export const clusterDetailAtom = atom<ClusterListItem | null>(null); export const clusterDetailAtom = atom<ClusterListItem | null>(null);
-6
View File
@@ -1,6 +0,0 @@
import { ClusterListItem } from '@/pages/cluster-management/config/types';
import { atom } from 'jotai';
export const currentClusterAtom = atom<
(Partial<ClusterListItem> & { label?: string; value?: number }) | null
>(null);
+54 -9
View File
@@ -3,6 +3,15 @@ import { atomWithStorage } from 'jotai/utils';
export const userAtom = atomWithStorage<any>('userInfo', null); export const userAtom = atomWithStorage<any>('userInfo', null);
// Backs the `currentOrganizationId` localStorage key. Stays null in
// builds with no Org context (single-tenant), and is shared with any
// extension that persists the same key so both sides stay in sync
// without one side having to import from the other.
export const currentOrganizationIdAtom = atomWithStorage<number | null>(
'currentOrganizationId',
null
);
export const GPUStackVersionAtom = atom<{ export const GPUStackVersionAtom = atom<{
version: string; version: string;
git_commit: string; git_commit: string;
@@ -30,9 +39,11 @@ export const initialPasswordAtom = atomWithStorage<string>(
// Namespace the server creates for an Org's resources on each Kubernetes // Namespace the server creates for an Org's resources on each Kubernetes
// cluster. The format must match the backend's ``get_namespace_name`` // cluster. The format must match the backend's ``get_namespace_name``
// helper — ``gpustack-{slug}`` — because the GPU-instance / storage CRDs // helper — ``gpustack-{name}`` — because the GPU-instance / storage CRDs
// (worker.gpustack.ai/v1) are namespaced and the server-side admission // (worker.gpustack.ai/v1) are namespaced and the server-side admission
// keys off this exact name. // keys off this exact name. The identifier column on the unified
// Principal table is now ``name`` (post identity-consolidation rename
// of the legacy ``slug``); the namespace prefix is unchanged.
// //
// Resolution path: // Resolution path:
// 1. The Org the caller is currently acting under — the enterprise // 1. The Org the caller is currently acting under — the enterprise
@@ -75,27 +86,61 @@ const getStoredCurrentOrgId = (): number | null => {
// the caller's member orgs; ``allOrganizations`` is admin-only (every // the caller's member orgs; ``allOrganizations`` is admin-only (every
// Org on the platform) so admin sessions can resolve any owner Org id. // Org on the platform) so admin sessions can resolve any owner Org id.
// Both are checked because ``currentOrganizationId`` is null in the // Both are checked because ``currentOrganizationId`` is null in the
// admin "All" view but a member org's slug might still cover the // admin "All" view but a member org's ``name`` might still cover the
// cluster-owner fallback. // cluster-owner fallback.
const ORG_CACHE_KEYS = ['organizationList', 'allOrganizations'] as const; const ORG_CACHE_KEYS = ['organizationList', 'allOrganizations'] as const;
const lookupOrgNamespace = (id: number | null): string | null => { export interface CachedOrg {
id: number;
name?: string;
// The platform Org (single global tenant). Its models are NOT
// namespaced in ``/v1/models`` — they appear under their bare name.
is_platform?: boolean;
}
// Resolve the cached Org record for an owner/principal id by scanning both
// org caches. ``organizationList`` (the caller's member orgs) is checked
// alongside the admin-only ``allOrganizations`` so member sessions resolve
// too. Id types vary between localStorage payloads (some writers stringify,
// others persist as a JSON number), so compare as strings — strict equality
// would silently miss those cases.
export const getOrgById = (
id: number | string | null | undefined
): CachedOrg | null => {
if (id == null) return null; if (id == null) return null;
// Normalise both sides to strings — the stored id type varies between
// localStorage payloads (some writers stringify, others persist as a
// JSON number); strict equality would silently miss those cases.
const target = String(id); const target = String(id);
for (const key of ORG_CACHE_KEYS) { for (const key of ORG_CACHE_KEYS) {
try { try {
const raw = localStorage.getItem(key); const raw = localStorage.getItem(key);
if (!raw) continue; if (!raw) continue;
const list = JSON.parse(raw) as Array<{ id: number; slug?: string }>; const list = JSON.parse(raw) as CachedOrg[];
if (!Array.isArray(list)) continue; if (!Array.isArray(list)) continue;
const match = list.find((item) => String(item?.id) === target); const match = list.find((item) => String(item?.id) === target);
if (match?.slug) return `gpustack-${match.slug}`; if (match) return match;
} catch { } catch {
// ignore malformed cache; continue checking other keys // ignore malformed cache; continue checking other keys
} }
} }
return null; return null;
}; };
// Bare Org *name* (e.g. ``org1``) for an owner/principal id, or null.
export const getOrgNameById = (
id: number | string | null | undefined
): string | null => {
return getOrgById(id)?.name ?? null;
};
const lookupOrgNamespace = (id: number | null): string | null => {
const name = getOrgNameById(id);
return name ? `gpustack-${name}` : null;
};
// The Org the caller is currently acting under, or null in the admin-"All"
// context. The org-switcher reloads the page on switch, so the list pages
// always show this org's resources — which is how ``/v1/models`` namespaces
// their model ids (``{org}/{name}``). Callers reconstructing that id use this
// as the fallback owner when a row carries no explicit ``owner_principal_id``.
export const getCurrentOrg = (): CachedOrg | null => {
return getOrgById(getStoredCurrentOrgId());
};
+7 -2
View File
@@ -6,12 +6,17 @@ export const clearStorageUserSettings = () => {
const savedSettings = JSON.parse( const savedSettings = JSON.parse(
localStorage.getItem('userSettings') || '{}' localStorage.getItem('userSettings') || '{}'
); );
// colorPrimary is an enterprise-wide branding setting (set by admins
// and applied by `onAppInit` from /enterprise/settings), not a per-user
// preference. Preserve it across login — otherwise the next layout
// mount triggers `atomWithStorage.onMount`, re-reads localStorage,
// and falls back to the default color until a full page refresh
// re-runs `applyEnterpriseSettings`.
localStorage.setItem( localStorage.setItem(
'userSettings', 'userSettings',
JSON.stringify({ JSON.stringify({
...savedSettings, ...savedSettings,
hideAddResourceModal: false, hideAddResourceModal: false
colorPrimary: undefined
}) })
); );
} catch (error) { } catch (error) {
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
+6
View File
@@ -92,6 +92,12 @@ declare namespace Global {
interface InitialStateType { interface InitialStateType {
fetchUserInfo: () => Promise<UserInfo>; fetchUserInfo: () => Promise<UserInfo>;
currentUser?: UserInfo; currentUser?: UserInfo;
// Captured at app boot so access predicates can gate GPU Service —
// the feature is Kubernetes-only, and Org members without a K8s
// cluster they can schedule on shouldn't see the menu. Refreshed
// by full page reload (e.g. OrgSwitcher) which re-runs
// getInitialState.
hasKubernetesCluster?: boolean;
} }
type SearchParams = Pagination & { search?: string; [key: string]: any }; type SearchParams = Pagination & { search?: string; [key: string]: any };
+1 -1
View File
@@ -84,4 +84,4 @@ export const modelNameReg =
*/ */
export const validateLabelNameRegxFor63 = export const validateLabelNameRegxFor63 =
/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/; /^(?![0-9])(?!.*--)[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
+4 -2
View File
@@ -54,7 +54,8 @@ export default {
itemHoverColor: 'rgba(0,0,0,1)', itemHoverColor: 'rgba(0,0,0,1)',
itemColor: 'rgba(0,0,0,1)', itemColor: 'rgba(0,0,0,1)',
itemHoverBg: 'rgb(24 25 27)', itemHoverBg: 'rgb(24 25 27)',
itemActiveBg: 'rgb(24 25 27)' itemActiveBg: 'rgb(24 25 27)',
menuItemSelectedBg: '#292929'
}, },
Progress: { Progress: {
lineBorderRadius: 4 lineBorderRadius: 4
@@ -108,6 +109,7 @@ export default {
fontSize: 14, fontSize: 14,
motion: true, motion: true,
colorFill: '#0A0A0A', colorFill: '#0A0A0A',
colorBgBase: '#0A0A0A' colorBgBase: '#0A0A0A',
menuItemSelectedBg: '#292929'
} }
}; };
+3 -1
View File
@@ -31,6 +31,7 @@ export default {
rowSelectedBg: 'transparent', rowSelectedBg: 'transparent',
headerSortActiveBg: 'transparent', headerSortActiveBg: 'transparent',
headerSortHoverBg: 'transparent', headerSortHoverBg: 'transparent',
headerSplitColor: '#e8e8e8',
headerBg: 'none' headerBg: 'none'
}, },
Button: { Button: {
@@ -55,7 +56,8 @@ export default {
itemHoverColor: 'rgba(0,0,0,1)', itemHoverColor: 'rgba(0,0,0,1)',
itemColor: 'rgba(0,0,0,1)', itemColor: 'rgba(0,0,0,1)',
itemHoverBg: 'rgba(0,0,0,0.04)', itemHoverBg: 'rgba(0,0,0,0.04)',
itemActiveBg: 'rgba(0,0,0,0.04)' itemActiveBg: 'rgba(0,0,0,0.04)',
menuItemSelectedBg: '#e8eaed'
}, },
Progress: { Progress: {
lineBorderRadius: 3 lineBorderRadius: 3
+18 -38
View File
@@ -6,6 +6,7 @@
html { html {
--page-header-height: 56px; --page-header-height: 56px;
--page-content-padding: 8px; --page-content-padding: 8px;
--app-banner-height: 0px;
--color-text-light-1: rgba(255, 255, 255, 90%); --color-text-light-1: rgba(255, 255, 255, 90%);
--color-fill-1: var(--ant-color-bg-container); --color-fill-1: var(--ant-color-bg-container);
--color-scrollbar-thumb: rgba(193, 193, 193, 80%); --color-scrollbar-thumb: rgba(193, 193, 193, 80%);
@@ -170,8 +171,6 @@ body {
} }
.ant-table .ant-table-container table { .ant-table .ant-table-container table {
// border-spacing: 0 20px;
.ant-table-thead th.ant-table-column-sort { .ant-table-thead th.ant-table-column-sort {
background-color: transparent; background-color: transparent;
@@ -237,12 +236,11 @@ body {
// ============== new theme style start =============== // ============== new theme style start ===============
.ant-pro-layout { .ant-pro-layout {
background-color: var(--color-fill-1);
height: 100%; height: 100%;
.ant-pro-sider-footer { .ant-pro-sider-footer {
padding-block: 2px 0; padding-block: 4px 0;
padding-left: 6px; padding-left: 8px;
} }
.ant-pro-sider-actions-list-item { .ant-pro-sider-actions-list-item {
@@ -259,46 +257,27 @@ body {
border-block-end: none; border-block-end: none;
} }
.ant-pro-sider-logo-collapsed { .ant-pro-sider-logo {
padding-left: 12px; .collapse-btn {
cursor: e-resize; color: var(--ant-color-text-tertiary);
:hover {
.collapse-wrap { color: var(--ant-color-text);
display: none;
position: absolute;
top: 12px;
&::after {
content: '';
position: absolute;
height: 48px;
width: 64px;
top: -16px;
left: -16px;
}
}
&:hover {
.collapse-wrap {
display: block;
} }
} }
} }
.ant-pro-sider .ant-layout-sider-children { .ant-pro-sider-logo-collapsed {
// border-right: 1px solid var(--ant-color-split); padding-left: 6px;
} }
} }
.ant-table-content table { .ant-table .ant-table-tbody {
.ant-table-tbody { .ant-table-row {
.ant-table-row { border-radius: var(--table-td-radius);
border-radius: var(--table-td-radius);
> td { > td {
background-color: unset; background-color: unset !important;
border-bottom: 1px solid var(--ant-color-split); border-bottom: 1px solid var(--ant-color-split);
}
} }
} }
} }
@@ -347,7 +326,6 @@ body {
.ant-pro-layout-container { .ant-pro-layout-container {
overflow-x: auto; overflow-x: auto;
min-height: 100vh; min-height: 100vh;
// background-color: var(--ant-color-bg-container);
} }
.ant-pro-sider { .ant-pro-sider {
@@ -812,6 +790,8 @@ body {
} }
.ant-pro-sider-logo + div { .ant-pro-sider-logo + div {
display: flex;
overflow: hidden;
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 0; width: 0;
} }
+8 -2
View File
@@ -1,4 +1,4 @@
import { TABLE_SORT_DIRECTIONS } from '@/config/settings'; import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings';
import useSetChunkRequest, { import useSetChunkRequest, {
createAxiosToken createAxiosToken
} from '@/hooks/use-chunk-request'; } from '@/hooks/use-chunk-request';
@@ -8,7 +8,6 @@ import { handleBatchRequest } from '@/utils';
import _ from 'lodash'; import _ from 'lodash';
import qs from 'query-string'; import qs from 'query-string';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { PaginationKey } from '../config/settings';
import { usePaginationStatus } from './use-pagination-status'; import { usePaginationStatus } from './use-pagination-status';
import { useTableMultiSort } from './use-table-sort'; import { useTableMultiSort } from './use-table-sort';
@@ -38,6 +37,7 @@ export default function useTableFetch<T>(
key?: (typeof PaginationKey)[keyof typeof PaginationKey]; key?: (typeof PaginationKey)[keyof typeof PaginationKey];
fetchAPI: (params: any, options?: any) => Promise<Global.PageResponse<T>>; fetchAPI: (params: any, options?: any) => Promise<Global.PageResponse<T>>;
deleteAPI?: (id: number, params?: any) => Promise<any>; deleteAPI?: (id: number, params?: any) => Promise<any>;
afterDelete?: (id?: number | number[]) => void;
contentForDelete?: string; contentForDelete?: string;
defaultData?: any[]; defaultData?: any[];
events?: EventsType[]; events?: EventsType[];
@@ -49,6 +49,7 @@ export default function useTableFetch<T>(
const { const {
fetchAPI, fetchAPI,
deleteAPI, deleteAPI,
afterDelete,
contentForDelete, contentForDelete,
API, API,
polling = false, polling = false,
@@ -265,6 +266,7 @@ export default function useTableFetch<T>(
url: `${API}?${qs.stringify(_.pickBy(query, (val: any) => !!val))}`, url: `${API}?${qs.stringify(_.pickBy(query, (val: any) => !!val))}`,
handler: updateHandler handler: updateHandler
}); });
// eslint-disable-next-line react-hooks/purity
triggerAtRef.current = Date.now(); triggerAtRef.current = Date.now();
} catch (error) { } catch (error) {
// ignore // ignore
@@ -361,6 +363,9 @@ export default function useTableFetch<T>(
...modalRef.current?.configuration ...modalRef.current?.configuration
}); });
// remove the deleted id from selected ids in row selection
rowSelection.removeSelectedKeys([row.id]);
afterDelete?.(row.id);
// ======== to avoid fetch data twice, because of debounceFetchData has been run ======= // ======== to avoid fetch data twice, because of debounceFetchData has been run =======
if (!updateManually) { if (!updateManually) {
fetchData(); fetchData();
@@ -387,6 +392,7 @@ export default function useTableFetch<T>(
successIds.push(id); successIds.push(id);
} }
); );
afterDelete?.(successIds);
rowSelection.removeSelectedKeys(successIds); rowSelection.removeSelectedKeys(successIds);
fetchData(); fetchData();
return res; return res;
+10 -18
View File
@@ -14,7 +14,6 @@ type EventsType = 'CREATE' | 'UPDATE' | 'DELETE' | 'INSERT';
export function useUpdateChunkedList(options: { export function useUpdateChunkedList(options: {
events?: EventsType[]; events?: EventsType[];
dataList?: any[]; dataList?: any[];
triggerAt?: React.MutableRefObject<number>;
limit?: number; limit?: number;
onCreate?: (args: any) => void; onCreate?: (args: any) => void;
onUpdate?: (args: any) => void; onUpdate?: (args: any) => void;
@@ -24,9 +23,9 @@ export function useUpdateChunkedList(options: {
filterFun?: (args: any) => boolean; filterFun?: (args: any) => boolean;
mapFun?: (args: any) => any; mapFun?: (args: any) => any;
computedID?: (d: object) => string; computedID?: (d: object) => string;
isNewItem?: (item: any) => boolean;
}) { }) {
const { events = ['CREATE', 'DELETE', 'UPDATE', 'INSERT'], triggerAt } = const { events = ['CREATE', 'DELETE', 'UPDATE', 'INSERT'] } = options;
options;
const deletedIdsRef = useRef<Set<number | string>>(new Set()); const deletedIdsRef = useRef<Set<number | string>>(new Set());
const cacheDataListRef = useRef<any[]>(options.dataList || []); const cacheDataListRef = useRef<any[]>(options.dataList || []);
const timerRef = useRef<any>(null); const timerRef = useRef<any>(null);
@@ -71,17 +70,14 @@ export function useUpdateChunkedList(options: {
(sItem: any) => sItem.id === item.id (sItem: any) => sItem.id === item.id
); );
const updateItem = { ...item }; const updateItem = { ...item };
if (updateIndex === -1 && !triggerAt?.current) { if (updateIndex === -1) {
acc.push(updateItem); acc.push(updateItem);
} else if (!triggerAt?.current) { } else {
cacheDataListRef.current[updateIndex] = updateItem; cacheDataListRef.current[updateIndex] = updateItem;
} }
// only push items created after the watch started
// TODO only push items created after triggerAt if (options.isNewItem?.(item)) {
if (
triggerAt?.current &&
Date.parse(item.created_at) >= triggerAt.current
) {
latestCreateList.push(updateItem); latestCreateList.push(updateItem);
} }
@@ -103,10 +99,8 @@ export function useUpdateChunkedList(options: {
cacheDataListRef.current = cacheDataListRef.current?.filter( cacheDataListRef.current = cacheDataListRef.current?.filter(
(item: any) => { (item: any) => {
// collect deleted items // collect deleted items
if (triggerAt?.current) { if (ids?.includes(item.id) && !options.isNewItem?.(item)) {
if (ids?.includes(item.id)) { deletedList.push(item);
deletedList.push(item);
}
} }
return !ids?.includes(item.id); return !ids?.includes(item.id);
} }
@@ -133,10 +127,8 @@ export function useUpdateChunkedList(options: {
updateItem, updateItem,
...cacheDataListRef.current.slice(0, limit - 1) ...cacheDataListRef.current.slice(0, limit - 1)
]; ];
if (options.onUpdate && triggerAt?.current) { if (options.onUpdate && options.isNewItem?.(item)) {
if (Date.parse(item.created_at) >= triggerAt.current) { options.onUpdate?.([updateItem]);
options.onUpdate?.([updateItem]);
}
} }
} }
}); });
+3 -3
View File
@@ -210,14 +210,14 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
key: 'settings', key: 'settings',
label: ( label: (
<span className="flex flex-center"> <span className="flex flex-center">
<IconFont type="icon-settings-02" /> <IconFont type="icon-preferences" />
<span className="m-l-8" style={{ marginLeft: 8 }}> <span className="m-l-8" style={{ marginLeft: 8 }}>
{intl?.formatMessage?.({ id: 'common.button.settings' })} {intl?.formatMessage?.({ id: 'common.preferences' })}
</span> </span>
</span> </span>
), ),
onClick: () => { onClick: () => {
history.push('/profile'); history.push('/preferences');
} }
} }
] ]
+52 -56
View File
@@ -1,6 +1,8 @@
import { routeCacheAtom, setRouteCache } from '@/atoms/route-cache'; import { routeCacheAtom, setRouteCache } from '@/atoms/route-cache';
import { userAtom } from '@/atoms/user'; import { userAtom } from '@/atoms/user';
import DarkMask from '@/components/dark-mask'; import DarkMask from '@/components/dark-mask';
import '@/components/iconfont/iconfont.js';
import PluginExtraFields from '@/components/plugin-extra-fields';
import routeCachekey from '@/config/route-cachekey'; import routeCachekey from '@/config/route-cachekey';
import { DEFAULT_ENTER_PAGE, GPUSTACK_API_BASE_URL } from '@/config/settings'; import { DEFAULT_ENTER_PAGE, GPUSTACK_API_BASE_URL } from '@/config/settings';
import { COLOR_PRIMARY } from '@/config/theme'; import { COLOR_PRIMARY } from '@/config/theme';
@@ -19,12 +21,9 @@ import {
import { useAccessMarkedRoutes } from '@@/plugin-access'; import { useAccessMarkedRoutes } from '@@/plugin-access';
import { useModel } from '@@/plugin-model'; import { useModel } from '@@/plugin-model';
import { ProLayout } from '@ant-design/pro-components'; import { ProLayout } from '@ant-design/pro-components';
import { CoreUIProvider, IconFont } from '@gpustack/core-ui';
import { import {
CoreUIProvider, Access,
IconFont,
useOverlayScroller
} from '@gpustack/core-ui';
import {
Outlet, Outlet,
dropByCacheKey, dropByCacheKey,
getAllLocales, getAllLocales,
@@ -64,16 +63,14 @@ const NO_CONTAINER_PAGES = [
'clusterCreate', 'clusterCreate',
'benchmarkDetail', 'benchmarkDetail',
'deployment', 'deployment',
'video', 'video'
'instances',
'storage'
]; ];
const CHECK_RESOURCE_PATH = [ const CHECK_RESOURCE_PATH = [
'/resources/workers', '/resources/workers',
'/cluster-management/clusters/list', '/resources/clusters/list',
'/cluster-management/credentials', '/resources/credentials',
'/cluster-management/clusters/create' '/resources/clusters/create'
]; ];
type NewRoute = IRoute & { type NewRoute = IRoute & {
@@ -134,9 +131,6 @@ const mapRoutes = (routes: IRoute[], role: string) => {
}; };
export default (props: any) => { export default (props: any) => {
const { initialize: initialize } = useOverlayScroller({
defer: false
});
const [, contextHolder] = Modal.useModal(); const [, contextHolder] = Modal.useModal();
const { themeData, setUserSettings, userSettings } = useUserSettings(); const { themeData, setUserSettings, userSettings } = useUserSettings();
const [userInfo] = useAtom(userAtom); const [userInfo] = useAtom(userAtom);
@@ -240,30 +234,7 @@ export default (props: any) => {
}, [userSettings.collapsed]); }, [userSettings.collapsed]);
const renderMenuHeader = (logo: React.ReactNode, title: React.ReactNode) => { const renderMenuHeader = (logo: React.ReactNode, title: React.ReactNode) => {
return ( return <>{logo}</>;
<>
{logo}
<div className="collapse-wrap" onClick={handleToggleCollapse}>
<Button
style={{
marginRight: collapsed ? 0 : -14,
border: 'none',
cursor: 'w-resize'
}}
size="small"
type={collapsed ? 'default' : 'text'}
>
<IconFont
type={collapsed ? 'icon-expand-left' : 'icon-expand-right'}
className="font-size-18 text-secondary"
style={{
display: 'block'
}}
/>
</Button>
</div>
</>
);
}; };
const menuContentRender = (menuProps: any, defaultDom: React.ReactNode) => { const menuContentRender = (menuProps: any, defaultDom: React.ReactNode) => {
@@ -356,7 +327,7 @@ export default (props: any) => {
config={{ config={{
apiBaseUrl: GPUSTACK_API_BASE_URL, apiBaseUrl: GPUSTACK_API_BASE_URL,
theme: userSettings.theme, theme: userSettings.theme,
iconUrl: '//at.alicdn.com/t/c/font_4613488_r6z6oew38db.js', iconUrl: '//at.alicdn.com/t/c/font_4613488_rwchdketjg8.js',
isDarkTheme: userSettings.isDarkTheme, isDarkTheme: userSettings.isDarkTheme,
defaultColorPrimary: COLOR_PRIMARY defaultColorPrimary: COLOR_PRIMARY
}} }}
@@ -387,6 +358,7 @@ export default (props: any) => {
writeState writeState
}} }}
slots={coreUISlots} slots={coreUISlots}
access={{ Access, useAccess }}
> >
<DarkMask></DarkMask> <DarkMask></DarkMask>
<ProLayout <ProLayout
@@ -406,9 +378,23 @@ export default (props: any) => {
openKeys={false} openKeys={false}
disableMobile={true} disableMobile={true}
siderWidth={220} siderWidth={220}
menuFooterRender={() => (
<Button
style={{
border: 'none'
}}
size="small"
type={'text'}
onClick={handleToggleCollapse}
>
<IconFont
type={collapsed ? 'icon-expand-left' : 'icon-expand-right'}
className="font-size-18"
/>
</Button>
)}
onCollapse={onCollapse} onCollapse={onCollapse}
onMenuHeaderClick={onMenuHeaderClick} onMenuHeaderClick={onMenuHeaderClick}
menuHeaderRender={renderMenuHeader}
collapsed={userSettings.collapsed} collapsed={userSettings.collapsed}
onPageChange={onPageChange} onPageChange={onPageChange}
formatMessage={formatMessage} formatMessage={formatMessage}
@@ -422,23 +408,33 @@ export default (props: any) => {
{...runtimeConfig} {...runtimeConfig}
ErrorBoundary={ErrorBoundary} ErrorBoundary={ErrorBoundary}
> >
<Exception <div
route={matchedRoute} style={{
notFound={runtimeConfig?.notFound} display: 'flex',
noFound={runtimeConfig?.noFound} flexDirection: 'column',
unAccessible={runtimeConfig?.unAccessible} height: '100vh',
noAccessible={runtimeConfig?.noAccessible} overflow: 'hidden'
}}
> >
{isNoContainerPage ? ( <PluginExtraFields name="GlobalLicenseBanner" />
<Outlet /> <Exception
) : ( route={matchedRoute}
<PageContainerInner> notFound={runtimeConfig?.notFound}
<div> noFound={runtimeConfig?.noFound}
<Outlet /> unAccessible={runtimeConfig?.unAccessible}
</div> noAccessible={runtimeConfig?.noAccessible}
</PageContainerInner> >
)} {isNoContainerPage ? (
</Exception> <Outlet />
) : (
<PageContainerInner>
<div>
<Outlet />
</div>
</PageContainerInner>
)}
</Exception>
</div>
{NoResourceModal} {NoResourceModal}
{contextHolder} {contextHolder}
</ProLayout> </ProLayout>
+1 -1
View File
@@ -256,7 +256,7 @@ export const getRightRenderContent = (opts: {
</span> </span>
), ),
onClick: () => { onClick: () => {
history.push('/profile'); history.push('/preferences');
} }
}, },
{ {
+167 -149
View File
@@ -1,8 +1,8 @@
import { CaretDownOutlined } from '@ant-design/icons'; import { CaretDownOutlined } from '@ant-design/icons';
import { IconFont } from '@gpustack/core-ui'; import { IconFont, OverlayScroller } from '@gpustack/core-ui';
import { Link, useLocation } from '@umijs/max'; import { Link, useLocation } from '@umijs/max';
import { Tooltip } from 'antd'; import { Tooltip } from 'antd';
import { createStyles } from 'antd-style'; import { createStyles, type FullToken } from 'antd-style';
import React, { useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
interface MenuItem { interface MenuItem {
@@ -20,121 +20,128 @@ interface SiderMenuProps {
initialState: Global.InitialStateType; initialState: Global.InitialStateType;
} }
const useStyles = createStyles(({ css, token }) => { const useStyles = createStyles(
console.log('useStyles', token); ({ css, token }: { css: any; token: FullToken }) => {
console.log('useStyles', token);
// @ts-ignore // @ts-ignore
const { Menu } = token; const { Menu } = token;
return { return {
siderMenu: css` siderMenu: css`
&.sider-menu-collapsed {
.menu-item {
justify-content: center;
padding: 0;
}
}
`,
groupTitle: css`
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
white-space: nowrap;
padding: var(--ant-padding-xs) var(--ant-padding);
font-size: 12px;
padding-bottom: 4px;
overflow: hidden;
height: 30px;
&:hover {
.group-title-text {
color: var(--ant-color-text);
}
}
.anticon {
transform: scale(0.8);
}
.group-title-text {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: var(--ant-color-text-tertiary);
font-weight: 400;
}
&.menu-item-group-title-collapsed {
position: relative;
height: 1px;
padding-block: 0;
padding-inline: 0;
justify-content: center;
}
`,
menuItemContent: css`
margin: 2px 0;
border-radius: 4px;
overflow: hidden;
`,
menuItemWrapper: css`
display: flex;
align-items: center;
justify-content: flex-start;
gap: 12px;
cursor: pointer;
position: relative;
padding-inline: calc(var(--ant-font-size) * 2) var(--ant-padding);
padding-left: 16px;
overflow: hidden;
white-space: nowrap;
height: ${Menu.itemHeight}px;
line-height: ${Menu.itemHeight}px;
color: var(--ant-color-text-tertiary);
&:hover {
background-color: ${Menu.itemHoverBg};
color: ${Menu.itemHoverColor};
}
&.menu-item-selected {
background-color: ${Menu.itemSelectedBg};
color: ${Menu.itemSelectedColor};
.anticon {
color: ${Menu.itemSelectedColor};
}
}
&:active {
background-color: ${Menu.itemActiveBg};
color: ${Menu.itemActiveColor};
}
.anticon {
font-size: 16px;
}
.icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 100%; width: 100%;
height: 100%; &.sider-menu-collapsed {
} .menu-item {
`, justify-content: center;
menuItemGroup: css` padding: 0;
&.menu-item-group-hidden { }
display: none; }
} .os-scrollbar-vertical .os-scrollbar-handle {
`, min-width: 4px;
line: css` max-width: 4px;
height: 1px; }
margin-block: 6px; `,
background-color: ${token.colorSplit}; groupTitle: css`
position: absolute; display: flex;
left: -2px; align-items: center;
right: -2px; gap: 8px;
` cursor: pointer;
}; white-space: nowrap;
}); padding: var(--ant-padding-xs) var(--ant-padding);
font-size: 12px;
padding-bottom: 4px;
overflow: hidden;
height: 30px;
&:hover {
.group-title-text {
color: var(--ant-color-text);
}
}
.anticon {
transform: scale(0.8);
}
.group-title-text {
display: flex;
align-items: center;
gap: 4px;
font-size: 12px;
color: var(--ant-color-text);
font-weight: 400;
}
&.menu-item-group-title-collapsed {
position: relative;
height: 1px;
padding-block: 0;
padding-inline: 0;
justify-content: center;
}
`,
menuItemContent: css`
margin: 2px 0;
border-radius: 4px;
overflow: hidden;
`,
menuItemWrapper: css`
display: flex;
align-items: center;
justify-content: flex-start;
gap: 12px;
cursor: pointer;
position: relative;
padding-inline: calc(var(--ant-font-size) * 2) var(--ant-padding);
padding-left: 16px;
overflow: hidden;
white-space: nowrap;
height: ${Menu.itemHeight}px;
line-height: ${Menu.itemHeight}px;
color: var(--ant-color-text-tertiary);
&:hover {
background-color: ${Menu.itemHoverBg};
color: ${Menu.itemHoverColor};
}
&.menu-item-selected {
background-color: ${Menu.menuItemSelectedBg};
color: ${Menu.itemSelectedColor};
.anticon {
color: ${Menu.itemSelectedColor};
}
}
&:active {
background-color: ${Menu.itemActiveBg};
color: ${Menu.itemActiveColor};
}
.anticon {
font-size: 16px;
}
.icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
`,
menuItemGroup: css`
&.menu-item-group-hidden {
display: none;
}
`,
line: css`
height: 1px;
margin-block: 6px;
background-color: ${token.colorSplit};
position: absolute;
left: -2px;
right: -2px;
`
};
}
);
const SiderMenu: React.FC<SiderMenuProps> = (props) => { const SiderMenu: React.FC<SiderMenuProps> = (props) => {
const { menuData, collapsed, initialState } = props; const { menuData, collapsed } = props;
const is_admin = initialState?.currentUser?.is_admin || false;
const { styles, cx } = useStyles(); const { styles, cx } = useStyles();
const location = useLocation(); const location = useLocation();
const [collapseKeys, setCollapseKeys] = useState<Set<string>>(new Set()); const [collapseKeys, setCollapseKeys] = useState<Set<string>>(new Set());
@@ -220,44 +227,55 @@ const SiderMenu: React.FC<SiderMenuProps> = (props) => {
'sider-menu-collapsed': collapsed 'sider-menu-collapsed': collapsed
})} })}
> >
{menuData.map((item: MenuItem, index: number) => ( <OverlayScroller
<div key={item.key}> styles={{
{item.children && item.children.length > 0 ? ( wrapper: {
<> paddingInline: 0,
<div maxHeight: '100%'
className={cx(styles.groupTitle, { }
'menu-item-group-title-collapsed': collapsed }}
})} >
onClick={(e) => handleToggleGroup(e, item)} <div style={{ paddingRight: 8 }}>
> {menuData.map((item: MenuItem, index: number) => (
{!collapsed ? ( <div key={item.key}>
<span className="group-title-text"> {item.children && item.children.length > 0 ? (
<span>{item.name}</span> <>
<CaretDownOutlined <div
rotate={collapseKeys.has(item.key) ? -90 : 0} className={cx(styles.groupTitle, {
></CaretDownOutlined> 'menu-item-group-title-collapsed': collapsed
</span> })}
) : is_admin ? ( onClick={(e) => handleToggleGroup(e, item)}
<span className={styles.line}></span> >
) : null} {!collapsed ? (
</div> <span className="group-title-text">
<div <span>{item.name}</span>
className={cx(styles.menuItemGroup, { <CaretDownOutlined
'menu-item-group-collapsed': collapsed, rotate={collapseKeys.has(item.key) ? -90 : 0}
'menu-item-group-hidden': ></CaretDownOutlined>
!collapsed && collapseKeys.has(item.key) </span>
})} ) : (
> <span className={styles.line}></span>
{item.children?.map((child: MenuItem) => )}
menuItemRender(child, child.key) </div>
)} <div
</div> className={cx(styles.menuItemGroup, {
</> 'menu-item-group-collapsed': collapsed,
) : ( 'menu-item-group-hidden':
menuItemRender(item, item.key) !collapsed && collapseKeys.has(item.key)
)} })}
>
{item.children?.map((child: MenuItem) =>
menuItemRender(child, child.key)
)}
</div>
</>
) : (
menuItemRender(item, item.key)
)}
</div>
))}
</div> </div>
))} </OverlayScroller>
</div> </div>
); );
}; };
+3 -1
View File
@@ -23,5 +23,7 @@ export default {
'apikeys.accessScope.inference': 'Inference APIs', 'apikeys.accessScope.inference': 'Inference APIs',
'apikeys.access.permissions': 'Access Permissions', 'apikeys.access.permissions': 'Access Permissions',
'apikeys.type.auto': 'Auto-generated', 'apikeys.type.auto': 'Auto-generated',
'apikeys.type.custom': 'Custom' 'apikeys.type.custom': 'Custom',
'apikeys.button.ipConfig': 'IP Access Control',
'quotaLimits.button.title': 'Quota Limit'
}; };
+7
View File
@@ -23,6 +23,13 @@ export default {
'backend.form.defaultExecuteCommand': 'Default Execution Command', 'backend.form.defaultExecuteCommand': 'Default Execution Command',
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' and '{{'model_name'}}' are placeholders that will be substituted with the actual values during deployment.`, 'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' and '{{'model_name'}}' are placeholders that will be substituted with the actual values during deployment.`,
'backend.form.defaultBackendParameters': 'Default Backend Parameters', 'backend.form.defaultBackendParameters': 'Default Backend Parameters',
'backend.form.parameterFormat': 'Parameter Format',
'backend.form.parameterFormat.default': 'Backend Default',
'backend.form.parameterFormat.space': 'Space (--key value)',
'backend.form.parameterFormat.equal': 'Equal (--key=value)',
'backend.form.commonParameters': 'Common Parameters',
'backend.form.commonParameters.tips':
'Shown as suggestions in the backend parameters input during deployment.',
'backend.form.versionConfig': 'Versions Config', 'backend.form.versionConfig': 'Versions Config',
'backend.form.addParameter': 'Add Parameter', 'backend.form.addParameter': 'Add Parameter',
'backend.form.noVersion': 'No versions added', 'backend.form.noVersion': 'No versions added',
+37 -2
View File
@@ -39,9 +39,11 @@ export default {
'clusters.workerpool.batchSize.desc': 'clusters.workerpool.batchSize.desc':
'Number of workers created simultaneously in the Worker pool', 'Number of workers created simultaneously in the Worker pool',
'clusters.create.addworker.tips': 'clusters.create.addworker.tips':
'Please make sure the <a href={link} target="_blank">prerequisites</a> for {label} are met before executing the following command.', 'Please make sure the <a href={link} target="_blank">prerequisites</a> are met before executing the following command.',
'clusters.create.addCommand.tips': 'clusters.create.addCommand.tips':
'On the Worker that needs to be added, run the following command to join it to the cluster.', 'On the Worker that needs to be added, run the following command to join it to the cluster.',
'clusters.create.addCommand.k8s.tips':
'On the Kubernetes cluster that needs to be registered, run the following command to create the Kubernetes resources and register the cluster.',
'clusters.create.register.tips': 'clusters.create.register.tips':
'On the Kubernetes cluster that needs to be added, run the following command to join its nodes to the cluster.', 'On the Kubernetes cluster that needs to be added, run the following command to join its nodes to the cluster.',
'cluster.create.checkEnv.tips': 'cluster.create.checkEnv.tips':
@@ -67,6 +69,7 @@ export default {
'clusters.addworker.selectCluster.tips': 'clusters.addworker.selectCluster.tips':
'For <span class="bold-text">non-Docker</span> clusters, please register clusters or manage worker pools from the Clusters page.', 'For <span class="bold-text">non-Docker</span> clusters, please register clusters or manage worker pools from the Clusters page.',
'clusters.addworker.selectGPU': 'Select GPU Vendor', 'clusters.addworker.selectGPU': 'Select GPU Vendor',
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
'clusters.addworker.checkEnv': 'Check Environment', 'clusters.addworker.checkEnv': 'Check Environment',
'clusters.addworker.specifyArgs': 'Specify Arguments', 'clusters.addworker.specifyArgs': 'Specify Arguments',
'clusters.addworker.runCommand': 'Run Command', 'clusters.addworker.runCommand': 'Run Command',
@@ -107,6 +110,10 @@ export default {
'{count} new workers have been added to the cluster.', '{count} new workers have been added to the cluster.',
'clusters.create.serverUrl': 'GPUStack Server URL', 'clusters.create.serverUrl': 'GPUStack Server URL',
'clusters.create.workerConfig': 'Worker Configuration', 'clusters.create.workerConfig': 'Worker Configuration',
'clusters.edit.k8sOptions.changed.tip':
'You have changed the Kubernetes options. Re-run the registration command on the target cluster for the changes to take effect.',
'clusters.edit.workerConfig.tip':
'Changes to the worker configuration take effect only after restarting the affected workers.',
'clusters.addworker.containerName': 'Worker Container Name', 'clusters.addworker.containerName': 'Worker Container Name',
'clusters.addworker.containerName.tips': 'clusters.addworker.containerName.tips':
'Specify a name for the worker container.', 'Specify a name for the worker container.',
@@ -158,5 +165,33 @@ export default {
'clusters.volume.pvc.readOnly': 'Read Only', 'clusters.volume.pvc.readOnly': 'Read Only',
'clusters.volume.configMap.name': 'ConfigMap Name', 'clusters.volume.configMap.name': 'ConfigMap Name',
'clusters.volume.configMap.optional': 'Optional', 'clusters.volume.configMap.optional': 'Optional',
'clusters.volume.add': 'Add Volume Mount' 'clusters.volume.add': 'Add Volume Mount',
'clusters.systemDefaultContainerRegistry.title': 'Default Container Registry',
'clusters.systemDefaultContainerRegistry.tip':
'Default registry used to resolve GPUStack images for this cluster. Falls back to the server default when unset.',
'clusters.k8sOptions.title': 'Kubernetes Deployment Options',
'clusters.imageCredentials.title': 'Image Credentials',
'clusters.imageCredentials.add': 'Add Credential',
'clusters.imageCredentials.registry': 'Registry',
'clusters.imageCredentials.username': 'Username',
'clusters.imageCredentials.password': 'Password',
'clusters.nodeSelector.title': 'Node Selector',
'clusters.nodeSelector.tip':
'Pod nodeSelector applied to every worker DaemonSet — only nodes whose labels match are eligible to run the worker.',
'clusters.operatorImage.title': 'Operator Image',
'clusters.operatorImage.tip':
'Override for the GPUStack Operator container image. Leave empty to use the server default.',
'clusters.namespace.title': 'Namespace',
'clusters.namespace.tip':
'Kubernetes namespace the clusters manifests render into. Leave empty to use gpustack-system.',
'clusters.clusterType.title': 'Cluster Type',
'clusters.modelService.title': 'Model Service',
'clusters.modelService.tip':
'For LLM inference and API serving — e.g. exposing model APIs and token-based services.',
'clusters.gpuInstances.title': 'GPU Service',
'clusters.gpuInstances.tip':
'For on-demand GPU compute — e.g. interactive development, training jobs, or custom environments.',
'clusters.gpuInstances.staticAddress': 'GPU Service Static Access Address',
'clusters.gpuInstances.staticAddress.tip':
'Static address the operator uses to access GPU instances in this cluster (e.g. a LoadBalancer VIP). Optional.'
}; };
+11 -2
View File
@@ -46,22 +46,28 @@ export default {
'common.button.enabled': 'Enabled', 'common.button.enabled': 'Enabled',
'common.button.disabled': 'Disabled', 'common.button.disabled': 'Disabled',
'common.button.upgrade': 'Upgrade', 'common.button.upgrade': 'Upgrade',
'common.enterprise.feature': 'Available in GPUStack Enterprise',
'common.input.holder': 'Please enter', 'common.input.holder': 'Please enter',
'common.validate.value': '{name} value is required', 'common.validate.value': '{name} value is required',
'common.button.edit': 'Edit', 'common.button.edit': 'Edit',
'common.button.authorize': 'Role Authorization', 'common.button.authorize': 'Role Authorization',
'common.button.confirm': 'Confirm', 'common.button.confirm': 'Confirm',
'common.button.viewlog': 'View Logs', 'common.button.viewlog': 'View Logs',
'common.button.viewevent': 'View Events',
'common.button.recreate': 'Recreate',
'common.table.operation': 'Operations', 'common.table.operation': 'Operations',
'common.table.creator': 'Creator',
'common.table.createTime': 'Created', 'common.table.createTime': 'Created',
'common.table.updateTime': 'Updated', 'common.table.updateTime': 'Updated',
'common.table.description': 'Description', 'common.table.description': 'Description',
'common.table.displayName': 'Display Name',
'common.table.name': 'Name', 'common.table.name': 'Name',
'common.table.status': 'Status', 'common.table.status': 'Status',
'common.table.name.list': '{type} Name', 'common.table.name.list': '{type} Name',
'common.search.name.placeholder': 'filter by name', 'common.search.name.placeholder': 'filter by name',
'common.search.id.placeholder': 'filter by ID', 'common.search.id.placeholder': 'filter by ID',
'common.filter.byId': 'filter by ID', 'common.filter.byId': 'filter by ID',
'common.filter.byCreator': 'Filter by creator',
'common.table.type': 'Type', 'common.table.type': 'Type',
'common.table.default': 'Default Value', 'common.table.default': 'Default Value',
'common.copy.success': 'Copied success!', 'common.copy.success': 'Copied success!',
@@ -223,7 +229,6 @@ export default {
'common.text.latest': 'Latest', 'common.text.latest': 'Latest',
'common.text.new': 'New', 'common.text.new': 'New',
'common.text.changelog': 'Release Notes', 'common.text.changelog': 'Release Notes',
'common.button.recreate': 'Recreate',
'common.button.delrecreate': 'Delete (Recreate)', 'common.button.delrecreate': 'Delete (Recreate)',
'common.options.all': 'All', 'common.options.all': 'All',
'common.options.none': 'None', 'common.options.none': 'None',
@@ -281,5 +286,9 @@ export default {
'common.file.format.limit': 'Invalid file format. Allowed: {formats}.', 'common.file.format.limit': 'Invalid file format. Allowed: {formats}.',
'common.image.limit.width': 'Image width must be {width}.', 'common.image.limit.width': 'Image width must be {width}.',
'common.image.limit.height': 'Image height must be {height}.', 'common.image.limit.height': 'Image height must be {height}.',
'common.max': 'Max {count}' 'common.remaining': 'Remaining {count}',
'common.max': 'Max {count}',
'common.max.count': '{label} Count',
'common.validate.group': 'Please complete the {group} configuration',
'common.preferences': 'Preferences'
}; };
+5 -14
View File
@@ -1,32 +1,23 @@
export default { export default {
'dashboard.title': 'Dashboard',
'dashboard.workers': 'Workers', 'dashboard.workers': 'Workers',
'dashboard.models': 'Models', 'dashboard.deployments': 'Deployments',
'dashboard.clusters': 'Clusters', 'dashboard.clusters': 'Clusters',
'dashboard.totalgpus': 'GPUs', 'dashboard.totalgpus': 'GPUs',
'dashboard.allocategpus': 'Allocated GPUs',
'dashboard.instances': 'Instances',
'dashboard.systemload': 'System Load', 'dashboard.systemload': 'System Load',
'dashboard.memory': 'RAM', 'dashboard.memory': 'RAM',
'dashboard.disk': 'Storage',
'dashboard.vram': 'VRAM', 'dashboard.vram': 'VRAM',
'dashboard.cpuutilization': 'Average CPU Utilization', 'dashboard.cpuutilization': 'Average CPU Utilization',
'dashboard.memoryutilization': 'Average RAM Utilization', 'dashboard.memoryutilization': 'Average RAM Utilization',
'dashboard.diskutilization': 'Storage Utilization',
'dashboard.vramutilization': 'Average VRAM Utilization', 'dashboard.vramutilization': 'Average VRAM Utilization',
'dashboard.gpuutilization': 'Average GPU Utilization', 'dashboard.gpuutilization': 'Average GPU Utilization',
'dashboard.usage': 'Usage', 'dashboard.usage': 'Usage',
'dashboard.apirequest': 'API Requests', 'dashboard.usage.title': 'Last {days} days usage',
'dashboard.usage.others': 'Others',
'dashboard.tokens': 'Token Usage', 'dashboard.tokens': 'Token Usage',
'dashboard.topusers': 'Top Users', 'dashboard.topusers': 'Top Users',
'dashboard.activeModels': 'Active Models', 'dashboard.activeDeployments': 'Active Deployments',
'dashboard.activeUsers': 'Active Users', 'dashboard.usageByModel': 'Usage by Model',
'dashboard.tokenUsageByModel': 'Token Usage by Model',
'dashboard.apiRequestsByModel': 'API Requests by Model',
'dashboard.topTokenUsageByUser': 'Top 10 Token Usage by User', 'dashboard.topTokenUsageByUser': 'Top 10 Token Usage by User',
'dashboard.topTokenUsageByApiKey': 'Top 10 Token Usage by API Key',
'dashboard.runninginstances': 'Running Instances',
'dashboard.activeModels.name': 'Model Name',
'dashboard.allocatevram': 'Allocated VRAM / RAM', 'dashboard.allocatevram': 'Allocated VRAM / RAM',
'dashboard.usage.selectuser': 'Select users', 'dashboard.usage.selectuser': 'Select users',
'dashboard.usage.selectmodel': 'Select models', 'dashboard.usage.selectmodel': 'Select models',
+104 -10
View File
@@ -13,12 +13,26 @@ export default {
'gpuservice.template.command.placeholder': 'gpuservice.template.command.placeholder':
'Separate arguments with spaces; wrap arguments containing spaces in quotes, e.g.: /bin/bash -c "echo hello world"', 'Separate arguments with spaces; wrap arguments containing spaces in quotes, e.g.: /bin/bash -c "echo hello world"',
'gpuservice.template.mountPath': 'Mount Path', 'gpuservice.template.mountPath': 'Mount Path',
'gpuservice.template.mountPath.tips':
'The default mount path for the storage volume when creating an instance from this template. Useful for persisting data that needs to be retained while the instance is running.',
'gpuservice.template.containerDisk': 'Container Disk (GB)', 'gpuservice.template.containerDisk': 'Container Disk (GB)',
'gpuservice.template.containerDisk.tips':
'The size of the container system disk.',
'gpuservice.template.memory': 'Memory (GB)', 'gpuservice.template.memory': 'Memory (GB)',
'gpuservice.instance.containerDisk.remaining':
'Container Disk (Max {count} GB)',
'gpuservice.instance.memory.remaining': 'Memory (Max {count} GB)',
'gpuservice.template.displayName': 'Display Name',
'gpuservice.template.displayName.max':
'Display name cannot exceed 63 characters.',
'gpuservice.template.ports': 'Ports', 'gpuservice.template.ports': 'Ports',
'gpuservice.template.ports.add': 'Add Port', 'gpuservice.template.ports.add': 'Add Port',
'gpuservice.template.ports.invalid': 'gpuservice.template.ports.invalid':
'Please complete the port configuration.', 'Please complete the port configuration.',
'gpuservice.template.ports.name': 'Name',
'gpuservice.template.ports.name.max':
'Port name cannot exceed 16 characters.',
'gpuservice.template.ports.name.duplicate': 'Port names must be unique.',
'gpuservice.template.env': 'Environment Variables', 'gpuservice.template.env': 'Environment Variables',
'gpuservice.template.env.add': 'Add Environment Variable', 'gpuservice.template.env.add': 'Add Environment Variable',
'gpuservice.template.env.invalid': 'gpuservice.template.env.invalid':
@@ -29,7 +43,49 @@ export default {
'gpuservice.template.card.mount': 'Mount', 'gpuservice.template.card.mount': 'Mount',
'gpuservice.template.card.resources': 'Resources', 'gpuservice.template.card.resources': 'Resources',
'gpuservice.template.card.ports': 'Ports', 'gpuservice.template.card.ports': 'Ports',
'gpuservice.storageType': 'Storage Type',
'gpuservice.storageType.add': 'Add Storage Type',
'gpuservice.storageType.edit': 'Edit Storage Type',
'gpuservice.storageType.filter.name': 'Search by name',
'gpuservice.storageType.kind': 'Type',
'gpuservice.storageType.mountOptions': 'Mount Options',
'gpuservice.storageType.nfs.server': 'NFS Server',
'gpuservice.storageType.nfs.server.tips':
'Ensure the NFS server address is reachable from all Kubernetes clusters.',
'gpuservice.storageType.nfs.share': 'Share Path',
'gpuservice.storageType.nfs.share.tips':
'A directory based on the organization and storage names will be automatically created within this share path. If a subdirectory is specified, the generated directory will be created under that subdirectory.',
'gpuservice.storageType.nfs.subDirectory': 'Sub Directory',
'gpuservice.storageType.nfs.subDirectory.tips':
'If empty, a subdirectory named after the persistent volume will be created. If set, a directory with the persistent volume name will be created beneath this subdirectory.',
'gpuservice.storageType.nfs.mountPermissions': 'Mount Permissions',
'gpuservice.storageType.nfs.mountPermissions.tips':
'Inherit the file permissions from the NFS server.',
'gpuservice.storageType.s3.endpoint': 'Endpoint',
'gpuservice.storageType.s3.endpoint.tips':
'Ensure the S3 endpoint is reachable from all Kubernetes clusters.',
'gpuservice.storageType.s3.endpoint.rule': 'Must start with http or https',
'gpuservice.storageType.s3.region': 'Region',
'gpuservice.storageType.s3.bucket': 'Bucket',
'gpuservice.storageType.s3.bucket.tips':
'If empty, a new bucket named after the persistent volume will be created. If set, a subdirectory with the persistent volume name will be created inside this bucket.',
'gpuservice.storageType.s3.bucket.tips1':
'A prefix based on the organization and storage names will be automatically created within this bucket.',
'gpuservice.storageType.s3.bucket.tips2':
'For example, if the organization is named <span class="desc-block">awesome-group</span> and the storage is named <span class="desc-block">storage-1</span>, the resulting prefix will be <span class="desc-block">awesome-group/storage-1</span>.',
'gpuservice.storageType.s3.accessKey': 'Access Key',
'gpuservice.storageType.s3.secretKey': 'Secret Key',
'gpuservice.storageType.s3.insecure': 'Skip TLS/SSL certificate verification',
'gpuservice.storageType.s3.insecure.tips':
'When enabled, the S3 server certificate is not validated. Use this for internal testing or self-signed certificates; enable with caution in production.',
'gpuservice.publicKey': 'SSH Public Key',
'gpuservice.publicKey.add': 'Add SSH Public Key',
'gpuservice.publicKey.edit': 'Edit SSH Public Key',
'gpuservice.publicKey.filter.name': 'Search by name',
'gpuservice.publicKey.label': 'SSH Public Key', 'gpuservice.publicKey.label': 'SSH Public Key',
'gpuservice.instance.ssh.enable': 'Enable SSH Access',
'gpuservice.instance.ssh.assignKey': 'Assign SSH Public Key',
'gpuservice.instance.ssh.addKey': 'Add SSH Public Key',
'gpuservice.publicKey.placeholder': 'gpuservice.publicKey.placeholder':
'Begin with ssh-rsa or ssh-ed25519. One Public Key per line.\n\nView Public Key:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub', 'Begin with ssh-rsa or ssh-ed25519. One Public Key per line.\n\nView Public Key:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub',
'gpuservice.instance': 'GPU Instance', 'gpuservice.instance': 'GPU Instance',
@@ -45,22 +101,49 @@ export default {
'gpuservice.instance.templates': 'Instance Templates', 'gpuservice.instance.templates': 'Instance Templates',
'gpuservice.instance.section.storage': 'Storage', 'gpuservice.instance.section.storage': 'Storage',
'gpuservice.instance.type.required': 'Please select an instance type', 'gpuservice.instance.type.required': 'Please select an instance type',
'gpuservice.instance.type.noAvailable': 'No instance type available',
'gpuservice.instance.gpuCount': 'GPU Count', 'gpuservice.instance.gpuCount': 'GPU Count',
'gpuservice.instance.gpuCount.required': 'Please enter the GPU count', 'gpuservice.instance.gpuCount.required': 'Please enter the GPU count',
'gpuservice.instance.gpuCount.max': 'gpuservice.instance.gpuCount.max':
'The current instance type supports at most {count} GPU(s)', 'Please select at most {count} GPU card(s)',
'gpuservice.instance.gpuCount.min':
'Please select at least {count} GPU card(s)',
'gpuservice.instance.cpuCount.max':
'Please select at most {count} CPU core(s)',
'gpuservice.instance.cpuCount.min':
'Please select at least {count} CPU core(s)',
'gpuservice.instance.gpuCount.noAvailable':
'No available GPU resources, please choose another instance type.',
'gpuservice.instance.gpuCount.zero':
'CPU-only setup for environment preparation.',
'gpuservice.instance.stock': 'Stock', 'gpuservice.instance.stock': 'Stock',
'gpuservice.instance.sliced': 'Sliced', 'gpuservice.instance.sliced': 'Sliced',
'gpuservice.instance.memory': 'Memory', 'gpuservice.instance.memory': 'VRAM',
'gpuservice.instance.ram': 'RAM', 'gpuservice.instance.ram': 'RAM',
'gpuservice.instance.search.type.placeholder': 'gpuservice.instance.disk': 'Disk',
'Search by name, VRAM, memory or vCPU', 'gpuservice.table.count': 'Count',
'gpuservice.instance.disk.system': 'System Disk',
'gpuservice.instance.disk.ephemeral': 'Ephemeral Storage',
'gpuservice.instance.disk.persistent': 'Persistent Storage',
'gpuservice.instance.search.type.placeholder': 'Search by name',
'gpuservice.instance.search.template.placeholder': 'gpuservice.instance.search.template.placeholder':
'Search by template name, image or mount path', 'Search by template name, image or mount path',
'gpuservice.instance.template.image': 'Image', 'gpuservice.instance.template.image': 'Image',
'gpuservice.instance.template.mount': 'Mount', 'gpuservice.instance.template.mount': 'Mount',
'gpuservice.instance.connect': 'Connect', 'gpuservice.instance.connect': 'Connect',
'gpuservice.instance.connect.copySshCommand': 'Copy SSH Command', 'gpuservice.instance.connect.copySshCommand': 'Copy SSH Command',
'gpuservice.instance.event.reason': 'Reason',
'gpuservice.instance.event.message': 'Message',
'gpuservice.instance.event.source': 'Source',
'gpuservice.instance.event.count': 'Count',
'gpuservice.instance.event.lastSeen': 'Last Seen',
'gpuservice.instance.event.recentHourTip':
'Only events from the last hour are shown',
'gpuservice.instance.event.tab.instance': 'Instance Events',
'gpuservice.instance.event.tab.volume': 'Volume Events',
'gpuservice.instance.recreate.confirm.title': 'Confirm recreation',
'gpuservice.instance.recreate.confirm.content':
'The current instance will be deleted first, then recreated with the current configuration.\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
'gpuservice.storage': 'Storage', 'gpuservice.storage': 'Storage',
'gpuservice.storage.add': 'Add Storage', 'gpuservice.storage.add': 'Add Storage',
'gpuservice.storage.edit': 'Edit Storage', 'gpuservice.storage.edit': 'Edit Storage',
@@ -72,11 +155,22 @@ export default {
'gpuservice.storage.capacity': 'Capacity', 'gpuservice.storage.capacity': 'Capacity',
'gpuservice.storage.accessMode': 'Access Mode', 'gpuservice.storage.accessMode': 'Access Mode',
'gpuservice.storage.persistent': 'Persistent', 'gpuservice.storage.persistent': 'Persistent',
'gpuservice.storage.temporary': 'Temporary', 'gpuservice.storage.temporary': 'Ephemeral',
'gpuservice.storage.persistentVolume': 'Persistent Volume', 'gpuservice.storage.persistentVolume': 'Persistent',
'gpuservice.storage.persistentVolume.required': 'gpuservice.storage.temporary.tips':
'Please select a persistent volume', 'Data is cleared when the instance stops.',
'gpuservice.storage.tempCapacity': 'Storage Capacity (GB)', 'gpuservice.storage.persistentVolume.tips':
'Data persists across instance restarts. Persistent volumes remain intact after instance termination and can be shared by multiple instances.',
'gpuservice.storage.persistentVolume.required': 'Please select a storage',
'gpuservice.storage.persistentVolume.capacity': 'Capacity (GB)',
'gpuservice.storage.persistentVolume.capacity.required':
'Please enter capacity',
'gpuservice.storage.persistentVolume.releaseWithInstance':
'Release with instance',
'gpuservice.storage.tempCapacity': 'Capacity (GB)',
'gpuservice.storage.tempCapacity.required': 'gpuservice.storage.tempCapacity.required':
'Please enter the local temporary storage capacity' 'Please enter the temporary storage capacity',
'gpuservice.form.rule.name':
"Lowercase letters, numbers, and '-'. Start and end with a letter or number, no consecutive '-', max 63 characters.",
'gpuservice.form.storage.select': 'Select Storage'
}; };
+13 -9
View File
@@ -8,7 +8,7 @@ export default {
'menu.playground.text2images': 'Image', 'menu.playground.text2images': 'Image',
'menu.playground.video': 'Video', 'menu.playground.video': 'Video',
'menu.compare': 'Compare', 'menu.compare': 'Compare',
'menu.models': 'Models', 'menu.models': 'Model Service',
'menu.models.modelList': 'Deploy & Manage', 'menu.models.modelList': 'Deploy & Manage',
'menu.models.modelCatalog': 'Catalog', 'menu.models.modelCatalog': 'Catalog',
'menu.models.catalog': 'Model Catalog', 'menu.models.catalog': 'Model Catalog',
@@ -27,23 +27,27 @@ export default {
'menu.users': 'Users', 'menu.users': 'Users',
'menu.resources.workers': 'Workers', 'menu.resources.workers': 'Workers',
'menu.resources.gpus': 'GPUs', 'menu.resources.gpus': 'GPUs',
'menu.resources.modelfiles': 'Model Files', 'menu.models.modelfiles': 'Model Files',
'menu.accessControl': 'Access Control', 'menu.accessControl': 'Access Control',
'menu.accessControl.apikeys': 'API Keys', 'menu.accessControl.apikeys': 'API Keys',
'menu.accessControl.users': 'Users', 'menu.accessControl.users': 'Users',
'menu.profile': 'Profile', 'menu.accessControl.organizations': 'Organizations',
'menu.profile': 'Preferences',
'menu.login': 'Login', 'menu.login': 'Login',
'menu.usage': 'Usage', 'menu.usage': 'Usage',
'menu.usage.usage': 'Usage',
'menu.billingAndUsage': 'Usage & Billing',
'menu.billingAndUsage.usage': 'Usage',
'menu.404': '404', 'menu.404': '404',
'menu.clusterManagement': 'Cluster Management', 'menu.resources.clusters': 'Clusters',
'menu.clusterManagement.clusters': 'Clusters', 'menu.resources.credentials': 'Cloud Credentials',
'menu.clusterManagement.credentials': 'Cloud Credentials', 'menu.resources.clusterDetail': 'Cluster Detail',
'menu.clusterManagement.clusterDetail': 'Cluster Detail', 'menu.resources.clusterCreate': 'Create Cluster',
'menu.clusterManagement.clusterCreate': 'Create Cluster', 'menu.models.backendsList': 'Inference Backends',
'menu.resources.backendsList': 'Inference Backends',
'menu.gpuService': 'GPU Service', 'menu.gpuService': 'GPU Service',
'menu.gpuService.instances': 'GPU Instances', 'menu.gpuService.instances': 'GPU Instances',
'menu.gpuService.templates': 'Instance Templates', 'menu.gpuService.templates': 'Instance Templates',
'menu.gpuService.storage': 'Storage', 'menu.gpuService.storage': 'Storage',
'menu.gpuService.storageTypes': 'Storage Types',
'menu.gpuService.publicKeys': 'SSH Public Keys' 'menu.gpuService.publicKeys': 'SSH Public Keys'
}; };
+8 -2
View File
@@ -62,7 +62,7 @@ export default {
'models.form.backend': 'Backend', 'models.form.backend': 'Backend',
'models.form.backend_parameters': 'Backend Parameters', 'models.form.backend_parameters': 'Backend Parameters',
'models.instance.params.configured': 'User Configured', 'models.instance.params.configured': 'User Configured',
'models.instance.params.autoInjected': 'Auto-injected', 'models.instance.params.autoInjected': 'Auto-injected Parameters',
'models.search.gguf.tips': 'models.search.gguf.tips':
'GGUF models use llama-box(supports Linux, macOS and Windows).', 'GGUF models use llama-box(supports Linux, macOS and Windows).',
'models.search.vllm.tips': 'models.search.vllm.tips':
@@ -290,5 +290,11 @@ export default {
'models.instance.previousRun': 'Previous Run', 'models.instance.previousRun': 'Previous Run',
'models.instance.startHistory': 'Run History', 'models.instance.startHistory': 'Run History',
'models.instance.startHistory.tips': 'models.instance.startHistory.tips':
'Shows logs from the run before the last error-triggered restart.' 'Shows logs from the run before the last error-triggered restart.',
'models.form.lora.label': 'LoRA Adapters',
'models.form.lora.add': 'Add LoRA Adapter',
'models.form.lora.select': 'Select LoRA',
'models.form.lora.name': 'LoRA name',
'models.form.lora.rule.empty': 'Input cannot be empty',
'models.form.lora.rule.duplicate': 'LoRA name cannot be duplicated'
}; };
+12 -1
View File
@@ -36,9 +36,12 @@ export default {
'noresult.catalog.nofound': 'No matching models found.', 'noresult.catalog.nofound': 'No matching models found.',
'noresult.resources.cluster': 'noresult.resources.cluster':
'No clusters available. Add a cluster to get started.', 'No clusters available. Add a cluster to get started.',
'noresult.resources.k8sCluster':
'No clusters available. Add a Kubernetes cluster to get started.',
'noresult.resources.worker': 'noresult.resources.worker':
'No workers available. Add a worker to get started.', 'No workers available. Add a worker to get started.',
'noresult.resources.gotocluster': 'Create Your First Cluster', 'noresult.resources.gotocluster': 'Create Your First Cluster',
'noresult.resources.addk8scluster': 'Add a Kubernetes Cluster',
'noresult.resources.gotoworker': 'Add Worker', 'noresult.resources.gotoworker': 'Add Worker',
'noresult.benchmark.title': 'No Benchmarks', 'noresult.benchmark.title': 'No Benchmarks',
'noresult.benchmark.subTitle': 'No benchmarks have been added yet.', 'noresult.benchmark.subTitle': 'No benchmarks have been added yet.',
@@ -64,5 +67,13 @@ export default {
'noresult.gpuservice.instance.nofound': 'No matching GPU instances found.', 'noresult.gpuservice.instance.nofound': 'No matching GPU instances found.',
'noresult.gpuservice.storage.title': 'No Storage', 'noresult.gpuservice.storage.title': 'No Storage',
'noresult.gpuservice.storage.subTitle': 'No storage has been added yet.', 'noresult.gpuservice.storage.subTitle': 'No storage has been added yet.',
'noresult.gpuservice.storage.nofound': 'No matching storage found.' 'noresult.gpuservice.storage.nofound': 'No matching storage found.',
'noresult.gpuservice.storageType.title': 'No Storage Types',
'noresult.gpuservice.storageType.subTitle':
'No storage types have been added yet.',
'noresult.gpuservice.storageType.nofound': 'No matching storage types found.',
'noresult.gpuservice.sshkey.title': 'No SSH Public Keys',
'noresult.gpuservice.sshkey.subTitle':
'No SSH public keys have been added yet.',
'noresult.gpuservice.sshkey.nofound': 'No matching SSH public keys found.'
}; };
+15
View File
@@ -0,0 +1,15 @@
export default {
'organizations.upsell.title': 'Organizations are an Enterprise feature',
'organizations.upsell.subtitle':
'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to GPUStack Enterprise to manage organizations.',
'organizations.upsell.featuresTitle': 'What you get in Enterprise',
'organizations.upsell.feature.orgs':
'Create organizations to group users and isolate workloads',
'organizations.upsell.feature.members':
'Manage members and roles per organization',
'organizations.upsell.feature.quotas':
'Set resource and token quotas per organization',
'organizations.upsell.feature.isolation':
'Scope API keys, model deployments, and resources per organization',
'organizations.upsell.cta': 'Learn about Enterprise'
};
+1
View File
@@ -98,6 +98,7 @@ export default {
'resources.worker.download.privatekey': 'Download Private Key', 'resources.worker.download.privatekey': 'Download Private Key',
'resources.modelfiles.form.exsting': 'Downloaded', 'resources.modelfiles.form.exsting': 'Downloaded',
'resources.modelfiles.form.added': 'Added', 'resources.modelfiles.form.added': 'Added',
'resources.modelfiles.form.isLora': 'Is LoRA',
'resources.worker.maintenance.title': 'System Maintenance', 'resources.worker.maintenance.title': 'System Maintenance',
'resources.worker.maintenance.enable': 'Enter Maintenance Mode', 'resources.worker.maintenance.enable': 'Enter Maintenance Mode',
'resources.worker.maintenance.disable': 'Exit Maintenance Mode', 'resources.worker.maintenance.disable': 'Exit Maintenance Mode',
+71 -1
View File
@@ -17,9 +17,15 @@ export default {
'usage.table.user.apiKeysUsed': 'API Keys Used', 'usage.table.user.apiKeysUsed': 'API Keys Used',
'usage.table.lastActive': 'Last Active', 'usage.table.lastActive': 'Last Active',
'usage.filter.granularity': 'Granularity', 'usage.filter.granularity': 'Granularity',
'usage.filter.granularity.hour': 'Hour',
'usage.filter.granularity.day': 'Day', 'usage.filter.granularity.day': 'Day',
'usage.filter.granularity.week': 'Week', 'usage.filter.granularity.week': 'Week',
'usage.filter.granularity.month': 'Month', 'usage.filter.granularity.month': 'Month',
'usage.tabs.summary': 'Summary',
'usage.tabs.tokens': 'Tokens',
'usage.tabs.gpuInstances': 'GPU Instances',
'usage.tabs.storage': 'Storage',
'usage.tabs.resourceEvents': 'Resource Events',
'usage.tabs.models': 'Models', 'usage.tabs.models': 'Models',
'usage.tabs.apikeys': 'API Keys', 'usage.tabs.apikeys': 'API Keys',
'usage.tabs.users': 'User', 'usage.tabs.users': 'User',
@@ -32,5 +38,69 @@ export default {
'usage.chart.cached': 'Cached', 'usage.chart.cached': 'Cached',
'usage.chart.uncached': 'Uncached', 'usage.chart.uncached': 'Uncached',
'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)', 'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)',
'usage.table.inputTokensCached': 'Input Tokens Cached' 'usage.table.inputTokensCached': 'Input Tokens Cached',
// --- Resource usage: shared metrics & units ---
'usage.metric.tokens': 'Tokens',
'usage.metric.input': 'Input',
'usage.metric.output': 'Output',
'usage.metric.gpuHours': 'GPU Hours',
'usage.metric.instanceHours': 'Instance Hours',
'usage.metric.gbDays': 'GB-Days',
'usage.metric.gbHours': 'GB-Hours',
'usage.metric.activeUsers': 'Active Users',
'usage.metric.activeInstances': 'Active Instances',
'usage.metric.activeStorage': 'Active Storage',
'usage.metric.activeVolumes': 'Active Volumes',
'usage.metric.storageTypes': 'Storage Types',
'usage.metric.gpuHours.tip':
'Instance running time weighted by GPU count: an instance with N GPUs running for H hours counts as N × H GPU-hours. Equal to Instance Hours when every instance uses a single GPU.',
'usage.metric.instanceHours.tip':
'Total running time summed across all instances, regardless of how many GPUs each uses. One instance running for 2 hours = 2 instance-hours.',
'usage.metric.gbDays.tip':
'Storage capacity integrated over time, in GB × days: 10 GB kept for 5 days = 50 GB-days. (= GB-Hours ÷ 24)',
'usage.metric.gbHours.tip':
'Storage capacity integrated over time, in GB × hours: 10 GB kept for 5 hours = 50 GB-hours.',
// --- Resource usage: common table / labels ---
'usage.common.noData': 'No data',
'usage.common.unknown': 'unknown',
'usage.table.date': 'Date',
'usage.table.name': 'Name',
'usage.table.user': 'User',
'usage.table.users': 'Users',
'usage.table.type': 'Type',
'usage.table.instance': 'Instance',
'usage.table.instanceType': 'Instance Type',
'usage.table.instanceTypes': 'Instance Types',
'usage.table.instances': 'Instances',
'usage.table.capacity': 'Capacity',
'usage.export.tableNamed': 'Export Table Data — {name}',
// --- Summary tab ---
'usage.summary.compute': 'Compute',
'usage.summary.tokensOverTime': 'Tokens over time',
'usage.summary.gpuHoursOverTime': 'GPU Hours over time',
'usage.summary.gbDaysOverTime': 'GB-Days over time',
// --- GPU Instances / Storage filters ---
'usage.filter.instance': 'Filter by instance',
'usage.filter.storage': 'Filter by storage',
// --- Resource events ---
'usage.events.resourceType': 'Resource type',
'usage.events.eventType': 'Event type',
'usage.events.col.time': 'Time',
'usage.events.col.resource': 'Resource',
'usage.events.col.event': 'Event',
'usage.events.col.message': 'Message',
'usage.events.resource.gpuInstance': 'GPU Instance',
'usage.events.resource.cpuInstance': 'CPU Instance',
'usage.events.type.created': 'Created',
'usage.events.type.deleted': 'Deleted',
'usage.events.type.started': 'Started',
'usage.events.type.stopped': 'Stopped',
'usage.events.type.updated': 'Updated',
'usage.events.type.attached': 'Attached',
'usage.events.type.detached': 'Detached'
}; };
+1 -1
View File
@@ -4,7 +4,7 @@ export default {
'vendor.hygon': 'Hygon', 'vendor.hygon': 'Hygon',
'vendor.moorthreads': 'Moore Threads', 'vendor.moorthreads': 'Moore Threads',
'vendor.iluvatar': 'Iluvatar', 'vendor.iluvatar': 'Iluvatar',
'vendor.metax': 'Metax', 'vendor.metax': 'MetaX',
'vendor.cambricon': 'Cambricon', 'vendor.cambricon': 'Cambricon',
'vendor.thead': 'T-Head PPU' 'vendor.thead': 'T-Head PPU'
}; };
+3 -1
View File
@@ -23,7 +23,9 @@ export default {
'apikeys.accessScope.inference': 'Inference APIs', 'apikeys.accessScope.inference': 'Inference APIs',
'apikeys.access.permissions': 'Access Permissions', 'apikeys.access.permissions': 'Access Permissions',
'apikeys.type.auto': 'Auto-generated', 'apikeys.type.auto': 'Auto-generated',
'apikeys.type.custom': 'Custom' 'apikeys.type.custom': 'Custom',
'apikeys.button.ipConfig': 'IP Access Control',
'quotaLimits.button.title': 'Quota Limit'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
+7
View File
@@ -23,6 +23,13 @@ export default {
'backend.form.defaultExecuteCommand': 'Default Execution Command', 'backend.form.defaultExecuteCommand': 'Default Execution Command',
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' and '{{'model_name'}}' are placeholders that will be substituted with the actual values during deployment.`, 'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' and '{{'model_name'}}' are placeholders that will be substituted with the actual values during deployment.`,
'backend.form.defaultBackendParameters': 'Default Backend Parameters', 'backend.form.defaultBackendParameters': 'Default Backend Parameters',
'backend.form.parameterFormat': 'パラメータ形式',
'backend.form.parameterFormat.default': 'バックエンドのデフォルト',
'backend.form.parameterFormat.space': 'スペース区切り (--key value)',
'backend.form.parameterFormat.equal': 'イコール連結 (--key=value)',
'backend.form.commonParameters': 'よく使うパラメータ',
'backend.form.commonParameters.tips':
'モデルのデプロイ時にバックエンドパラメータ入力欄の候補として表示されます。',
'backend.form.versionConfig': 'Versions Config', 'backend.form.versionConfig': 'Versions Config',
'backend.form.addParameter': 'Add Parameter', 'backend.form.addParameter': 'Add Parameter',
'backend.form.noVersion': 'No versions added', 'backend.form.noVersion': 'No versions added',
+37 -2
View File
@@ -39,9 +39,11 @@ export default {
'clusters.workerpool.batchSize.desc': 'clusters.workerpool.batchSize.desc':
'Number of workers created simultaneously in the Worker pool', 'Number of workers created simultaneously in the Worker pool',
'clusters.create.addworker.tips': 'clusters.create.addworker.tips':
'Please make sure the <a href={link} target="_blank">prerequisites</a> for {label} are met before executing the following command.', 'Please make sure the <a href={link} target="_blank">prerequisites</a> are met before executing the following command.',
'clusters.create.addCommand.tips': 'clusters.create.addCommand.tips':
'On the Worker that needs to be added, run the following command to join it to the cluster.', 'On the Worker that needs to be added, run the following command to join it to the cluster.',
'clusters.create.addCommand.k8s.tips':
'登録する Kubernetes クラスターで以下のコマンドを実行し、Kubernetes リソースを作成してクラスターを登録します。',
'cluster.create.checkEnv.tips': 'cluster.create.checkEnv.tips':
'Use the following command to check if the environment is ready.', 'Use the following command to check if the environment is ready.',
'clusters.create.register.tips': 'clusters.create.register.tips':
@@ -67,6 +69,7 @@ export default {
'clusters.addworker.selectCluster.tips': 'clusters.addworker.selectCluster.tips':
'For <span class="bold-text">non-Docker</span> clusters, please register clusters or manage worker pools from the Clusters page.', 'For <span class="bold-text">non-Docker</span> clusters, please register clusters or manage worker pools from the Clusters page.',
'clusters.addworker.selectGPU': 'Select GPU Vendor', 'clusters.addworker.selectGPU': 'Select GPU Vendor',
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
'clusters.addworker.checkEnv': 'Check Environment', 'clusters.addworker.checkEnv': 'Check Environment',
'clusters.addworker.specifyArgs': 'Specify Arguments', 'clusters.addworker.specifyArgs': 'Specify Arguments',
'clusters.addworker.runCommand': 'Run Command', 'clusters.addworker.runCommand': 'Run Command',
@@ -107,6 +110,10 @@ export default {
'{count} new workers have been added to the cluster.', '{count} new workers have been added to the cluster.',
'clusters.create.serverUrl': 'GPUStack Server URL', 'clusters.create.serverUrl': 'GPUStack Server URL',
'clusters.create.workerConfig': 'Worker Configuration', 'clusters.create.workerConfig': 'Worker Configuration',
'clusters.edit.k8sOptions.changed.tip':
'Kubernetes オプションを変更しました。変更を有効にするには、対象クラスターで登録コマンドを再実行してください。',
'clusters.edit.workerConfig.tip':
'ワーカー設定の変更は、対象のワーカーを再起動した後に有効になります。',
'clusters.addworker.containerName': 'Worker Container Name', 'clusters.addworker.containerName': 'Worker Container Name',
'clusters.addworker.containerName.tips': 'clusters.addworker.containerName.tips':
'Specify a name for the worker container.', 'Specify a name for the worker container.',
@@ -158,7 +165,35 @@ export default {
'clusters.volume.pvc.readOnly': 'Read Only', 'clusters.volume.pvc.readOnly': 'Read Only',
'clusters.volume.configMap.name': 'ConfigMap Name', 'clusters.volume.configMap.name': 'ConfigMap Name',
'clusters.volume.configMap.optional': 'Optional', 'clusters.volume.configMap.optional': 'Optional',
'clusters.volume.add': 'Add Volume Mount' 'clusters.volume.add': 'Add Volume Mount',
'clusters.systemDefaultContainerRegistry.title': 'Default Container Registry',
'clusters.systemDefaultContainerRegistry.tip':
'Default registry used to resolve GPUStack images for this cluster. Falls back to the server default when unset.',
'clusters.k8sOptions.title': 'Kubernetes Deployment Options',
'clusters.imageCredentials.title': 'Image Credentials',
'clusters.imageCredentials.add': 'Add Credential',
'clusters.imageCredentials.registry': 'Registry',
'clusters.imageCredentials.username': 'Username',
'clusters.imageCredentials.password': 'Password',
'clusters.nodeSelector.title': 'Node Selector',
'clusters.nodeSelector.tip':
'Pod nodeSelector applied to every worker DaemonSet — only nodes whose labels match are eligible to run the worker.',
'clusters.operatorImage.title': 'Operator Image',
'clusters.operatorImage.tip':
'Override for the GPUStack Operator container image. Leave empty to use the server default.',
'clusters.namespace.title': 'Namespace',
'clusters.namespace.tip':
'Kubernetes namespace the clusters manifests render into. Leave empty to use gpustack-system.',
'clusters.clusterType.title': 'Cluster Type',
'clusters.modelService.title': 'Model Service',
'clusters.modelService.tip':
'For LLM inference and API serving — e.g. exposing model APIs and token-based services.',
'clusters.gpuInstances.title': 'GPU Service',
'clusters.gpuInstances.tip':
'For on-demand GPU compute — e.g. interactive development, training jobs, or custom environments.',
'clusters.gpuInstances.staticAddress': 'GPU Service Static Access Address',
'clusters.gpuInstances.staticAddress.tip':
'Static address the operator uses to access GPU instances in this cluster (e.g. a LoadBalancer VIP). Optional.'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
+11 -2
View File
@@ -46,22 +46,28 @@ export default {
'common.button.enabled': '有効', 'common.button.enabled': '有効',
'common.button.disabled': '無効', 'common.button.disabled': '無効',
'common.button.upgrade': 'アップグレード', 'common.button.upgrade': 'アップグレード',
'common.enterprise.feature': 'Available in GPUStack Enterprise',
'common.input.holder': '入力してください', 'common.input.holder': '入力してください',
'common.validate.value': '{name} の値は必須です', 'common.validate.value': '{name} の値は必須です',
'common.button.edit': '編集', 'common.button.edit': '編集',
'common.button.authorize': 'ロール認可', 'common.button.authorize': 'ロール認可',
'common.button.confirm': '確認', 'common.button.confirm': '確認',
'common.button.viewlog': 'ログを表示', 'common.button.viewlog': 'ログを表示',
'common.button.viewevent': 'イベントを表示',
'common.button.recreate': '再作成',
'common.table.operation': '操作', 'common.table.operation': '操作',
'common.table.creator': '作成者',
'common.table.createTime': '作成日時', 'common.table.createTime': '作成日時',
'common.table.updateTime': '更新日時', 'common.table.updateTime': '更新日時',
'common.table.description': '説明', 'common.table.description': '説明',
'common.table.displayName': '表示名',
'common.table.name': '名前', 'common.table.name': '名前',
'common.table.status': 'ステータス', 'common.table.status': 'ステータス',
'common.table.name.list': '{type} 名称', 'common.table.name.list': '{type} 名称',
'common.search.name.placeholder': '名前でフィルタ', 'common.search.name.placeholder': '名前でフィルタ',
'common.search.id.placeholder': 'IDでフィルタ', 'common.search.id.placeholder': 'IDでフィルタ',
'common.filter.byId': 'IDでフィルタ', 'common.filter.byId': 'IDでフィルタ',
'common.filter.byCreator': '作成者でフィルタ',
'common.table.type': 'タイプ', 'common.table.type': 'タイプ',
'common.table.default': 'デフォルト値', 'common.table.default': 'デフォルト値',
'common.copy.success': 'コピー成功!', 'common.copy.success': 'コピー成功!',
@@ -223,7 +229,6 @@ export default {
'common.text.latest': '最新', 'common.text.latest': '最新',
'common.text.new': '新規', 'common.text.new': '新規',
'common.text.changelog': 'リリースノート', 'common.text.changelog': 'リリースノート',
'common.button.recreate': '再作成',
'common.button.delrecreate': '削除(再作成)', 'common.button.delrecreate': '削除(再作成)',
'common.options.all': 'すべて', 'common.options.all': 'すべて',
'common.options.none': 'なし', 'common.options.none': 'なし',
@@ -281,7 +286,11 @@ export default {
'common.file.format.limit': 'Invalid file format. Allowed: {formats}.', 'common.file.format.limit': 'Invalid file format. Allowed: {formats}.',
'common.image.limit.width': 'Image width must be {width}.', 'common.image.limit.width': 'Image width must be {width}.',
'common.image.limit.height': 'Image height must be {height}.', 'common.image.limit.height': 'Image height must be {height}.',
'common.max': '最大 {count}' 'common.remaining': '残り {count}',
'common.max': '最大 {count}',
'common.max.count': '{label} 数',
'common.validate.group': 'Please complete the {group} configuration',
'common.preferences': 'Preferences'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
+5 -14
View File
@@ -1,31 +1,22 @@
export default { export default {
'dashboard.title': 'ダッシュボード',
'dashboard.workers': 'ワーカー', 'dashboard.workers': 'ワーカー',
'dashboard.models': 'モデル', 'dashboard.deployments': 'Deployments',
'dashboard.totalgpus': 'GPU数', 'dashboard.totalgpus': 'GPU数',
'dashboard.allocategpus': '割り当て済みGPU',
'dashboard.instances': 'インスタンス',
'dashboard.systemload': 'システム負荷', 'dashboard.systemload': 'システム負荷',
'dashboard.memory': 'メモリ', 'dashboard.memory': 'メモリ',
'dashboard.disk': 'ストレージ',
'dashboard.vram': 'VRAM', 'dashboard.vram': 'VRAM',
'dashboard.cpuutilization': '平均CPU使用率', 'dashboard.cpuutilization': '平均CPU使用率',
'dashboard.memoryutilization': '平均メモリ使用率', 'dashboard.memoryutilization': '平均メモリ使用率',
'dashboard.diskutilization': 'ストレージ使用率',
'dashboard.vramutilization': '平均VRAM使用率', 'dashboard.vramutilization': '平均VRAM使用率',
'dashboard.gpuutilization': '平均GPU使用率', 'dashboard.gpuutilization': '平均GPU使用率',
'dashboard.usage': '使用状況', 'dashboard.usage': '使用状況',
'dashboard.apirequest': 'APIリクエスト', 'dashboard.usage.title': '過去 {days} 日間の使用状況',
'dashboard.usage.others': 'その他',
'dashboard.tokens': 'トークン使用量', 'dashboard.tokens': 'トークン使用量',
'dashboard.topusers': 'トップユーザー', 'dashboard.topusers': 'トップユーザー',
'dashboard.activeModels': 'アクティブなモデル', 'dashboard.activeDeployments': 'Active Deployments',
'dashboard.activeUsers': 'アクティブユーザー', 'dashboard.usageByModel': 'モデル別使用量',
'dashboard.tokenUsageByModel': 'モデル別トークン使用量',
'dashboard.apiRequestsByModel': 'モデル別APIリクエスト',
'dashboard.topTokenUsageByUser': 'ユーザー別トークン使用量トップ10', 'dashboard.topTokenUsageByUser': 'ユーザー別トークン使用量トップ10',
'dashboard.topTokenUsageByApiKey': 'APIキー別トークン使用量トップ10',
'dashboard.runninginstances': '稼働中のインスタンス',
'dashboard.activeModels.name': 'モデル名',
'dashboard.allocatevram': '割り当て済みVRAM / メモリ', 'dashboard.allocatevram': '割り当て済みVRAM / メモリ',
'dashboard.usage.selectuser': 'Select users', 'dashboard.usage.selectuser': 'Select users',
'dashboard.usage.selectmodel': 'Select models', 'dashboard.usage.selectmodel': 'Select models',
+104 -8
View File
@@ -13,11 +13,25 @@ export default {
'gpuservice.template.command.placeholder': 'gpuservice.template.command.placeholder':
'引数はスペースで区切り、スペースを含む引数は引用符で囲んでください。例:/bin/bash -c "echo hello world"', '引数はスペースで区切り、スペースを含む引数は引用符で囲んでください。例:/bin/bash -c "echo hello world"',
'gpuservice.template.mountPath': 'マウントパス', 'gpuservice.template.mountPath': 'マウントパス',
'gpuservice.template.mountPath.tips':
'このテンプレートからインスタンスを作成する際に、ストレージボリュームがデフォルトでマウントされるパスです。インスタンスの実行中に保持する必要があるデータの永続化に使用できます。',
'gpuservice.template.containerDisk': 'コンテナディスク (GB)', 'gpuservice.template.containerDisk': 'コンテナディスク (GB)',
'gpuservice.template.containerDisk.tips':
'コンテナシステムディスクのサイズです。',
'gpuservice.template.memory': 'メモリ (GB)', 'gpuservice.template.memory': 'メモリ (GB)',
'gpuservice.instance.containerDisk.remaining':
'コンテナディスク (最大 {count} GB)',
'gpuservice.instance.memory.remaining': 'メモリ (最大 {count} GB)',
'gpuservice.template.displayName': '表示名',
'gpuservice.template.displayName.max':
'表示名は 63 文字以内で入力してください。',
'gpuservice.template.ports': 'ポート', 'gpuservice.template.ports': 'ポート',
'gpuservice.template.ports.add': 'ポートを追加', 'gpuservice.template.ports.add': 'ポートを追加',
'gpuservice.template.ports.invalid': 'ポート設定を完成させてください。', 'gpuservice.template.ports.invalid': 'ポート設定を完成させてください。',
'gpuservice.template.ports.name': '名前',
'gpuservice.template.ports.name.max':
'ポート名は 16 文字以内で入力してください。',
'gpuservice.template.ports.name.duplicate': 'ポート名は重複できません。',
'gpuservice.template.env': '環境変数', 'gpuservice.template.env': '環境変数',
'gpuservice.template.env.add': '環境変数を追加', 'gpuservice.template.env.add': '環境変数を追加',
'gpuservice.template.env.invalid': '環境変数を完成させてください。', 'gpuservice.template.env.invalid': '環境変数を完成させてください。',
@@ -27,7 +41,50 @@ export default {
'gpuservice.template.card.mount': 'マウント', 'gpuservice.template.card.mount': 'マウント',
'gpuservice.template.card.resources': 'リソース', 'gpuservice.template.card.resources': 'リソース',
'gpuservice.template.card.ports': 'ポート', 'gpuservice.template.card.ports': 'ポート',
'gpuservice.storageType': 'ストレージタイプ',
'gpuservice.storageType.add': 'ストレージタイプを追加',
'gpuservice.storageType.edit': 'ストレージタイプを編集',
'gpuservice.storageType.filter.name': '名前で検索',
'gpuservice.storageType.kind': '種別',
'gpuservice.storageType.mountOptions': 'マウントオプション',
'gpuservice.storageType.nfs.server': 'NFS サーバー',
'gpuservice.storageType.nfs.server.tips':
'すべての Kubernetes クラスターから NFS サーバーアドレスにアクセスできることを確認してください。',
'gpuservice.storageType.nfs.share': '共有パス',
'gpuservice.storageType.nfs.share.tips':
'この共有パス配下に、組織名とストレージ名に基づくディレクトリが自動的に作成されます。サブディレクトリが指定されている場合、生成されたディレクトリはそのサブディレクトリ配下に作成されます。',
'gpuservice.storageType.nfs.subDirectory': 'サブディレクトリ',
'gpuservice.storageType.nfs.subDirectory.tips':
'空の場合、永続ボリューム名のサブディレクトリが作成されます。設定されている場合、このサブディレクトリ配下に永続ボリューム名のディレクトリが作成されます。',
'gpuservice.storageType.nfs.mountPermissions': 'マウント権限',
'gpuservice.storageType.nfs.mountPermissions.tips':
'NFS サーバー上のファイル権限を継承します。',
'gpuservice.storageType.s3.endpoint': 'エンドポイント',
'gpuservice.storageType.s3.endpoint.tips':
'すべての Kubernetes クラスターから S3 エンドポイントにアクセスできることを確認してください。',
'gpuservice.storageType.s3.endpoint.rule':
'http または https で始まる必要があります',
'gpuservice.storageType.s3.region': 'リージョン',
'gpuservice.storageType.s3.bucket': 'バケット',
'gpuservice.storageType.s3.bucket.tips':
'空の場合、永続ボリューム名で新しいバケットが作成されます。設定されている場合、このバケット配下に永続ボリューム名のサブディレクトリが作成されます。',
'gpuservice.storageType.s3.bucket.tips1':
'このバケット内に、組織名とストレージ名に基づくプレフィックスディレクトリが自動的に作成されます。',
'gpuservice.storageType.s3.bucket.tips2':
'例えば、組織名が <span class="desc-block">awesome-group</span>、ストレージ名が <span class="desc-block">storage-1</span> の場合、生成されるプレフィックスは <span class="desc-block">awesome-group/storage-1</span> になります。',
'gpuservice.storageType.s3.accessKey': 'アクセスキー',
'gpuservice.storageType.s3.secretKey': 'シークレットキー',
'gpuservice.storageType.s3.insecure': 'TLS/SSL 証明書の検証をスキップ',
'gpuservice.storageType.s3.insecure.tips':
'有効にすると S3 サーバーの証明書検証を無視します。社内テストや自己署名証明書の利用時に適しており、本番環境では慎重に有効化してください。',
'gpuservice.publicKey': 'SSH 公開鍵',
'gpuservice.publicKey.add': 'SSH 公開鍵を追加',
'gpuservice.publicKey.edit': 'SSH 公開鍵を編集',
'gpuservice.publicKey.filter.name': '名前で検索',
'gpuservice.publicKey.label': 'SSH 公開鍵', 'gpuservice.publicKey.label': 'SSH 公開鍵',
'gpuservice.instance.ssh.enable': 'SSH アクセスを有効化',
'gpuservice.instance.ssh.assignKey': 'SSH 公開鍵を割り当て',
'gpuservice.instance.ssh.addKey': 'SSH 公開鍵を追加',
'gpuservice.publicKey.placeholder': 'gpuservice.publicKey.placeholder':
'ssh-rsa または ssh-ed25519 で始まり、各公開鍵は1行ずつ記述します\n\n公開鍵を確認:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub', 'ssh-rsa または ssh-ed25519 で始まり、各公開鍵は1行ずつ記述します\n\n公開鍵を確認:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub',
'gpuservice.instance': 'GPU インスタンス', 'gpuservice.instance': 'GPU インスタンス',
@@ -43,22 +100,49 @@ export default {
'gpuservice.instance.templates': 'インスタンステンプレート', 'gpuservice.instance.templates': 'インスタンステンプレート',
'gpuservice.instance.section.storage': 'ストレージボリューム', 'gpuservice.instance.section.storage': 'ストレージボリューム',
'gpuservice.instance.type.required': 'インスタンスタイプを選択してください', 'gpuservice.instance.type.required': 'インスタンスタイプを選択してください',
'gpuservice.instance.type.noAvailable':
'利用可能なインスタンスタイプがありません',
'gpuservice.instance.gpuCount': 'GPU 数', 'gpuservice.instance.gpuCount': 'GPU 数',
'gpuservice.instance.gpuCount.required': 'GPU 数を入力してください', 'gpuservice.instance.gpuCount.required': 'GPU 数を入力してください',
'gpuservice.instance.gpuCount.max': 'gpuservice.instance.gpuCount.max':
'現在のインスタンスタイプは最大 {count} の GPU をサポートします', '最大 {count} の GPU カードを選択してください',
'gpuservice.instance.gpuCount.min':
'少なくとも {count} 枚の GPU カードを選択してください',
'gpuservice.instance.cpuCount.max':
'最大 {count} 個の CPU コアを選択してください',
'gpuservice.instance.cpuCount.min':
'少なくとも {count} 個の CPU コアを選択してください',
'gpuservice.instance.gpuCount.noAvailable':
'利用可能な GPU リソースがありません。別のインスタンスタイプを選択してください。',
'gpuservice.instance.gpuCount.zero': 'CPU のみを使用し、環境準備用です。',
'gpuservice.instance.stock': '在庫', 'gpuservice.instance.stock': '在庫',
'gpuservice.instance.sliced': '分割', 'gpuservice.instance.sliced': '分割',
'gpuservice.instance.memory': 'Memory', 'gpuservice.instance.memory': 'VRAM',
'gpuservice.instance.ram': 'RAM', 'gpuservice.instance.ram': 'RAM',
'gpuservice.instance.search.type.placeholder': 'gpuservice.instance.disk': 'ディスク',
'名前、VRAM、メモリまたは vCPU で検索', 'gpuservice.table.count': '数量',
'gpuservice.instance.disk.system': 'システムディスク',
'gpuservice.instance.disk.ephemeral': '一時ストレージ',
'gpuservice.instance.disk.persistent': '永続ストレージ',
'gpuservice.instance.search.type.placeholder': '名前で検索',
'gpuservice.instance.search.template.placeholder': 'gpuservice.instance.search.template.placeholder':
'テンプレート名、イメージまたはマウントパスで検索', 'テンプレート名、イメージまたはマウントパスで検索',
'gpuservice.instance.template.image': 'イメージ', 'gpuservice.instance.template.image': 'イメージ',
'gpuservice.instance.template.mount': 'マウント', 'gpuservice.instance.template.mount': 'マウント',
'gpuservice.instance.connect': '接続', 'gpuservice.instance.connect': '接続',
'gpuservice.instance.connect.copySshCommand': 'SSH コマンドをコピー', 'gpuservice.instance.connect.copySshCommand': 'SSH コマンドをコピー',
'gpuservice.instance.event.reason': '理由',
'gpuservice.instance.event.message': 'メッセージ',
'gpuservice.instance.event.source': 'ソース',
'gpuservice.instance.event.count': '回数',
'gpuservice.instance.event.lastSeen': '最終発生',
'gpuservice.instance.event.recentHourTip':
'直近 1 時間のイベントのみ表示されます',
'gpuservice.instance.event.tab.instance': 'インスタンスイベント',
'gpuservice.instance.event.tab.volume': 'ボリュームイベント',
'gpuservice.instance.recreate.confirm.title': '再作成を確認しますか',
'gpuservice.instance.recreate.confirm.content':
'現在のインスタンスを削除した後、現在の構成で再作成します。\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
'gpuservice.storage': 'ストレージ', 'gpuservice.storage': 'ストレージ',
'gpuservice.storage.add': 'ストレージを追加', 'gpuservice.storage.add': 'ストレージを追加',
'gpuservice.storage.edit': 'ストレージを編集', 'gpuservice.storage.edit': 'ストレージを編集',
@@ -71,10 +155,22 @@ export default {
'gpuservice.storage.accessMode': 'アクセスモード', 'gpuservice.storage.accessMode': 'アクセスモード',
'gpuservice.storage.persistent': '永続', 'gpuservice.storage.persistent': '永続',
'gpuservice.storage.temporary': '一時', 'gpuservice.storage.temporary': '一時',
'gpuservice.storage.persistentVolume': '永続ボリューム', 'gpuservice.storage.persistentVolume': '永続',
'gpuservice.storage.persistentVolume.required': 'gpuservice.storage.persistentVolume.required':
'永続ボリュームを選択してください', 'ストレージを選択してください',
'gpuservice.storage.tempCapacity': 'ストレージ容量 (GB)', 'gpuservice.storage.persistentVolume.capacity': '容量 (GB)',
'gpuservice.storage.persistentVolume.capacity.required':
'容量を入力してください',
'gpuservice.storage.persistentVolume.releaseWithInstance':
'インスタンスと共に解放',
'gpuservice.storage.tempCapacity': '容量 (GB)',
'gpuservice.storage.tempCapacity.required': 'gpuservice.storage.tempCapacity.required':
'ローカルの一時ストレージ容量を入力してください' '一時ストレージ容量を入力してください',
'gpuservice.form.rule.name':
"小文字、数字、'-' のみ使用可能。文字または数字で始まり、文字または数字で終わる必要があり、連続する '-' は不可、最大 63 文字。",
'gpuservice.storage.temporary.tips':
'Data is cleared when the instance stops.',
'gpuservice.storage.persistentVolume.tips':
'Data persists across instance restarts. Persistent volumes remain intact after instance termination and can be shared by multiple instances.',
'gpuservice.form.storage.select': 'ストレージを選択'
}; };
+15 -11
View File
@@ -8,7 +8,7 @@ export default {
'menu.playground.text2images': '画像生成', 'menu.playground.text2images': '画像生成',
'menu.playground.video': '動画', 'menu.playground.video': '動画',
'menu.compare': '比較', 'menu.compare': '比較',
'menu.models': 'モデル', 'menu.models': 'モデルサービス',
'menu.models.modelList': 'デプロイと管理', 'menu.models.modelList': 'デプロイと管理',
'menu.models.modelCatalog': 'カタログ', 'menu.models.modelCatalog': 'カタログ',
'menu.models.catalog': 'モデルカタログ', 'menu.models.catalog': 'モデルカタログ',
@@ -23,28 +23,32 @@ export default {
'menu.resources': 'リソース', 'menu.resources': 'リソース',
'menu.apikeys': 'APIキー', 'menu.apikeys': 'APIキー',
'menu.users': 'ユーザー', 'menu.users': 'ユーザー',
'menu.profile': 'プロフィール', 'menu.profile': 'Preferences',
'menu.login': 'ログイン', 'menu.login': 'ログイン',
'menu.usage': '使用状況', 'menu.usage': '使用状況',
'menu.usage.usage': '使用状況',
'menu.billingAndUsage': '使用状況と請求',
'menu.billingAndUsage.usage': '使用状況',
'menu.404': '404', 'menu.404': '404',
'menu.settings': 'Settings', 'menu.settings': 'Settings',
'menu.resources.workers': 'Workers', 'menu.resources.workers': 'Workers',
'menu.resources.gpus': 'GPUs', 'menu.resources.gpus': 'GPUs',
'menu.resources.modelfiles': 'Model Files', 'menu.models.modelfiles': 'Model Files',
'menu.accessControl': 'Access Control', 'menu.accessControl': 'Access Control',
'menu.accessControl.apikeys': 'API Keys', 'menu.accessControl.apikeys': 'API Keys',
'menu.accessControl.users': 'Users', 'menu.accessControl.users': 'Users',
'menu.clusterManagement': 'Cluster Management', 'menu.accessControl.organizations': 'Organizations',
'menu.clusterManagement.clusters': 'Clusters', 'menu.resources.clusters': 'Clusters',
'menu.clusterManagement.credentials': 'Cloud Credentials', 'menu.resources.credentials': 'Cloud Credentials',
'menu.models.userModels': 'My Models', 'menu.models.userModels': 'My Models',
'menu.clusterManagement.clusterDetail': 'Cluster Detail', 'menu.resources.clusterDetail': 'Cluster Detail',
'menu.clusterManagement.clusterCreate': 'Create Cluster', 'menu.resources.clusterCreate': 'Create Cluster',
'menu.resources.backendsList': 'Inference Backends', 'menu.models.backendsList': 'Inference Backends',
'menu.gpuService': 'GPU Service', 'menu.gpuService': 'GPU Service',
'menu.gpuService.instances': 'GPU Instances', 'menu.gpuService.instances': 'GPU Instances',
'menu.gpuService.templates': 'Instance Templates', 'menu.gpuService.templates': 'Instance Templates',
'menu.gpuService.storage': 'Storage', 'menu.gpuService.storage': 'Storage',
'menu.gpuService.storageTypes': 'ストレージタイプ',
'menu.gpuService.publicKeys': 'SSH Public Keys' 'menu.gpuService.publicKeys': 'SSH Public Keys'
}; };
@@ -52,7 +56,7 @@ export default {
// 1. 'menu.models.deployment': 'Deployment', // 1. 'menu.models.deployment': 'Deployment',
// 2. 'menu.resources.workers': 'Workers', // 2. 'menu.resources.workers': 'Workers',
// 3. 'menu.resources.gpus': 'GPUs', // 3. 'menu.resources.gpus': 'GPUs',
// 4. 'menu.resources.modelfiles': 'Model Files', // 4. 'menu.models.modelfiles': 'Model Files',
// 5. 'menu.accessControl': 'Access Control', // 5. 'menu.accessControl': 'Access Control',
// 6. 'menu.accessControl.apikeys': 'API Keys', // 6. 'menu.accessControl.apikeys': 'API Keys',
// 7. 'menu.accessControl.users': 'Users', // 7. 'menu.accessControl.users': 'Users',
@@ -62,7 +66,7 @@ export default {
// 11. 'menu.models.userModels': 'My Models' // 11. 'menu.models.userModels': 'My Models'
// 12. 'menu.clusterManagement.clusterDetail': 'Cluster Detail', // 12. 'menu.clusterManagement.clusterDetail': 'Cluster Detail',
// 13. 'menu.clusterManagement.clusterCreate': 'Create Cluster', // 13. 'menu.clusterManagement.clusterCreate': 'Create Cluster',
// 14. 'menu.resources.backendsList': 'Inference Backends', // 14. 'menu.models.backendsList': 'Inference Backends',
// 15. 'menu.models.benchmark': 'Benchmarks', // 15. 'menu.models.benchmark': 'Benchmarks',
// 15. 'menu.models.provider': 'Provider', // 15. 'menu.models.provider': 'Provider',
// 15. 'menu.models.providers': 'Provider', // 15. 'menu.models.providers': 'Provider',
+15 -3
View File
@@ -63,7 +63,7 @@ export default {
'models.form.backend': 'バックエンド', 'models.form.backend': 'バックエンド',
'models.form.backend_parameters': 'バックエンドパラメータ', 'models.form.backend_parameters': 'バックエンドパラメータ',
'models.instance.params.configured': 'User Configured', 'models.instance.params.configured': 'User Configured',
'models.instance.params.autoInjected': '自動注入', 'models.instance.params.autoInjected': '自動注入パラメータ',
'models.search.gguf.tips': 'models.search.gguf.tips':
'GGUFモデルはllama-boxを使用します(Linux、macOS、Windowsをサポート)。', 'GGUFモデルはllama-boxを使用します(Linux、macOS、Windowsをサポート)。',
'models.search.vllm.tips': 'models.search.vllm.tips':
@@ -290,7 +290,13 @@ export default {
'models.instance.previousRun': 'Previous Run', 'models.instance.previousRun': 'Previous Run',
'models.instance.startHistory': 'Run History', 'models.instance.startHistory': 'Run History',
'models.instance.startHistory.tips': 'models.instance.startHistory.tips':
'Shows logs from the run before the last error-triggered restart.' 'Shows logs from the run before the last error-triggered restart.',
'models.form.lora.label': 'LoRA Adapters',
'models.form.lora.add': 'Add LoRA Adapter',
'models.form.lora.select': 'Select LoRA',
'models.form.lora.name': 'LoRA name',
'models.form.lora.rule.empty': 'Input cannot be empty',
'models.form.lora.rule.duplicate': 'LoRA name cannot be duplicated'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -393,5 +399,11 @@ export default {
// 78. 'models.form.enableModelRoute.tips': 'Enable Model Route', // 78. 'models.form.enableModelRoute.tips': 'Enable Model Route',
// 79. 'models.table.modelView': 'Model View', // 79. 'models.table.modelView': 'Model View',
// 80. 'models.table.instanceView': 'Instance View', // 80. 'models.table.instanceView': 'Instance View',
// 81. 'models.table.category': 'Category' // 81. 'models.table.category': 'Category',
// 82. 'models.form.lora.label': 'LoRA Adapter',
// 83. 'models.form.lora.add': 'Add LoRA Adapter',
// 84. 'models.form.lora.select': 'Select LoRA',
// 85. 'models.form.lora.name': 'LoRA name',
// 86. 'models.form.lora.rule.empty': 'Input cannot be empty',
// 87. 'models.form.lora.rule.duplicate': 'LoRA name cannot be duplicated'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+12 -1
View File
@@ -36,9 +36,12 @@ export default {
'noresult.catalog.nofound': 'No matching models found.', 'noresult.catalog.nofound': 'No matching models found.',
'noresult.resources.cluster': 'noresult.resources.cluster':
'No clusters available. Add a cluster to get started.', 'No clusters available. Add a cluster to get started.',
'noresult.resources.k8sCluster':
'No clusters available. Add a Kubernetes cluster to get started.',
'noresult.resources.worker': 'noresult.resources.worker':
'No workers available. Add a worker to get started.', 'No workers available. Add a worker to get started.',
'noresult.resources.gotocluster': 'Create Your First Cluster', 'noresult.resources.gotocluster': 'Create Your First Cluster',
'noresult.resources.addk8scluster': 'Add a Kubernetes Cluster',
'noresult.resources.gotoworker': 'Add Worker', 'noresult.resources.gotoworker': 'Add Worker',
'noresult.benchmark.title': 'No Benchmarks', 'noresult.benchmark.title': 'No Benchmarks',
'noresult.benchmark.subTitle': 'No benchmarks have been added yet.', 'noresult.benchmark.subTitle': 'No benchmarks have been added yet.',
@@ -66,5 +69,13 @@ export default {
'noresult.gpuservice.storage.title': 'ストレージなし', 'noresult.gpuservice.storage.title': 'ストレージなし',
'noresult.gpuservice.storage.subTitle': 'noresult.gpuservice.storage.subTitle':
'ストレージはまだ追加されていません。', 'ストレージはまだ追加されていません。',
'noresult.gpuservice.storage.nofound': '一致するストレージが見つかりません。' 'noresult.gpuservice.storage.nofound': '一致するストレージが見つかりません。',
'noresult.gpuservice.storageType.title': 'ストレージタイプなし',
'noresult.gpuservice.storageType.subTitle':
'ストレージタイプはまだ追加されていません。',
'noresult.gpuservice.storageType.nofound':
'一致するストレージタイプが見つかりません。',
'noresult.gpuservice.sshkey.title': 'SSH 公開鍵なし',
'noresult.gpuservice.sshkey.subTitle': 'SSH 公開鍵はまだ追加されていません。',
'noresult.gpuservice.sshkey.nofound': '一致する SSH 公開鍵が見つかりません。'
}; };
+15
View File
@@ -0,0 +1,15 @@
export default {
'organizations.upsell.title': 'Organizations are an Enterprise feature',
'organizations.upsell.subtitle':
'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to GPUStack Enterprise to manage organizations.',
'organizations.upsell.featuresTitle': 'What you get in Enterprise',
'organizations.upsell.feature.orgs':
'Create organizations to group users and isolate workloads',
'organizations.upsell.feature.members':
'Manage members and roles per organization',
'organizations.upsell.feature.quotas':
'Set resource and token quotas per organization',
'organizations.upsell.feature.isolation':
'Scope API keys, model deployments, and resources per organization',
'organizations.upsell.cta': 'Learn about Enterprise'
};
+1
View File
@@ -99,6 +99,7 @@ export default {
'resources.worker': 'Worker', 'resources.worker': 'Worker',
'resources.modelfiles.form.exsting': 'Downloaded', 'resources.modelfiles.form.exsting': 'Downloaded',
'resources.modelfiles.form.added': 'Added', 'resources.modelfiles.form.added': 'Added',
'resources.modelfiles.form.isLora': 'Is LoRA',
'resources.worker.maintenance.title': 'System Maintenance', 'resources.worker.maintenance.title': 'System Maintenance',
'resources.worker.maintenance.enable': 'Enter Maintenance Mode', 'resources.worker.maintenance.enable': 'Enter Maintenance Mode',
'resources.worker.maintenance.disable': 'Exit Maintenance Mode', 'resources.worker.maintenance.disable': 'Exit Maintenance Mode',
+71 -1
View File
@@ -17,9 +17,15 @@ export default {
'usage.table.user.apiKeysUsed': 'API Keys Used', 'usage.table.user.apiKeysUsed': 'API Keys Used',
'usage.table.lastActive': 'Last Active', 'usage.table.lastActive': 'Last Active',
'usage.filter.granularity': 'Granularity', 'usage.filter.granularity': 'Granularity',
'usage.filter.granularity.hour': '時間',
'usage.filter.granularity.day': 'Day', 'usage.filter.granularity.day': 'Day',
'usage.filter.granularity.week': 'Week', 'usage.filter.granularity.week': 'Week',
'usage.filter.granularity.month': 'Month', 'usage.filter.granularity.month': 'Month',
'usage.tabs.summary': '概要',
'usage.tabs.tokens': 'トークン',
'usage.tabs.gpuInstances': 'GPU インスタンス',
'usage.tabs.storage': 'ストレージ',
'usage.tabs.resourceEvents': 'リソースイベント',
'usage.tabs.models': 'Models', 'usage.tabs.models': 'Models',
'usage.tabs.apikeys': 'API Keys', 'usage.tabs.apikeys': 'API Keys',
'usage.tabs.users': 'User', 'usage.tabs.users': 'User',
@@ -32,5 +38,69 @@ export default {
'usage.chart.cached': 'Cached', 'usage.chart.cached': 'Cached',
'usage.chart.uncached': 'Uncached', 'usage.chart.uncached': 'Uncached',
'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)', 'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)',
'usage.table.inputTokensCached': 'Input Tokens Cached' 'usage.table.inputTokensCached': 'Input Tokens Cached',
// --- Resource usage: shared metrics & units ---
'usage.metric.tokens': 'トークン数',
'usage.metric.input': '入力',
'usage.metric.output': '出力',
'usage.metric.gpuHours': 'GPU 時間',
'usage.metric.instanceHours': 'インスタンス時間',
'usage.metric.gbDays': 'GB·日',
'usage.metric.gbHours': 'GB·時間',
'usage.metric.activeUsers': 'アクティブユーザー',
'usage.metric.activeInstances': 'アクティブインスタンス',
'usage.metric.activeStorage': 'アクティブストレージ',
'usage.metric.activeVolumes': 'アクティブボリューム',
'usage.metric.storageTypes': 'ストレージタイプ',
'usage.metric.gpuHours.tip':
'インスタンスの稼働時間を GPU 数で重み付けした値:N 個の GPU を使用するインスタンスが H 時間稼働すると N × H GPU 時間としてカウントされます。すべてのインスタンスが単一の GPU を使用する場合はインスタンス時間と等しくなります。',
'usage.metric.instanceHours.tip':
'GPU の数に関係なく、すべてのインスタンスの稼働時間を合計した値。1 つのインスタンスが 2 時間稼働 = 2 インスタンス時間。',
'usage.metric.gbDays.tip':
'ストレージ容量を時間で積分した値(GB × 日):10 GB を 5 日間保持 = 50 GB·日。(= GB·時間 ÷ 24',
'usage.metric.gbHours.tip':
'ストレージ容量を時間で積分した値(GB × 時間):10 GB を 5 時間保持 = 50 GB·時間。',
// --- Resource usage: common table / labels ---
'usage.common.noData': 'データがありません',
'usage.common.unknown': '不明',
'usage.table.date': '日付',
'usage.table.name': '名前',
'usage.table.user': 'ユーザー',
'usage.table.users': 'ユーザー',
'usage.table.type': 'タイプ',
'usage.table.instance': 'インスタンス',
'usage.table.instanceType': 'インスタンスタイプ',
'usage.table.instanceTypes': 'インスタンスタイプ',
'usage.table.instances': 'インスタンス',
'usage.table.capacity': '容量',
'usage.export.tableNamed': 'テーブルデータをエクスポート — {name}',
// --- Summary tab ---
'usage.summary.compute': 'コンピュート',
'usage.summary.tokensOverTime': 'トークン数の推移',
'usage.summary.gpuHoursOverTime': 'GPU 時間の推移',
'usage.summary.gbDaysOverTime': 'GB·日の推移',
// --- GPU Instances / Storage filters ---
'usage.filter.instance': 'インスタンスで絞り込み',
'usage.filter.storage': 'ストレージで絞り込み',
// --- Resource events ---
'usage.events.resourceType': 'リソースタイプ',
'usage.events.eventType': 'イベントタイプ',
'usage.events.col.time': '時刻',
'usage.events.col.resource': 'リソース',
'usage.events.col.event': 'イベント',
'usage.events.col.message': 'メッセージ',
'usage.events.resource.gpuInstance': 'GPU インスタンス',
'usage.events.resource.cpuInstance': 'CPU インスタンス',
'usage.events.type.created': '作成済み',
'usage.events.type.deleted': '削除済み',
'usage.events.type.started': '開始',
'usage.events.type.stopped': '停止',
'usage.events.type.updated': '更新済み',
'usage.events.type.attached': 'アタッチ済み',
'usage.events.type.detached': 'デタッチ済み'
}; };
+1 -1
View File
@@ -4,7 +4,7 @@ export default {
'vendor.hygon': 'Hygon', 'vendor.hygon': 'Hygon',
'vendor.moorthreads': 'Moore Threads', 'vendor.moorthreads': 'Moore Threads',
'vendor.iluvatar': 'Iluvatar', 'vendor.iluvatar': 'Iluvatar',
'vendor.metax': 'Metax', 'vendor.metax': 'MetaX',
'vendor.cambricon': 'Cambricon', 'vendor.cambricon': 'Cambricon',
'vendor.thead': 'T-Head PPU' 'vendor.thead': 'T-Head PPU'
}; };
+3 -1
View File
@@ -23,7 +23,9 @@ export default {
'apikeys.accessScope.inference': 'Inference APIs', 'apikeys.accessScope.inference': 'Inference APIs',
'apikeys.access.permissions': 'Access Permissions', 'apikeys.access.permissions': 'Access Permissions',
'apikeys.type.auto': 'Auto-generated', 'apikeys.type.auto': 'Auto-generated',
'apikeys.type.custom': 'Custom' 'apikeys.type.custom': 'Custom',
'apikeys.button.ipConfig': 'IP Access Control',
'quotaLimits.button.title': 'Quota Limit'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
+7
View File
@@ -23,6 +23,13 @@ export default {
'backend.form.defaultExecuteCommand': 'Команда выполнения по умолчанию', 'backend.form.defaultExecuteCommand': 'Команда выполнения по умолчанию',
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' и '{{'model_name'}}' заполняются реальными значениями во время запуска`, 'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' и '{{'model_name'}}' заполняются реальными значениями во время запуска`,
'backend.form.defaultBackendParameters': 'Параметры бэкенда по умолчанию', 'backend.form.defaultBackendParameters': 'Параметры бэкенда по умолчанию',
'backend.form.parameterFormat': 'Формат параметров',
'backend.form.parameterFormat.default': 'По умолчанию бэкенда',
'backend.form.parameterFormat.space': 'Пробел (--key value)',
'backend.form.parameterFormat.equal': 'Знак равенства (--key=value)',
'backend.form.commonParameters': 'Часто используемые параметры',
'backend.form.commonParameters.tips':
'Отображаются как подсказки в поле параметров бэкенда при развёртывании.',
'backend.form.versionConfig': 'Конфигурация версий', 'backend.form.versionConfig': 'Конфигурация версий',
'backend.form.addParameter': 'Добавить параметр', 'backend.form.addParameter': 'Добавить параметр',
'backend.form.noVersion': 'Версии не добавлены', 'backend.form.noVersion': 'Версии не добавлены',
+37 -2
View File
@@ -39,9 +39,11 @@ export default {
'clusters.workerpool.batchSize.desc': 'clusters.workerpool.batchSize.desc':
'Количество воркеров, создаваемых одновременно в пуле воркеров', 'Количество воркеров, создаваемых одновременно в пуле воркеров',
'clusters.create.addworker.tips': 'clusters.create.addworker.tips':
'Пожалуйста, убедитесь, что выполнены <a href={link} target="_blank">предварительные условия</a> для {label} перед выполнением следующей команды.', 'Пожалуйста, убедитесь, что выполнены <a href={link} target="_blank">предварительные условия</a> перед выполнением следующей команды.',
'clusters.create.addCommand.tips': 'clusters.create.addCommand.tips':
'На воркере, который необходимо добавить, выполните следующую команду, чтобы присоединить его к кластеру.', 'На воркере, который необходимо добавить, выполните следующую команду, чтобы присоединить его к кластеру.',
'clusters.create.addCommand.k8s.tips':
'На Kubernetes-кластере, который необходимо зарегистрировать, выполните следующую команду, чтобы создать ресурсы Kubernetes и зарегистрировать этот кластер.',
'cluster.create.checkEnv.tips': 'cluster.create.checkEnv.tips':
'Используйте следующую команду для проверки готовности окружения', 'Используйте следующую команду для проверки готовности окружения',
'clusters.create.register.tips': 'clusters.create.register.tips':
@@ -67,6 +69,7 @@ export default {
'clusters.addworker.selectCluster.tips': 'clusters.addworker.selectCluster.tips':
'Для <span class="bold-text">не-Docker</span> кластеров, пожалуйста, регистрируйте кластеры или управляйте пулами воркеров на странице Кластеры.', 'Для <span class="bold-text">не-Docker</span> кластеров, пожалуйста, регистрируйте кластеры или управляйте пулами воркеров на странице Кластеры.',
'clusters.addworker.selectGPU': 'Выбрать производителя GPU', 'clusters.addworker.selectGPU': 'Выбрать производителя GPU',
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
'clusters.addworker.checkEnv': 'Проверить окружение', 'clusters.addworker.checkEnv': 'Проверить окружение',
'clusters.addworker.specifyArgs': 'Указать аргументы', 'clusters.addworker.specifyArgs': 'Указать аргументы',
'clusters.addworker.runCommand': 'Выполнить команду', 'clusters.addworker.runCommand': 'Выполнить команду',
@@ -107,6 +110,10 @@ export default {
'{count} новых воркеров были добавлены в кластер.', '{count} новых воркеров были добавлены в кластер.',
'clusters.create.serverUrl': 'URL сервера GPUStack', 'clusters.create.serverUrl': 'URL сервера GPUStack',
'clusters.create.workerConfig': 'Конфигурация воркера', 'clusters.create.workerConfig': 'Конфигурация воркера',
'clusters.edit.k8sOptions.changed.tip':
'Вы изменили параметры Kubernetes. Чтобы изменения вступили в силу, повторно выполните команду регистрации в целевом кластере.',
'clusters.edit.workerConfig.tip':
'Изменения конфигурации воркера вступают в силу только после перезапуска соответствующих воркеров.',
'clusters.addworker.containerName': 'Имя контейнера воркера', 'clusters.addworker.containerName': 'Имя контейнера воркера',
'clusters.addworker.containerName.tips': 'clusters.addworker.containerName.tips':
'Укажите имя для контейнера воркера.', 'Укажите имя для контейнера воркера.',
@@ -159,7 +166,35 @@ export default {
'clusters.volume.pvc.readOnly': 'Read Only', 'clusters.volume.pvc.readOnly': 'Read Only',
'clusters.volume.configMap.name': 'ConfigMap Name', 'clusters.volume.configMap.name': 'ConfigMap Name',
'clusters.volume.configMap.optional': 'Optional', 'clusters.volume.configMap.optional': 'Optional',
'clusters.volume.add': 'Add Volume Mount' 'clusters.volume.add': 'Add Volume Mount',
'clusters.systemDefaultContainerRegistry.title': 'Default Container Registry',
'clusters.systemDefaultContainerRegistry.tip':
'Default registry used to resolve GPUStack images for this cluster. Falls back to the server default when unset.',
'clusters.k8sOptions.title': 'Kubernetes Deployment Options',
'clusters.imageCredentials.title': 'Image Credentials',
'clusters.imageCredentials.add': 'Add Credential',
'clusters.imageCredentials.registry': 'Registry',
'clusters.imageCredentials.username': 'Username',
'clusters.imageCredentials.password': 'Password',
'clusters.nodeSelector.title': 'Node Selector',
'clusters.nodeSelector.tip':
'Pod nodeSelector applied to every worker DaemonSet — only nodes whose labels match are eligible to run the worker.',
'clusters.operatorImage.title': 'Operator Image',
'clusters.operatorImage.tip':
'Override for the GPUStack Operator container image. Leave empty to use the server default.',
'clusters.namespace.title': 'Namespace',
'clusters.namespace.tip':
'Kubernetes namespace the clusters manifests render into. Leave empty to use gpustack-system.',
'clusters.clusterType.title': 'Cluster Type',
'clusters.modelService.title': 'Model Service',
'clusters.modelService.tip':
'For LLM inference and API serving — e.g. exposing model APIs and token-based services.',
'clusters.gpuInstances.title': 'GPU Service',
'clusters.gpuInstances.tip':
'For on-demand GPU compute — e.g. interactive development, training jobs, or custom environments.',
'clusters.gpuInstances.staticAddress': 'GPU Service Static Access Address',
'clusters.gpuInstances.staticAddress.tip':
'Static address the operator uses to access GPU instances in this cluster (e.g. a LoadBalancer VIP). Optional.'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
+12 -3
View File
@@ -46,22 +46,28 @@ export default {
'common.button.enabled': 'Активно', 'common.button.enabled': 'Активно',
'common.button.disabled': 'Отключено', 'common.button.disabled': 'Отключено',
'common.button.upgrade': 'Обновить', 'common.button.upgrade': 'Обновить',
'common.enterprise.feature': 'Available in GPUStack Enterprise',
'common.input.holder': 'Введите значение', 'common.input.holder': 'Введите значение',
'common.validate.value': 'Поле {name} обязательно', 'common.validate.value': 'Поле {name} обязательно',
'common.button.edit': 'Редактировать', 'common.button.edit': 'Редактировать',
'common.button.authorize': 'Настройка прав', 'common.button.authorize': 'Настройка прав',
'common.button.confirm': 'Подтвердить', 'common.button.confirm': 'Подтвердить',
'common.button.viewlog': 'Просмотр логов', 'common.button.viewlog': 'Просмотр логов',
'common.button.viewevent': 'Просмотр событий',
'common.button.recreate': 'Пересоздать',
'common.table.operation': 'Действия', 'common.table.operation': 'Действия',
'common.table.creator': 'Создатель',
'common.table.createTime': 'Создано', 'common.table.createTime': 'Создано',
'common.table.updateTime': 'Обновлено', 'common.table.updateTime': 'Обновлено',
'common.table.description': 'Описание', 'common.table.description': 'Описание',
'common.table.displayName': 'Отображаемое имя',
'common.table.name': 'Название', 'common.table.name': 'Название',
'common.table.status': 'Статус', 'common.table.status': 'Статус',
'common.table.name.list': 'Название {type}', 'common.table.name.list': 'Название {type}',
'common.search.name.placeholder': 'Фильтр по названию', 'common.search.name.placeholder': 'Фильтр по названию',
'common.search.id.placeholder': 'Фильтр по ID', 'common.search.id.placeholder': 'Фильтр по ID',
'common.filter.byId': 'Фильтр по ID', 'common.filter.byId': 'Фильтр по ID',
'common.filter.byCreator': 'Фильтр по создателю',
'common.table.type': 'Тип', 'common.table.type': 'Тип',
'common.table.default': 'Значение по умолчанию', 'common.table.default': 'Значение по умолчанию',
'common.copy.success': 'Скопировано!', 'common.copy.success': 'Скопировано!',
@@ -221,7 +227,6 @@ export default {
'common.text.latest': 'Последняя', 'common.text.latest': 'Последняя',
'common.text.new': 'Новая', 'common.text.new': 'Новая',
'common.text.changelog': 'История изменений', 'common.text.changelog': 'История изменений',
'common.button.recreate': 'Пересоздать',
'common.button.delrecreate': 'Удалить (Пересоздать)', 'common.button.delrecreate': 'Удалить (Пересоздать)',
'common.options.all': 'Все', 'common.options.all': 'Все',
'common.options.none': 'Нет', 'common.options.none': 'Нет',
@@ -258,7 +263,7 @@ export default {
'common.sso.noConfig': 'common.sso.noConfig':
'Единый вход не настроен в этой системе. Пожалуйста, обратитесь к администратору.', 'Единый вход не настроен в этой системе. Пожалуйста, обратитесь к администратору.',
'common.button.edit.item': 'Редактировать {name}', 'common.button.edit.item': 'Редактировать {name}',
'common.button.copy.item': 'Duplicate {name}', 'common.button.copy.item': 'Дублировать {name}',
'common.button.terminal': 'Терминал', 'common.button.terminal': 'Терминал',
'common.button.addItem': 'Добавить элемент', 'common.button.addItem': 'Добавить элемент',
'common.help.default': 'По умолчанию: {content}', 'common.help.default': 'По умолчанию: {content}',
@@ -280,7 +285,11 @@ export default {
'common.file.format.limit': 'Invalid file format. Allowed: {formats}.', 'common.file.format.limit': 'Invalid file format. Allowed: {formats}.',
'common.image.limit.width': 'Image width must be {width}.', 'common.image.limit.width': 'Image width must be {width}.',
'common.image.limit.height': 'Image height must be {height}.', 'common.image.limit.height': 'Image height must be {height}.',
'common.max': 'Макс. {count}' 'common.remaining': 'Остаток {count}',
'common.max': 'Макс. {count}',
'common.max.count': 'Количество {label}',
'common.validate.group': 'Please complete the {group} configuration',
'common.preferences': 'Preferences'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
+5 -14
View File
@@ -1,31 +1,22 @@
export default { export default {
'dashboard.title': 'Панель управления',
'dashboard.workers': 'Рабочие узлы', 'dashboard.workers': 'Рабочие узлы',
'dashboard.models': 'Модели', 'dashboard.deployments': 'Deployments',
'dashboard.totalgpus': 'Всего GPU', 'dashboard.totalgpus': 'Всего GPU',
'dashboard.allocategpus': 'Выделенные GPU',
'dashboard.instances': 'Инстансы',
'dashboard.systemload': 'Нагрузка системы', 'dashboard.systemload': 'Нагрузка системы',
'dashboard.memory': 'ОЗУ', 'dashboard.memory': 'ОЗУ',
'dashboard.disk': 'Хранилище',
'dashboard.vram': 'VRAM', 'dashboard.vram': 'VRAM',
'dashboard.cpuutilization': 'Средняя загрузка CPU', 'dashboard.cpuutilization': 'Средняя загрузка CPU',
'dashboard.memoryutilization': 'Средняя загрузка ОЗУ', 'dashboard.memoryutilization': 'Средняя загрузка ОЗУ',
'dashboard.diskutilization': 'Использование хранилища',
'dashboard.vramutilization': 'Средняя загрузка видеопамяти', 'dashboard.vramutilization': 'Средняя загрузка видеопамяти',
'dashboard.gpuutilization': 'Средняя загрузка GPU', 'dashboard.gpuutilization': 'Средняя загрузка GPU',
'dashboard.usage': 'Использование', 'dashboard.usage': 'Использование',
'dashboard.apirequest': 'API-запросы', 'dashboard.usage.title': 'Использование за последние {days} дн.',
'dashboard.usage.others': 'Прочее',
'dashboard.tokens': 'Использование токенов', 'dashboard.tokens': 'Использование токенов',
'dashboard.topusers': 'Топ пользователей', 'dashboard.topusers': 'Топ пользователей',
'dashboard.activeModels': 'Активные модели', 'dashboard.activeDeployments': 'Active Deployments',
'dashboard.activeUsers': 'Активные пользователи', 'dashboard.usageByModel': 'Использование по моделям',
'dashboard.tokenUsageByModel': 'Использование токенов по моделям',
'dashboard.apiRequestsByModel': 'API-запросы по моделям',
'dashboard.topTokenUsageByUser': 'Топ-10 пользователей по токенам', 'dashboard.topTokenUsageByUser': 'Топ-10 пользователей по токенам',
'dashboard.topTokenUsageByApiKey': 'Топ-10 API-ключей по токенам',
'dashboard.runninginstances': 'Запущенные инстансы',
'dashboard.activeModels.name': 'Название модели',
'dashboard.allocatevram': 'Выделено VRAM / ОЗУ', 'dashboard.allocatevram': 'Выделено VRAM / ОЗУ',
'dashboard.usage.selectuser': 'Выбрать пользователей', 'dashboard.usage.selectuser': 'Выбрать пользователей',
'dashboard.usage.selectmodel': 'Выбрать модели', 'dashboard.usage.selectmodel': 'Выбрать модели',
+101 -9
View File
@@ -14,11 +14,26 @@ export default {
'gpuservice.template.command.placeholder': 'gpuservice.template.command.placeholder':
'Разделяйте аргументы пробелами; аргументы с пробелами заключайте в кавычки, например: /bin/bash -c "echo hello world"', 'Разделяйте аргументы пробелами; аргументы с пробелами заключайте в кавычки, например: /bin/bash -c "echo hello world"',
'gpuservice.template.mountPath': 'Путь монтирования', 'gpuservice.template.mountPath': 'Путь монтирования',
'gpuservice.template.mountPath.tips':
'Путь, по которому том хранилища монтируется по умолчанию при создании экземпляра из этого шаблона. Может использоваться для сохранения данных, которые нужно сохранить во время работы экземпляра.',
'gpuservice.template.containerDisk': 'Диск контейнера (GB)', 'gpuservice.template.containerDisk': 'Диск контейнера (GB)',
'gpuservice.template.containerDisk.tips':
'Размер системного диска контейнера.',
'gpuservice.template.memory': 'Память (GB)', 'gpuservice.template.memory': 'Память (GB)',
'gpuservice.instance.containerDisk.remaining':
'Диск контейнера (Макс. {count} GB)',
'gpuservice.instance.memory.remaining': 'Память (Макс. {count} GB)',
'gpuservice.template.displayName': 'Отображаемое имя',
'gpuservice.template.displayName.max':
'Отображаемое имя не должно превышать 63 символа.',
'gpuservice.template.ports': 'Порты', 'gpuservice.template.ports': 'Порты',
'gpuservice.template.ports.add': 'Добавить порт', 'gpuservice.template.ports.add': 'Добавить порт',
'gpuservice.template.ports.invalid': 'Заполните настройки портов полностью.', 'gpuservice.template.ports.invalid': 'Заполните настройки портов полностью.',
'gpuservice.template.ports.name': 'Имя',
'gpuservice.template.ports.name.max':
'Имя порта не должно превышать 16 символов.',
'gpuservice.template.ports.name.duplicate':
'Имена портов должны быть уникальными.',
'gpuservice.template.env': 'Переменные окружения', 'gpuservice.template.env': 'Переменные окружения',
'gpuservice.template.env.add': 'Добавить переменную окружения', 'gpuservice.template.env.add': 'Добавить переменную окружения',
'gpuservice.template.env.invalid': 'gpuservice.template.env.invalid':
@@ -29,7 +44,51 @@ export default {
'gpuservice.template.card.mount': 'Монтирование', 'gpuservice.template.card.mount': 'Монтирование',
'gpuservice.template.card.resources': 'Ресурсы', 'gpuservice.template.card.resources': 'Ресурсы',
'gpuservice.template.card.ports': 'Порты', 'gpuservice.template.card.ports': 'Порты',
'gpuservice.storageType': 'Тип хранилища',
'gpuservice.storageType.add': 'Добавить тип хранилища',
'gpuservice.storageType.edit': 'Изменить тип хранилища',
'gpuservice.storageType.filter.name': 'Поиск по имени',
'gpuservice.storageType.kind': 'Тип',
'gpuservice.storageType.mountOptions': 'Параметры монтирования',
'gpuservice.storageType.nfs.server': 'Сервер NFS',
'gpuservice.storageType.nfs.server.tips':
'Убедитесь, что адрес NFS-сервера доступен из всех кластеров Kubernetes.',
'gpuservice.storageType.nfs.share': 'Путь общего ресурса',
'gpuservice.storageType.nfs.share.tips':
'В этом общем пути будет автоматически создан каталог на основе названия организации и названия хранилища. Если указан подкаталог, итоговый каталог будет создан внутри него.',
'gpuservice.storageType.nfs.subDirectory': 'Подкаталог',
'gpuservice.storageType.nfs.subDirectory.tips':
'Если поле пустое, будет создан подкаталог с именем постоянного тома. Если задано, под этим подкаталогом будет создан каталог с именем постоянного тома.',
'gpuservice.storageType.nfs.mountPermissions': 'Права монтирования',
'gpuservice.storageType.nfs.mountPermissions.tips':
'Наследует права файлов с NFS-сервера.',
'gpuservice.storageType.s3.endpoint': 'Endpoint',
'gpuservice.storageType.s3.endpoint.tips':
'Убедитесь, что S3 endpoint доступен из всех кластеров Kubernetes.',
'gpuservice.storageType.s3.endpoint.rule':
'Должен начинаться с http или https',
'gpuservice.storageType.s3.region': 'Регион',
'gpuservice.storageType.s3.bucket': 'Бакет',
'gpuservice.storageType.s3.bucket.tips':
'Если поле пустое, будет создан новый бакет с именем постоянного тома. Если задано, в этом бакете будет создан подкаталог с именем постоянного тома.',
'gpuservice.storageType.s3.bucket.tips1':
'В этом бакете будет автоматически создан префикс на основе названия организации и названия хранилища.',
'gpuservice.storageType.s3.bucket.tips2':
'Например, если организация называется <span class="desc-block">awesome-group</span>, а хранилище — <span class="desc-block">storage-1</span>, итоговый префикс будет: <span class="desc-block">awesome-group/storage-1</span>.',
'gpuservice.storageType.s3.accessKey': 'Access Key',
'gpuservice.storageType.s3.secretKey': 'Secret Key',
'gpuservice.storageType.s3.insecure':
'Пропустить проверку сертификата TLS/SSL',
'gpuservice.storageType.s3.insecure.tips':
'Если включено, сертификат сервера S3 не проверяется. Подходит для внутреннего тестирования или самоподписанных сертификатов; в производственной среде включайте с осторожностью.',
'gpuservice.publicKey': 'Открытый ключ SSH',
'gpuservice.publicKey.add': 'Добавить открытый ключ SSH',
'gpuservice.publicKey.edit': 'Изменить открытый ключ SSH',
'gpuservice.publicKey.filter.name': 'Поиск по имени',
'gpuservice.publicKey.label': 'Открытый ключ SSH', 'gpuservice.publicKey.label': 'Открытый ключ SSH',
'gpuservice.instance.ssh.enable': 'Включить SSH-доступ',
'gpuservice.instance.ssh.assignKey': 'Назначить открытый ключ SSH',
'gpuservice.instance.ssh.addKey': 'Добавить открытый ключ SSH',
'gpuservice.publicKey.placeholder': 'gpuservice.publicKey.placeholder':
'Начинается с ssh-rsa или ssh-ed25519, по одному открытому ключу на строку\n\nПросмотр открытого ключа:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub', 'Начинается с ssh-rsa или ssh-ed25519, по одному открытому ключу на строку\n\nПросмотр открытого ключа:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub',
'gpuservice.instance': 'Экземпляр GPU', 'gpuservice.instance': 'Экземпляр GPU',
@@ -45,22 +104,44 @@ export default {
'gpuservice.instance.templates': 'Шаблоны экземпляров', 'gpuservice.instance.templates': 'Шаблоны экземпляров',
'gpuservice.instance.section.storage': 'Том хранилища', 'gpuservice.instance.section.storage': 'Том хранилища',
'gpuservice.instance.type.required': 'Выберите тип экземпляра', 'gpuservice.instance.type.required': 'Выберите тип экземпляра',
'gpuservice.instance.type.noAvailable': 'Нет доступных типов экземпляров',
'gpuservice.instance.gpuCount': 'Количество GPU', 'gpuservice.instance.gpuCount': 'Количество GPU',
'gpuservice.instance.gpuCount.required': 'Введите количество GPU', 'gpuservice.instance.gpuCount.required': 'Введите количество GPU',
'gpuservice.instance.gpuCount.max': 'gpuservice.instance.gpuCount.max': 'Выберите максимум {count} GPU-карт',
'Текущий тип экземпляра поддерживает максимум {count} GPU', 'gpuservice.instance.gpuCount.min': 'Выберите минимум {count} GPU-карт',
'gpuservice.instance.cpuCount.max': 'Выберите максимум {count} ядер CPU',
'gpuservice.instance.cpuCount.min': 'Выберите минимум {count} ядер CPU',
'gpuservice.instance.gpuCount.noAvailable':
'Нет доступных ресурсов GPU, выберите другой тип экземпляра.',
'gpuservice.instance.gpuCount.zero': 'Только CPU, для подготовки окружения.',
'gpuservice.instance.stock': 'Остаток', 'gpuservice.instance.stock': 'Остаток',
'gpuservice.instance.sliced': 'Разделено', 'gpuservice.instance.sliced': 'Разделено',
'gpuservice.instance.memory': 'Память', 'gpuservice.instance.memory': 'VRAM',
'gpuservice.instance.ram': 'RAM', 'gpuservice.instance.ram': 'RAM',
'gpuservice.instance.search.type.placeholder': 'gpuservice.instance.disk': 'Диск',
'Поиск по имени, VRAM, памяти или vCPU', 'gpuservice.table.count': 'Количество',
'gpuservice.instance.disk.system': 'Системный диск',
'gpuservice.instance.disk.ephemeral': 'Временное хранилище',
'gpuservice.instance.disk.persistent': 'Постоянное хранилище',
'gpuservice.instance.search.type.placeholder': 'Поиск по имени',
'gpuservice.instance.search.template.placeholder': 'gpuservice.instance.search.template.placeholder':
'Поиск по имени шаблона, образу или пути монтирования', 'Поиск по имени шаблона, образу или пути монтирования',
'gpuservice.instance.template.image': 'Образ', 'gpuservice.instance.template.image': 'Образ',
'gpuservice.instance.template.mount': 'Монтирование', 'gpuservice.instance.template.mount': 'Монтирование',
'gpuservice.instance.connect': 'Подключение', 'gpuservice.instance.connect': 'Подключение',
'gpuservice.instance.connect.copySshCommand': 'Скопировать команду SSH', 'gpuservice.instance.connect.copySshCommand': 'Скопировать команду SSH',
'gpuservice.instance.event.reason': 'Причина',
'gpuservice.instance.event.message': 'Сообщение',
'gpuservice.instance.event.source': 'Источник',
'gpuservice.instance.event.count': 'Кол-во',
'gpuservice.instance.event.lastSeen': 'Последнее событие',
'gpuservice.instance.event.recentHourTip':
'Отображаются только события за последний час',
'gpuservice.instance.event.tab.instance': 'События экземпляра',
'gpuservice.instance.event.tab.volume': 'События тома',
'gpuservice.instance.recreate.confirm.title': 'Подтвердить пересоздание',
'gpuservice.instance.recreate.confirm.content':
'Текущий экземпляр будет сначала удалён, а затем пересоздан с текущей конфигурацией.\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
'gpuservice.storage': 'Хранилище', 'gpuservice.storage': 'Хранилище',
'gpuservice.storage.add': 'Добавить хранилище', 'gpuservice.storage.add': 'Добавить хранилище',
'gpuservice.storage.edit': 'Редактировать хранилище', 'gpuservice.storage.edit': 'Редактировать хранилище',
@@ -73,9 +154,20 @@ export default {
'gpuservice.storage.accessMode': 'Режим доступа', 'gpuservice.storage.accessMode': 'Режим доступа',
'gpuservice.storage.persistent': 'Постоянное', 'gpuservice.storage.persistent': 'Постоянное',
'gpuservice.storage.temporary': 'Временное', 'gpuservice.storage.temporary': 'Временное',
'gpuservice.storage.persistentVolume': 'Постоянный том', 'gpuservice.storage.persistentVolume': 'Постоянное',
'gpuservice.storage.persistentVolume.required': 'Выберите постоянный том', 'gpuservice.storage.persistentVolume.required': 'Выберите хранилище',
'gpuservice.storage.tempCapacity': 'Объём хранилища (ГБ)', 'gpuservice.storage.persistentVolume.capacity': 'Ёмкость (ГБ)',
'gpuservice.storage.persistentVolume.capacity.required': 'Введите ёмкость',
'gpuservice.storage.persistentVolume.releaseWithInstance':
'Освобождать вместе с экземпляром',
'gpuservice.storage.tempCapacity': 'Объём (ГБ)',
'gpuservice.storage.tempCapacity.required': 'gpuservice.storage.tempCapacity.required':
'Введите объём локального временного хранилища' 'Введите объём временного хранилища',
'gpuservice.form.rule.name':
"Строчные буквы, цифры и '-'. Должно начинаться и заканчиваться буквой или цифрой, без подряд идущих '-', максимум 63 символа.",
'gpuservice.storage.temporary.tips':
'Data is cleared when the instance stops.',
'gpuservice.storage.persistentVolume.tips':
'Data persists across instance restarts. Persistent volumes remain intact after instance termination and can be shared by multiple instances.',
'gpuservice.form.storage.select': 'Выберите хранилище'
}; };
+13 -9
View File
@@ -8,7 +8,7 @@ export default {
'menu.playground.text2images': 'Генерация изображений', 'menu.playground.text2images': 'Генерация изображений',
'menu.playground.video': 'Видео', 'menu.playground.video': 'Видео',
'menu.compare': 'Сравнение', 'menu.compare': 'Сравнение',
'menu.models': 'Модели', 'menu.models': 'Сервисы моделей',
'menu.models.modelList': 'Развертывание и управление', 'menu.models.modelList': 'Развертывание и управление',
'menu.models.modelCatalog': 'Каталог', 'menu.models.modelCatalog': 'Каталог',
'menu.models.catalog': 'Каталог моделей', 'menu.models.catalog': 'Каталог моделей',
@@ -23,28 +23,32 @@ export default {
'menu.resources': 'Ресурсы', 'menu.resources': 'Ресурсы',
'menu.apikeys': 'API-ключи', 'menu.apikeys': 'API-ключи',
'menu.users': 'Пользователи', 'menu.users': 'Пользователи',
'menu.profile': 'Профиль', 'menu.profile': 'Preferences',
'menu.login': 'Авторизация', 'menu.login': 'Авторизация',
'menu.usage': 'Использование', 'menu.usage': 'Использование',
'menu.usage.usage': 'Использование',
'menu.billingAndUsage': 'Использование и биллинг',
'menu.billingAndUsage.usage': 'Использование',
'menu.404': 'Ошибка 404', 'menu.404': 'Ошибка 404',
'menu.resources.workers': 'Воркеры', 'menu.resources.workers': 'Воркеры',
'menu.resources.gpus': 'GPUs', 'menu.resources.gpus': 'GPUs',
'menu.resources.modelfiles': 'Файлы модлей', 'menu.models.modelfiles': 'Файлы модлей',
'menu.accessControl': 'Управление доступом', 'menu.accessControl': 'Управление доступом',
'menu.accessControl.apikeys': 'API Ключи', 'menu.accessControl.apikeys': 'API Ключи',
'menu.accessControl.users': 'Пользователи', 'menu.accessControl.users': 'Пользователи',
'menu.clusterManagement': 'Управление кластерами', 'menu.accessControl.organizations': 'Организации',
'menu.clusterManagement.clusters': 'Кластеры', 'menu.resources.clusters': 'Кластеры',
'menu.clusterManagement.credentials': 'Облачные аккаунты', 'menu.resources.credentials': 'Облачные аккаунты',
'menu.models.userModels': 'Мои модели', 'menu.models.userModels': 'Мои модели',
'menu.clusterManagement.clusterDetail': 'Детали кластера', 'menu.resources.clusterDetail': 'Детали кластера',
'menu.clusterManagement.clusterCreate': 'Создать кластер', 'menu.resources.clusterCreate': 'Создать кластер',
'menu.resources.backendsList': 'Бэкенды запуска', 'menu.models.backendsList': 'Бэкенды запуска',
'menu.settings': 'Settings', 'menu.settings': 'Settings',
'menu.gpuService': 'GPU Service', 'menu.gpuService': 'GPU Service',
'menu.gpuService.instances': 'GPU Instances', 'menu.gpuService.instances': 'GPU Instances',
'menu.gpuService.templates': 'Instance Templates', 'menu.gpuService.templates': 'Instance Templates',
'menu.gpuService.storage': 'Storage', 'menu.gpuService.storage': 'Storage',
'menu.gpuService.storageTypes': 'Типы хранилищ',
'menu.gpuService.publicKeys': 'SSH Public Keys' 'menu.gpuService.publicKeys': 'SSH Public Keys'
}; };
+15 -3
View File
@@ -64,7 +64,7 @@ export default {
'models.form.backend': 'Бэкенд', 'models.form.backend': 'Бэкенд',
'models.form.backend_parameters': 'Параметры бэкенда', 'models.form.backend_parameters': 'Параметры бэкенда',
'models.instance.params.configured': 'User Configured', 'models.instance.params.configured': 'User Configured',
'models.instance.params.autoInjected': 'Автовнедрённые', 'models.instance.params.autoInjected': 'Автовнедрённые параметры',
'models.search.gguf.tips': 'models.search.gguf.tips':
'GGUF-модели используют llama-box (поддерживает Linux, macOS и Windows).', 'GGUF-модели используют llama-box (поддерживает Linux, macOS и Windows).',
'models.search.vllm.tips': 'models.search.vllm.tips':
@@ -294,7 +294,13 @@ export default {
'models.instance.previousRun': 'Previous Run', 'models.instance.previousRun': 'Previous Run',
'models.instance.startHistory': 'Run History', 'models.instance.startHistory': 'Run History',
'models.instance.startHistory.tips': 'models.instance.startHistory.tips':
'Shows logs from the run before the last error-triggered restart.' 'Shows logs from the run before the last error-triggered restart.',
'models.form.lora.label': 'LoRA Adapters',
'models.form.lora.add': 'Add LoRA Adapter',
'models.form.lora.select': 'Select LoRA',
'models.form.lora.name': 'LoRA name',
'models.form.lora.rule.empty': 'Input cannot be empty',
'models.form.lora.rule.duplicate': 'LoRA name cannot be duplicated'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -307,5 +313,11 @@ export default {
// 5. 'models.form.backend.sglang': 'Built-in support for NVIDIA, AMD, Ascend, Moore Threads, MetaX, T-Head PPU devices.', // 5. 'models.form.backend.sglang': 'Built-in support for NVIDIA, AMD, Ascend, Moore Threads, MetaX, T-Head PPU devices.',
// 6. 'models.table.modelView': 'Model List', // 6. 'models.table.modelView': 'Model List',
// 7. 'models.table.instanceView': 'Instance List', // 7. 'models.table.instanceView': 'Instance List',
// 8. 'models.table.category': 'Category' // 8. 'models.table.category': 'Category',
// 9. 'models.form.lora.label': 'LoRA Adapter',
// 10. 'models.form.lora.add': 'Add LoRA Adapter',
// 11. 'models.form.lora.select': 'Select LoRA',
// 12. 'models.form.lora.name': 'LoRA name',
// 13. 'models.form.lora.rule.empty': 'Input cannot be empty',
// 14. 'models.form.lora.rule.duplicate': 'LoRA name cannot be duplicated'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+12 -1
View File
@@ -37,9 +37,12 @@ export default {
'noresult.catalog.nofound': 'Подходящие модели не найдены.', 'noresult.catalog.nofound': 'Подходящие модели не найдены.',
'noresult.resources.cluster': 'noresult.resources.cluster':
'No clusters available. Add a cluster to get started.', 'No clusters available. Add a cluster to get started.',
'noresult.resources.k8sCluster':
'No clusters available. Add a Kubernetes cluster to get started.',
'noresult.resources.worker': 'noresult.resources.worker':
'No workers available. Add a worker to get started.', 'No workers available. Add a worker to get started.',
'noresult.resources.gotocluster': 'Create Your First Cluster', 'noresult.resources.gotocluster': 'Create Your First Cluster',
'noresult.resources.addk8scluster': 'Add a Kubernetes Cluster',
'noresult.resources.gotoworker': 'Add Worker', 'noresult.resources.gotoworker': 'Add Worker',
'noresult.benchmark.title': 'No Benchmarks', 'noresult.benchmark.title': 'No Benchmarks',
'noresult.benchmark.subTitle': 'No benchmarks have been added yet.', 'noresult.benchmark.subTitle': 'No benchmarks have been added yet.',
@@ -65,7 +68,15 @@ export default {
'Подходящие экземпляры GPU не найдены.', 'Подходящие экземпляры GPU не найдены.',
'noresult.gpuservice.storage.title': 'Нет хранилищ', 'noresult.gpuservice.storage.title': 'Нет хранилищ',
'noresult.gpuservice.storage.subTitle': 'Хранилища ещё не добавлены.', 'noresult.gpuservice.storage.subTitle': 'Хранилища ещё не добавлены.',
'noresult.gpuservice.storage.nofound': 'Подходящие хранилища не найдены.' 'noresult.gpuservice.storage.nofound': 'Подходящие хранилища не найдены.',
'noresult.gpuservice.storageType.title': 'Нет типов хранилищ',
'noresult.gpuservice.storageType.subTitle': 'Типы хранилищ ещё не добавлены.',
'noresult.gpuservice.storageType.nofound':
'Подходящие типы хранилищ не найдены.',
'noresult.gpuservice.sshkey.title': 'Нет открытых ключей SSH',
'noresult.gpuservice.sshkey.subTitle': 'Открытые ключи SSH ещё не добавлены.',
'noresult.gpuservice.sshkey.nofound':
'Подходящие открытые ключи SSH не найдены.'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
+15
View File
@@ -0,0 +1,15 @@
export default {
'organizations.upsell.title': 'Organizations are an Enterprise feature',
'organizations.upsell.subtitle':
'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to GPUStack Enterprise to manage organizations.',
'organizations.upsell.featuresTitle': 'What you get in Enterprise',
'organizations.upsell.feature.orgs':
'Create organizations to group users and isolate workloads',
'organizations.upsell.feature.members':
'Manage members and roles per organization',
'organizations.upsell.feature.quotas':
'Set resource and token quotas per organization',
'organizations.upsell.feature.isolation':
'Scope API keys, model deployments, and resources per organization',
'organizations.upsell.cta': 'Learn about Enterprise'
};
+1
View File
@@ -97,6 +97,7 @@ export default {
'resources.worker': 'Рабочий узел', 'resources.worker': 'Рабочий узел',
'resources.modelfiles.form.exsting': 'Загружено', 'resources.modelfiles.form.exsting': 'Загружено',
'resources.modelfiles.form.added': 'Добавлено', 'resources.modelfiles.form.added': 'Добавлено',
'resources.modelfiles.form.isLora': 'Is LoRA',
'resources.worker.maintenance.title': 'System Maintenance', 'resources.worker.maintenance.title': 'System Maintenance',
'resources.worker.maintenance.enable': 'Enter Maintenance Mode', 'resources.worker.maintenance.enable': 'Enter Maintenance Mode',
'resources.worker.maintenance.disable': 'Exit Maintenance Mode', 'resources.worker.maintenance.disable': 'Exit Maintenance Mode',
+71 -1
View File
@@ -17,9 +17,15 @@ export default {
'usage.table.user.apiKeysUsed': 'API Keys Used', 'usage.table.user.apiKeysUsed': 'API Keys Used',
'usage.table.lastActive': 'Last Active', 'usage.table.lastActive': 'Last Active',
'usage.filter.granularity': 'Granularity', 'usage.filter.granularity': 'Granularity',
'usage.filter.granularity.hour': 'Час',
'usage.filter.granularity.day': 'Day', 'usage.filter.granularity.day': 'Day',
'usage.filter.granularity.week': 'Week', 'usage.filter.granularity.week': 'Week',
'usage.filter.granularity.month': 'Month', 'usage.filter.granularity.month': 'Month',
'usage.tabs.summary': 'Обзор',
'usage.tabs.tokens': 'Токены',
'usage.tabs.gpuInstances': 'GPU-инстансы',
'usage.tabs.storage': 'Хранилище',
'usage.tabs.resourceEvents': 'События ресурсов',
'usage.tabs.models': 'Models', 'usage.tabs.models': 'Models',
'usage.tabs.apikeys': 'API Keys', 'usage.tabs.apikeys': 'API Keys',
'usage.tabs.users': 'User', 'usage.tabs.users': 'User',
@@ -32,5 +38,69 @@ export default {
'usage.chart.cached': 'Cached', 'usage.chart.cached': 'Cached',
'usage.chart.uncached': 'Uncached', 'usage.chart.uncached': 'Uncached',
'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)', 'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)',
'usage.table.inputTokensCached': 'Input Tokens Cached' 'usage.table.inputTokensCached': 'Input Tokens Cached',
// --- Resource usage: shared metrics & units ---
'usage.metric.tokens': 'Токены',
'usage.metric.input': 'Вход',
'usage.metric.output': 'Выход',
'usage.metric.gpuHours': 'GPU-часы',
'usage.metric.instanceHours': 'Часы инстансов',
'usage.metric.gbDays': 'ГБ-дни',
'usage.metric.gbHours': 'ГБ-часы',
'usage.metric.activeUsers': 'Активные пользователи',
'usage.metric.activeInstances': 'Активные инстансы',
'usage.metric.activeStorage': 'Активное хранилище',
'usage.metric.activeVolumes': 'Активные тома',
'usage.metric.storageTypes': 'Типы хранилищ',
'usage.metric.gpuHours.tip':
'Время работы инстанса, взвешенное по количеству GPU: инстанс с N GPU, работающий H часов, считается как N × H GPU-часов. Равно часам инстансов, когда каждый инстанс использует один GPU.',
'usage.metric.instanceHours.tip':
'Суммарное время работы всех инстансов независимо от количества используемых GPU. Один инстанс, работающий 2 часа = 2 часа инстанса.',
'usage.metric.gbDays.tip':
'Ёмкость хранилища, проинтегрированная по времени, в ГБ × дни: 10 ГБ в течение 5 дней = 50 ГБ-дней. (= ГБ-часы ÷ 24)',
'usage.metric.gbHours.tip':
'Ёмкость хранилища, проинтегрированная по времени, в ГБ × часы: 10 ГБ в течение 5 часов = 50 ГБ-часов.',
// --- Resource usage: common table / labels ---
'usage.common.noData': 'Нет данных',
'usage.common.unknown': 'неизвестно',
'usage.table.date': 'Дата',
'usage.table.name': 'Имя',
'usage.table.user': 'Пользователь',
'usage.table.users': 'Пользователи',
'usage.table.type': 'Тип',
'usage.table.instance': 'Инстанс',
'usage.table.instanceType': 'Тип инстанса',
'usage.table.instanceTypes': 'Типы инстансов',
'usage.table.instances': 'Инстансы',
'usage.table.capacity': 'Ёмкость',
'usage.export.tableNamed': 'Экспорт данных таблицы — {name}',
// --- Summary tab ---
'usage.summary.compute': 'Вычисления',
'usage.summary.tokensOverTime': 'Токены по времени',
'usage.summary.gpuHoursOverTime': 'GPU-часы по времени',
'usage.summary.gbDaysOverTime': 'ГБ-дни по времени',
// --- GPU Instances / Storage filters ---
'usage.filter.instance': 'Фильтр по инстансу',
'usage.filter.storage': 'Фильтр по хранилищу',
// --- Resource events ---
'usage.events.resourceType': 'Тип ресурса',
'usage.events.eventType': 'Тип события',
'usage.events.col.time': 'Время',
'usage.events.col.resource': 'Ресурс',
'usage.events.col.event': 'Событие',
'usage.events.col.message': 'Сообщение',
'usage.events.resource.gpuInstance': 'GPU-инстанс',
'usage.events.resource.cpuInstance': 'CPU-инстанс',
'usage.events.type.created': 'Создан',
'usage.events.type.deleted': 'Удалён',
'usage.events.type.started': 'Запущен',
'usage.events.type.stopped': 'Остановлен',
'usage.events.type.updated': 'Обновлён',
'usage.events.type.attached': 'Подключён',
'usage.events.type.detached': 'Отключён'
}; };
+1 -1
View File
@@ -4,7 +4,7 @@ export default {
'vendor.hygon': 'Hygon', 'vendor.hygon': 'Hygon',
'vendor.moorthreads': 'Moore Threads', 'vendor.moorthreads': 'Moore Threads',
'vendor.iluvatar': 'Iluvatar', 'vendor.iluvatar': 'Iluvatar',
'vendor.metax': 'Metax', 'vendor.metax': 'MetaX',
'vendor.cambricon': 'Cambricon', 'vendor.cambricon': 'Cambricon',
'vendor.thead': 'T-Head PPU' 'vendor.thead': 'T-Head PPU'
}; };
+3 -1
View File
@@ -23,5 +23,7 @@ export default {
'apikeys.accessScope.inference': 'Inference APIs', 'apikeys.accessScope.inference': 'Inference APIs',
'apikeys.access.permissions': 'Access Permissions', 'apikeys.access.permissions': 'Access Permissions',
'apikeys.type.auto': 'Auto-generated', 'apikeys.type.auto': 'Auto-generated',
'apikeys.type.custom': 'Custom' 'apikeys.type.custom': 'Custom',
'apikeys.button.ipConfig': 'IP Access Control',
'quotaLimits.button.title': 'Quota Limit'
}; };
+7
View File
@@ -23,6 +23,13 @@ export default {
'backend.form.defaultExecuteCommand': 'Varsayılan Çalıştırma Komutu', 'backend.form.defaultExecuteCommand': 'Varsayılan Çalıştırma Komutu',
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' ve '{{'model_name'}}' dağıtım sırasında gerçek değerlerle değiştirilecek yer tutuculardır.`, 'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' ve '{{'model_name'}}' dağıtım sırasında gerçek değerlerle değiştirilecek yer tutuculardır.`,
'backend.form.defaultBackendParameters': 'Varsayılan Altyapı Parametreleri', 'backend.form.defaultBackendParameters': 'Varsayılan Altyapı Parametreleri',
'backend.form.parameterFormat': 'Parametre Biçimi',
'backend.form.parameterFormat.default': 'Varsayılan',
'backend.form.parameterFormat.space': 'Boşluk (--key value)',
'backend.form.parameterFormat.equal': 'Eşittir (--key=value)',
'backend.form.commonParameters': 'Yaygın Parametreler',
'backend.form.commonParameters.tips':
'Dağıtım sırasında altyapı parametreleri girişinde öneri olarak gösterilir.',
'backend.form.versionConfig': 'Sürüm Yapılandırması', 'backend.form.versionConfig': 'Sürüm Yapılandırması',
'backend.form.addParameter': 'Parametre Ekle', 'backend.form.addParameter': 'Parametre Ekle',
'backend.form.noVersion': 'Sürüm eklenmedi', 'backend.form.noVersion': 'Sürüm eklenmedi',
+37 -2
View File
@@ -39,9 +39,11 @@ export default {
'clusters.workerpool.batchSize.desc': 'clusters.workerpool.batchSize.desc':
'İşçi havuzunda eşzamanlı olarak oluşturulan işçi düğüm sayısı', 'İşçi havuzunda eşzamanlı olarak oluşturulan işçi düğüm sayısı',
'clusters.create.addworker.tips': 'clusters.create.addworker.tips':
'Aşağıdaki komutu çalıştırmadan önce lütfen {label} için <a href={link} target="_blank">ön koşulların</a> karşılandığından emin olun.', 'Aşağıdaki komutu çalıştırmadan önce lütfen <a href={link} target="_blank">ön koşulların</a> karşılandığından emin olun.',
'clusters.create.addCommand.tips': 'clusters.create.addCommand.tips':
'Eklenmesi gereken İşçi Düğümde, kümeye katılması için aşağıdaki komutu çalıştırın.', 'Eklenmesi gereken İşçi Düğümde, kümeye katılması için aşağıdaki komutu çalıştırın.',
'clusters.create.addCommand.k8s.tips':
'Kaydedilmesi gereken Kubernetes kümesinde, Kubernetes kaynaklarını oluşturmak ve kümeyi kaydetmek için aşağıdaki komutu çalıştırın.',
'clusters.create.register.tips': 'clusters.create.register.tips':
'Eklenmesi gereken Kubernetes kümesinde, düğümlerini kümeye katılması için aşağıdaki komutu çalıştırın.', 'Eklenmesi gereken Kubernetes kümesinde, düğümlerini kümeye katılması için aşağıdaki komutu çalıştırın.',
'cluster.create.checkEnv.tips': 'cluster.create.checkEnv.tips':
@@ -67,6 +69,7 @@ export default {
'clusters.addworker.selectCluster.tips': 'clusters.addworker.selectCluster.tips':
'<span class="bold-text">Docker dışı</span> kümeler için lütfen Kümeler sayfasından küme kaydı oluşturun veya işçi havuzlarını yönetin.', '<span class="bold-text">Docker dışı</span> kümeler için lütfen Kümeler sayfasından küme kaydı oluşturun veya işçi havuzlarını yönetin.',
'clusters.addworker.selectGPU': 'GPU Üreticisi Seç', 'clusters.addworker.selectGPU': 'GPU Üreticisi Seç',
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
'clusters.addworker.checkEnv': 'Ortamı Kontrol Et', 'clusters.addworker.checkEnv': 'Ortamı Kontrol Et',
'clusters.addworker.specifyArgs': 'Argümanları Belirle', 'clusters.addworker.specifyArgs': 'Argümanları Belirle',
'clusters.addworker.runCommand': 'Komutu Çalıştır', 'clusters.addworker.runCommand': 'Komutu Çalıştır',
@@ -107,6 +110,10 @@ export default {
'{count} yeni işçi düğüm kümeye eklendi.', '{count} yeni işçi düğüm kümeye eklendi.',
'clusters.create.serverUrl': "GPUStack Sunucu URL'si", 'clusters.create.serverUrl': "GPUStack Sunucu URL'si",
'clusters.create.workerConfig': 'İşçi Düğüm Yapılandırması', 'clusters.create.workerConfig': 'İşçi Düğüm Yapılandırması',
'clusters.edit.k8sOptions.changed.tip':
'Kubernetes seçeneklerini değiştirdiniz. Değişikliklerin etkili olması için kayıt komutunu hedef kümede yeniden çalıştırın.',
'clusters.edit.workerConfig.tip':
'İşçi düğüm yapılandırmasındaki değişiklikler yalnızca ilgili işçi düğümleri yeniden başlatıldıktan sonra etkili olur.',
'clusters.addworker.containerName': 'İşçi Düğüm Konteyner Adı', 'clusters.addworker.containerName': 'İşçi Düğüm Konteyner Adı',
'clusters.addworker.containerName.tips': 'clusters.addworker.containerName.tips':
'İşçi düğüm konteyneri için bir ad belirtin.', 'İşçi düğüm konteyneri için bir ad belirtin.',
@@ -159,5 +166,33 @@ export default {
'clusters.volume.pvc.readOnly': 'Read Only', 'clusters.volume.pvc.readOnly': 'Read Only',
'clusters.volume.configMap.name': 'ConfigMap Name', 'clusters.volume.configMap.name': 'ConfigMap Name',
'clusters.volume.configMap.optional': 'Optional', 'clusters.volume.configMap.optional': 'Optional',
'clusters.volume.add': 'Add Volume Mount' 'clusters.volume.add': 'Add Volume Mount',
'clusters.systemDefaultContainerRegistry.title': 'Default Container Registry',
'clusters.systemDefaultContainerRegistry.tip':
'Default registry used to resolve GPUStack images for this cluster. Falls back to the server default when unset.',
'clusters.k8sOptions.title': 'Kubernetes Deployment Options',
'clusters.imageCredentials.title': 'Image Credentials',
'clusters.imageCredentials.add': 'Add Credential',
'clusters.imageCredentials.registry': 'Registry',
'clusters.imageCredentials.username': 'Username',
'clusters.imageCredentials.password': 'Password',
'clusters.nodeSelector.title': 'Node Selector',
'clusters.nodeSelector.tip':
'Pod nodeSelector applied to every worker DaemonSet — only nodes whose labels match are eligible to run the worker.',
'clusters.operatorImage.title': 'Operator Image',
'clusters.operatorImage.tip':
'Override for the GPUStack Operator container image. Leave empty to use the server default.',
'clusters.namespace.title': 'Namespace',
'clusters.namespace.tip':
'Kubernetes namespace the clusters manifests render into. Leave empty to use gpustack-system.',
'clusters.clusterType.title': 'Cluster Type',
'clusters.modelService.title': 'Model Service',
'clusters.modelService.tip':
'For LLM inference and API serving — e.g. exposing model APIs and token-based services.',
'clusters.gpuInstances.title': 'GPU Service',
'clusters.gpuInstances.tip':
'For on-demand GPU compute — e.g. interactive development, training jobs, or custom environments.',
'clusters.gpuInstances.staticAddress': 'GPU Service Static Access Address',
'clusters.gpuInstances.staticAddress.tip':
'Static address the operator uses to access GPU instances in this cluster (e.g. a LoadBalancer VIP). Optional.'
}; };
+11 -2
View File
@@ -46,22 +46,28 @@ export default {
'common.button.enabled': 'Etkin', 'common.button.enabled': 'Etkin',
'common.button.disabled': 'Devre dışı', 'common.button.disabled': 'Devre dışı',
'common.button.upgrade': 'Yükselt', 'common.button.upgrade': 'Yükselt',
'common.enterprise.feature': 'Available in GPUStack Enterprise',
'common.input.holder': 'Lütfen girin', 'common.input.holder': 'Lütfen girin',
'common.validate.value': '{name} değeri gereklidir', 'common.validate.value': '{name} değeri gereklidir',
'common.button.edit': 'Düzenle', 'common.button.edit': 'Düzenle',
'common.button.authorize': 'Rol Yetkilendirme', 'common.button.authorize': 'Rol Yetkilendirme',
'common.button.confirm': 'Onayla', 'common.button.confirm': 'Onayla',
'common.button.viewlog': 'Günlükleri Görüntüle', 'common.button.viewlog': 'Günlükleri Görüntüle',
'common.button.viewevent': 'Olayları Görüntüle',
'common.button.recreate': 'Yeniden Oluştur',
'common.table.operation': 'İşlemler', 'common.table.operation': 'İşlemler',
'common.table.creator': 'Oluşturan',
'common.table.createTime': 'Oluşturulma', 'common.table.createTime': 'Oluşturulma',
'common.table.updateTime': 'Güncellenme', 'common.table.updateTime': 'Güncellenme',
'common.table.description': 'Açıklama', 'common.table.description': 'Açıklama',
'common.table.displayName': 'Görünen Ad',
'common.table.name': 'Ad', 'common.table.name': 'Ad',
'common.table.status': 'Durum', 'common.table.status': 'Durum',
'common.table.name.list': '{type} Adı', 'common.table.name.list': '{type} Adı',
'common.search.name.placeholder': 'ada göre filtrele', 'common.search.name.placeholder': 'ada göre filtrele',
'common.search.id.placeholder': 'kimliğe göre filtrele', 'common.search.id.placeholder': 'kimliğe göre filtrele',
'common.filter.byId': 'kimliğe göre filtrele', 'common.filter.byId': 'kimliğe göre filtrele',
'common.filter.byCreator': 'Oluşturana göre filtrele',
'common.table.type': 'Tür', 'common.table.type': 'Tür',
'common.table.default': 'Varsayılan Değer', 'common.table.default': 'Varsayılan Değer',
'common.copy.success': 'Kopyalama başarılı!', 'common.copy.success': 'Kopyalama başarılı!',
@@ -226,7 +232,6 @@ export default {
'common.text.latest': 'En Son', 'common.text.latest': 'En Son',
'common.text.new': 'Yeni', 'common.text.new': 'Yeni',
'common.text.changelog': 'Sürüm Notları', 'common.text.changelog': 'Sürüm Notları',
'common.button.recreate': 'Yeniden oluştur',
'common.button.delrecreate': 'Sil (Yeniden oluştur)', 'common.button.delrecreate': 'Sil (Yeniden oluştur)',
'common.options.all': 'Tümü', 'common.options.all': 'Tümü',
'common.options.none': 'Hiçbiri', 'common.options.none': 'Hiçbiri',
@@ -284,5 +289,9 @@ export default {
'common.file.format.limit': 'Invalid file format. Allowed: {formats}.', 'common.file.format.limit': 'Invalid file format. Allowed: {formats}.',
'common.image.limit.width': 'Image width must be {width}.', 'common.image.limit.width': 'Image width must be {width}.',
'common.image.limit.height': 'Image height must be {height}.', 'common.image.limit.height': 'Image height must be {height}.',
'common.max': 'Maks. {count}' 'common.remaining': 'Kalan {count}',
'common.max': 'Maks. {count}',
'common.max.count': '{label} Sayısı',
'common.validate.group': 'Please complete the {group} configuration',
'common.preferences': 'Tercihler'
}; };
+5 -15
View File
@@ -1,33 +1,23 @@
export default { export default {
'dashboard.title': 'Gösterge Paneli',
'dashboard.workers': 'İşçi Düğümler', 'dashboard.workers': 'İşçi Düğümler',
'dashboard.models': 'Modeller', 'dashboard.deployments': 'Deployments',
'dashboard.clusters': 'Kümeler', 'dashboard.clusters': 'Kümeler',
'dashboard.totalgpus': "GPU'lar", 'dashboard.totalgpus': "GPU'lar",
'dashboard.allocategpus': "Ayrılan GPU'lar",
'dashboard.instances': 'Örnekler',
'dashboard.systemload': 'Sistem Yükü', 'dashboard.systemload': 'Sistem Yükü',
'dashboard.memory': 'RAM', 'dashboard.memory': 'RAM',
'dashboard.disk': 'Depolama',
'dashboard.vram': 'VRAM', 'dashboard.vram': 'VRAM',
'dashboard.cpuutilization': 'Ortalama CPU Kullanımı', 'dashboard.cpuutilization': 'Ortalama CPU Kullanımı',
'dashboard.memoryutilization': 'Ortalama RAM Kullanımı', 'dashboard.memoryutilization': 'Ortalama RAM Kullanımı',
'dashboard.diskutilization': 'Depolama Kullanımı',
'dashboard.vramutilization': 'Ortalama VRAM Kullanımı', 'dashboard.vramutilization': 'Ortalama VRAM Kullanımı',
'dashboard.gpuutilization': 'Ortalama GPU Kullanımı', 'dashboard.gpuutilization': 'Ortalama GPU Kullanımı',
'dashboard.usage': 'Kullanım', 'dashboard.usage': 'Kullanım',
'dashboard.apirequest': 'API İstekleri', 'dashboard.usage.title': 'Son {days} günün kullanımı',
'dashboard.usage.others': 'Diğer',
'dashboard.tokens': 'Token Kullanımı', 'dashboard.tokens': 'Token Kullanımı',
'dashboard.topusers': 'En Aktif Kullanıcılar', 'dashboard.topusers': 'En Aktif Kullanıcılar',
'dashboard.activeModels': 'Aktif Modeller', 'dashboard.activeDeployments': 'Active Deployments',
'dashboard.activeUsers': 'Aktif Kullanıcılar', 'dashboard.usageByModel': 'Modele Göre Kullanım',
'dashboard.tokenUsageByModel': 'Modele Göre Token Kullanımı',
'dashboard.apiRequestsByModel': 'Modele Göre API İstekleri',
'dashboard.topTokenUsageByUser': 'Kullanıcıya Göre İlk 10 Token Kullanımı', 'dashboard.topTokenUsageByUser': 'Kullanıcıya Göre İlk 10 Token Kullanımı',
'dashboard.topTokenUsageByApiKey':
'API Anahtarına Göre İlk 10 Token Kullanımı',
'dashboard.runninginstances': 'Çalışan Örnekler',
'dashboard.activeModels.name': 'Model Adı',
'dashboard.allocatevram': 'Ayrılan VRAM / RAM', 'dashboard.allocatevram': 'Ayrılan VRAM / RAM',
'dashboard.usage.selectuser': 'Kullanıcı seçin', 'dashboard.usage.selectuser': 'Kullanıcı seçin',
'dashboard.usage.selectmodel': 'Model seçin', 'dashboard.usage.selectmodel': 'Model seçin',
+100 -10
View File
@@ -13,12 +13,26 @@ export default {
'gpuservice.template.command.placeholder': 'gpuservice.template.command.placeholder':
'Argümanları boşlukla ayırın; boşluk içeren argümanları tırnak içine alın, örn.: /bin/bash -c "echo hello world"', 'Argümanları boşlukla ayırın; boşluk içeren argümanları tırnak içine alın, örn.: /bin/bash -c "echo hello world"',
'gpuservice.template.mountPath': 'Bağlama Yolu', 'gpuservice.template.mountPath': 'Bağlama Yolu',
'gpuservice.template.mountPath.tips':
'Bu şablondan bir örnek oluşturulurken depolama biriminin varsayılan olarak bağlanacağı yol. Örnek çalışırken saklanması gereken verileri kalıcı hale getirmek için kullanılabilir.',
'gpuservice.template.containerDisk': 'Konteyner Diski (GB)', 'gpuservice.template.containerDisk': 'Konteyner Diski (GB)',
'gpuservice.template.containerDisk.tips': 'Konteyner sistem diskinin boyutu.',
'gpuservice.template.memory': 'Bellek (GB)', 'gpuservice.template.memory': 'Bellek (GB)',
'gpuservice.instance.containerDisk.remaining':
'Konteyner Diski (Maks. {count} GB)',
'gpuservice.instance.memory.remaining': 'Bellek (Maks. {count} GB)',
'gpuservice.template.displayName': 'Görünen Ad',
'gpuservice.template.displayName.max':
'Görünen ad 63 karakterden uzun olamaz.',
'gpuservice.template.ports': 'Bağlantı Noktaları', 'gpuservice.template.ports': 'Bağlantı Noktaları',
'gpuservice.template.ports.add': 'Bağlantı Noktası Ekle', 'gpuservice.template.ports.add': 'Bağlantı Noktası Ekle',
'gpuservice.template.ports.invalid': 'gpuservice.template.ports.invalid':
'Bağlantı noktası yapılandırmasını eksiksiz doldurun.', 'Bağlantı noktası yapılandırmasını eksiksiz doldurun.',
'gpuservice.template.ports.name': 'Ad',
'gpuservice.template.ports.name.max':
'Bağlantı noktası adı 16 karakterden uzun olamaz.',
'gpuservice.template.ports.name.duplicate':
'Bağlantı noktası adları benzersiz olmalıdır.',
'gpuservice.template.env': 'Ortam Değişkenleri', 'gpuservice.template.env': 'Ortam Değişkenleri',
'gpuservice.template.env.add': 'Ortam Değişkeni Ekle', 'gpuservice.template.env.add': 'Ortam Değişkeni Ekle',
'gpuservice.template.env.invalid': 'Ortam değişkenlerini eksiksiz doldurun.', 'gpuservice.template.env.invalid': 'Ortam değişkenlerini eksiksiz doldurun.',
@@ -28,7 +42,49 @@ export default {
'gpuservice.template.card.mount': 'Bağlama', 'gpuservice.template.card.mount': 'Bağlama',
'gpuservice.template.card.resources': 'Kaynaklar', 'gpuservice.template.card.resources': 'Kaynaklar',
'gpuservice.template.card.ports': 'Bağlantı Noktaları', 'gpuservice.template.card.ports': 'Bağlantı Noktaları',
'gpuservice.storageType': 'Depolama Türü',
'gpuservice.storageType.add': 'Depolama Türü Ekle',
'gpuservice.storageType.edit': 'Depolama Türünü Düzenle',
'gpuservice.storageType.filter.name': 'Ada göre ara',
'gpuservice.storageType.kind': 'Tür',
'gpuservice.storageType.mountOptions': 'Bağlama Seçenekleri',
'gpuservice.storageType.nfs.server': 'NFS Sunucusu',
'gpuservice.storageType.nfs.server.tips':
'NFS sunucu adresinin tüm Kubernetes kümelerinden erişilebilir olduğundan emin olun.',
'gpuservice.storageType.nfs.share': 'Paylaşım Yolu',
'gpuservice.storageType.nfs.share.tips':
'Bu paylaşım yolu altında organizasyon ve depolama adlarına dayalı bir dizin otomatik olarak oluşturulur. Bir alt dizin belirtilmişse, oluşturulan dizin o alt dizin altında yer alır.',
'gpuservice.storageType.nfs.subDirectory': 'Alt Dizin',
'gpuservice.storageType.nfs.subDirectory.tips':
'Boş bırakılırsa kalıcı hacim adıyla bir alt dizin oluşturulur. Doldurulursa bu alt dizinin altında kalıcı hacim adıyla bir dizin oluşturulur.',
'gpuservice.storageType.nfs.mountPermissions': 'Bağlama İzinleri',
'gpuservice.storageType.nfs.mountPermissions.tips':
'NFS sunucusundaki dosya izinleri devralınır.',
'gpuservice.storageType.s3.endpoint': 'Endpoint',
'gpuservice.storageType.s3.endpoint.tips':
'S3 endpoint adresinin tüm Kubernetes kümelerinden erişilebilir olduğundan emin olun.',
'gpuservice.storageType.s3.endpoint.rule': 'http veya https ile başlamalıdır',
'gpuservice.storageType.s3.region': 'Bölge',
'gpuservice.storageType.s3.bucket': 'Kova',
'gpuservice.storageType.s3.bucket.tips':
'Boş bırakılırsa kalıcı hacim adıyla yeni bir kova oluşturulur. Doldurulursa bu kova içinde kalıcı hacim adıyla bir alt dizin oluşturulur.',
'gpuservice.storageType.s3.bucket.tips1':
'Bu kova içinde organizasyon ve depolama adlarına dayalı bir önek dizini otomatik olarak oluşturulur.',
'gpuservice.storageType.s3.bucket.tips2':
'Örneğin, organizasyon adı <span class="desc-block">awesome-group</span> ve depolama adı <span class="desc-block">storage-1</span> ise, oluşacak önek: <span class="desc-block">awesome-group/storage-1</span>.',
'gpuservice.storageType.s3.accessKey': 'Access Key',
'gpuservice.storageType.s3.secretKey': 'Secret Key',
'gpuservice.storageType.s3.insecure': 'TLS/SSL sertifika doğrulamasını atla',
'gpuservice.storageType.s3.insecure.tips':
'Etkinleştirildiğinde S3 sunucu sertifikası doğrulanmaz. İç ağ testleri veya kendinden imzalı sertifikalar için uygundur; üretim ortamında dikkatli kullanın.',
'gpuservice.publicKey': 'SSH Açık Anahtarı',
'gpuservice.publicKey.add': 'SSH Açık Anahtarı Ekle',
'gpuservice.publicKey.edit': 'SSH Açık Anahtarını Düzenle',
'gpuservice.publicKey.filter.name': 'Ada göre ara',
'gpuservice.publicKey.label': 'SSH Açık Anahtarı', 'gpuservice.publicKey.label': 'SSH Açık Anahtarı',
'gpuservice.instance.ssh.enable': 'SSH Erişimini Etkinleştir',
'gpuservice.instance.ssh.assignKey': 'SSH Açık Anahtarı Ata',
'gpuservice.instance.ssh.addKey': 'SSH Açık Anahtarı Ekle',
'gpuservice.publicKey.placeholder': 'gpuservice.publicKey.placeholder':
'ssh-rsa veya ssh-ed25519 ile başlar, her açık anahtar ayrı bir satırda\n\nAçık anahtarı görüntüle:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub', 'ssh-rsa veya ssh-ed25519 ile başlar, her açık anahtar ayrı bir satırda\n\nAçık anahtarı görüntüle:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub',
'gpuservice.instance': 'GPU Örneği', 'gpuservice.instance': 'GPU Örneği',
@@ -44,22 +100,45 @@ export default {
'gpuservice.instance.templates': 'Örnek Şablonları', 'gpuservice.instance.templates': 'Örnek Şablonları',
'gpuservice.instance.section.storage': 'Depolama Hacmi', 'gpuservice.instance.section.storage': 'Depolama Hacmi',
'gpuservice.instance.type.required': 'Lütfen bir örnek türü seçin', 'gpuservice.instance.type.required': 'Lütfen bir örnek türü seçin',
'gpuservice.instance.type.noAvailable': 'Kullanılabilir örnek türü yok',
'gpuservice.instance.gpuCount': 'GPU Sayısı', 'gpuservice.instance.gpuCount': 'GPU Sayısı',
'gpuservice.instance.gpuCount.required': 'Lütfen GPU sayısını girin', 'gpuservice.instance.gpuCount.required': 'Lütfen GPU sayısını girin',
'gpuservice.instance.gpuCount.max': 'gpuservice.instance.gpuCount.max': 'En fazla {count} GPU kartı seçin',
'Mevcut örnek türü en fazla {count} GPU destekler', 'gpuservice.instance.gpuCount.min': 'En az {count} GPU kartı seçin',
'gpuservice.instance.cpuCount.max': 'En fazla {count} CPU çekirdeği seçin',
'gpuservice.instance.cpuCount.min': 'En az {count} CPU çekirdeği seçin',
'gpuservice.instance.gpuCount.noAvailable':
'Kullanılabilir GPU kaynağı yok, lütfen başka bir örnek türü seçin.',
'gpuservice.instance.gpuCount.zero': 'Yalnızca CPU, ortam hazırlığı için.',
'gpuservice.instance.stock': 'Stok', 'gpuservice.instance.stock': 'Stok',
'gpuservice.instance.sliced': 'Bölünmüş', 'gpuservice.instance.sliced': 'Bölünmüş',
'gpuservice.instance.memory': 'Bellek', 'gpuservice.instance.memory': 'VRAM',
'gpuservice.instance.ram': 'RAM', 'gpuservice.instance.ram': 'RAM',
'gpuservice.instance.search.type.placeholder': 'gpuservice.instance.disk': 'Disk',
"Ada, VRAM, belleğe veya vCPU'ya göre ara", 'gpuservice.table.count': 'Sayı',
'gpuservice.instance.disk.system': 'Sistem Diski',
'gpuservice.instance.disk.ephemeral': 'Geçici Depolama',
'gpuservice.instance.disk.persistent': 'Kalıcı Depolama',
'gpuservice.instance.search.type.placeholder': 'Ada göre ara',
'gpuservice.instance.search.template.placeholder': 'gpuservice.instance.search.template.placeholder':
'Şablon adına, imaja veya bağlama yoluna göre ara', 'Şablon adına, imaja veya bağlama yoluna göre ara',
'gpuservice.instance.template.image': 'İmaj', 'gpuservice.instance.template.image': 'İmaj',
'gpuservice.instance.template.mount': 'Bağlama', 'gpuservice.instance.template.mount': 'Bağlama',
'gpuservice.instance.connect': 'Bağlan', 'gpuservice.instance.connect': 'Bağlan',
'gpuservice.instance.connect.copySshCommand': 'SSH Komutunu Kopyala', 'gpuservice.instance.connect.copySshCommand': 'SSH Komutunu Kopyala',
'gpuservice.instance.event.reason': 'Neden',
'gpuservice.instance.event.message': 'Mesaj',
'gpuservice.instance.event.source': 'Kaynak',
'gpuservice.instance.event.count': 'Sayı',
'gpuservice.instance.event.lastSeen': 'Son Görülen',
'gpuservice.instance.event.recentHourTip':
'Yalnızca son bir saatteki olaylar gösterilir',
'gpuservice.instance.event.tab.instance': 'Örnek Olayları',
'gpuservice.instance.event.tab.volume': 'Birim Olayları',
'gpuservice.instance.recreate.confirm.title':
'Yeniden oluşturma onaylansın mı',
'gpuservice.instance.recreate.confirm.content':
'Mevcut örnek önce silinecek, ardından mevcut yapılandırmayla yeniden oluşturulacaktır.\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
'gpuservice.storage': 'Depolama', 'gpuservice.storage': 'Depolama',
'gpuservice.storage.add': 'Depolama Ekle', 'gpuservice.storage.add': 'Depolama Ekle',
'gpuservice.storage.edit': 'Depolamayı Düzenle', 'gpuservice.storage.edit': 'Depolamayı Düzenle',
@@ -72,10 +151,21 @@ export default {
'gpuservice.storage.accessMode': 'Erişim Modu', 'gpuservice.storage.accessMode': 'Erişim Modu',
'gpuservice.storage.persistent': 'Kalıcı', 'gpuservice.storage.persistent': 'Kalıcı',
'gpuservice.storage.temporary': 'Geçici', 'gpuservice.storage.temporary': 'Geçici',
'gpuservice.storage.persistentVolume': 'Kalıcı Hacim', 'gpuservice.storage.persistentVolume': 'Kalıcı',
'gpuservice.storage.persistentVolume.required': 'gpuservice.storage.persistentVolume.required': 'Lütfen bir depolama seçin',
'Lütfen bir kalıcı hacim seçin', 'gpuservice.storage.persistentVolume.capacity': 'Kapasite (GB)',
'gpuservice.storage.tempCapacity': 'Depolama Kapasitesi (GB)', 'gpuservice.storage.persistentVolume.capacity.required':
'Lütfen kapasiteyi girin',
'gpuservice.storage.persistentVolume.releaseWithInstance':
'Örnekle birlikte serbest bırak',
'gpuservice.storage.tempCapacity': 'Kapasite (GB)',
'gpuservice.storage.tempCapacity.required': 'gpuservice.storage.tempCapacity.required':
'Lütfen yerel geçici depolama kapasitesini girin' 'Lütfen geçici depolama kapasitesini girin',
'gpuservice.form.rule.name':
"Küçük harfler, rakamlar ve '-'. Harf veya rakamla başlamalı ve bitmeli, ardışık '-' içermemeli, en fazla 63 karakter.",
'gpuservice.storage.temporary.tips':
'Data is cleared when the instance stops.',
'gpuservice.storage.persistentVolume.tips':
'Data persists across instance restarts. Persistent volumes remain intact after instance termination and can be shared by multiple instances.',
'gpuservice.form.storage.select': 'Depolamayı Seç'
}; };
+13 -9
View File
@@ -8,7 +8,7 @@ export default {
'menu.playground.text2images': 'Görsel', 'menu.playground.text2images': 'Görsel',
'menu.playground.video': 'Video', 'menu.playground.video': 'Video',
'menu.compare': 'Karşılaştır', 'menu.compare': 'Karşılaştır',
'menu.models': 'Modeller', 'menu.models': 'Model Hizmetleri',
'menu.models.modelList': 'Dağıt ve Yönet', 'menu.models.modelList': 'Dağıt ve Yönet',
'menu.models.modelCatalog': 'Katalog', 'menu.models.modelCatalog': 'Katalog',
'menu.models.catalog': 'Model Kataloğu', 'menu.models.catalog': 'Model Kataloğu',
@@ -25,26 +25,30 @@ export default {
'menu.users': 'Kullanıcılar', 'menu.users': 'Kullanıcılar',
'menu.resources.workers': 'İşçi Düğümler', 'menu.resources.workers': 'İşçi Düğümler',
'menu.resources.gpus': "GPU'lar", 'menu.resources.gpus': "GPU'lar",
'menu.resources.modelfiles': 'Model Dosyaları', 'menu.models.modelfiles': 'Model Dosyaları',
'menu.accessControl': 'Erişim Kontrolü', 'menu.accessControl': 'Erişim Kontrolü',
'menu.accessControl.apikeys': 'API Anahtarları', 'menu.accessControl.apikeys': 'API Anahtarları',
'menu.accessControl.users': 'Kullanıcılar', 'menu.accessControl.users': 'Kullanıcılar',
'menu.profile': 'Profil', 'menu.accessControl.organizations': 'Organizasyonlar',
'menu.profile': 'Preferences',
'menu.login': 'Giriş', 'menu.login': 'Giriş',
'menu.usage': 'Kullanım', 'menu.usage': 'Kullanım',
'menu.usage.usage': 'Kullanım',
'menu.billingAndUsage': 'Kullanım ve Faturalandırma',
'menu.billingAndUsage.usage': 'Kullanım',
'menu.404': '404', 'menu.404': '404',
'menu.clusterManagement': 'Küme Yönetimi', 'menu.resources.clusters': 'Kümeler',
'menu.clusterManagement.clusters': 'Kümeler', 'menu.resources.credentials': 'Bulut Kimlik Bilgileri',
'menu.clusterManagement.credentials': 'Bulut Kimlik Bilgileri', 'menu.resources.clusterDetail': 'Küme Detayı',
'menu.clusterManagement.clusterDetail': 'Küme Detayı', 'menu.resources.clusterCreate': 'Küme Oluştur',
'menu.clusterManagement.clusterCreate': 'Küme Oluştur', 'menu.models.backendsList': 'Çıkarım Altyapıları',
'menu.resources.backendsList': 'Çıkarım Altyapıları',
'menu.models.instances': 'Instances', 'menu.models.instances': 'Instances',
'menu.settings': 'Settings', 'menu.settings': 'Settings',
'menu.gpuService': 'GPU Service', 'menu.gpuService': 'GPU Service',
'menu.gpuService.instances': 'GPU Instances', 'menu.gpuService.instances': 'GPU Instances',
'menu.gpuService.templates': 'Instance Templates', 'menu.gpuService.templates': 'Instance Templates',
'menu.gpuService.storage': 'Storage', 'menu.gpuService.storage': 'Storage',
'menu.gpuService.storageTypes': 'Depolama Türleri',
'menu.gpuService.publicKeys': 'SSH Public Keys' 'menu.gpuService.publicKeys': 'SSH Public Keys'
}; };
+15 -3
View File
@@ -62,7 +62,7 @@ export default {
'models.form.backend': 'Altyapı', 'models.form.backend': 'Altyapı',
'models.form.backend_parameters': 'Altyapı Parametreleri', 'models.form.backend_parameters': 'Altyapı Parametreleri',
'models.instance.params.configured': 'User Configured', 'models.instance.params.configured': 'User Configured',
'models.instance.params.autoInjected': 'Otomatik Enjekte', 'models.instance.params.autoInjected': 'Otomatik Enjekte Edilen Parametreler',
'models.search.gguf.tips': 'models.search.gguf.tips':
'GGUF modelleri llama-box kullanır (Linux, macOS ve Windows destekler).', 'GGUF modelleri llama-box kullanır (Linux, macOS ve Windows destekler).',
'models.search.vllm.tips': 'models.search.vllm.tips':
@@ -290,11 +290,23 @@ export default {
'models.instance.previousRun': 'Previous Run', 'models.instance.previousRun': 'Previous Run',
'models.instance.startHistory': 'Run History', 'models.instance.startHistory': 'Run History',
'models.instance.startHistory.tips': 'models.instance.startHistory.tips':
'Shows logs from the run before the last error-triggered restart.' 'Shows logs from the run before the last error-triggered restart.',
'models.form.lora.label': 'LoRA Adapters',
'models.form.lora.add': 'Add LoRA Adapter',
'models.form.lora.select': 'Select LoRA',
'models.form.lora.name': 'LoRA name',
'models.form.lora.rule.empty': 'Input cannot be empty',
'models.form.lora.rule.duplicate': 'LoRA name cannot be duplicated'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'models.table.modelView': 'Model List', // 1. 'models.table.modelView': 'Model List',
// 2. 'models.table.instanceView': 'Instance List', // 2. 'models.table.instanceView': 'Instance List',
// 3. 'models.table.category': 'Category' // 3. 'models.table.category': 'Category',
// 4. 'models.form.lora.label': 'LoRA Adapter',
// 5. 'models.form.lora.add': 'Add LoRA Adapter',
// 6. 'models.form.lora.select': 'Select LoRA',
// 7. 'models.form.lora.name': 'LoRA name',
// 8. 'models.form.lora.rule.empty': 'Input cannot be empty',
// 9. 'models.form.lora.rule.duplicate': 'LoRA name cannot be duplicated'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+11 -1
View File
@@ -38,9 +38,12 @@ export default {
'noresult.catalog.nofound': 'Eşleşen model bulunamadı.', 'noresult.catalog.nofound': 'Eşleşen model bulunamadı.',
'noresult.resources.cluster': 'noresult.resources.cluster':
'Kullanılabilir küme yok. Başlamak için bir küme ekleyin.', 'Kullanılabilir küme yok. Başlamak için bir küme ekleyin.',
'noresult.resources.k8sCluster':
'Kullanılabilir küme yok. Başlamak için bir Kubernetes kümesi ekleyin.',
'noresult.resources.worker': 'noresult.resources.worker':
'Kullanılabilir işçi düğüm yok. Başlamak için bir işçi düğüm ekleyin.', 'Kullanılabilir işçi düğüm yok. Başlamak için bir işçi düğüm ekleyin.',
'noresult.resources.gotocluster': 'İlk Kümenizi Oluşturun', 'noresult.resources.gotocluster': 'İlk Kümenizi Oluşturun',
'noresult.resources.addk8scluster': 'Kubernetes Kümesi Ekle',
'noresult.resources.gotoworker': 'İşçi Düğüm Ekle', 'noresult.resources.gotoworker': 'İşçi Düğüm Ekle',
'noresult.benchmark.title': 'Kıyaslama Yok', 'noresult.benchmark.title': 'Kıyaslama Yok',
'noresult.benchmark.subTitle': 'Henüz kıyaslama eklenmedi.', 'noresult.benchmark.subTitle': 'Henüz kıyaslama eklenmedi.',
@@ -63,7 +66,14 @@ export default {
'noresult.gpuservice.instance.nofound': 'Eşleşen GPU örneği bulunamadı.', 'noresult.gpuservice.instance.nofound': 'Eşleşen GPU örneği bulunamadı.',
'noresult.gpuservice.storage.title': 'Depolama Yok', 'noresult.gpuservice.storage.title': 'Depolama Yok',
'noresult.gpuservice.storage.subTitle': 'Henüz depolama eklenmedi.', 'noresult.gpuservice.storage.subTitle': 'Henüz depolama eklenmedi.',
'noresult.gpuservice.storage.nofound': 'Eşleşen depolama bulunamadı.' 'noresult.gpuservice.storage.nofound': 'Eşleşen depolama bulunamadı.',
'noresult.gpuservice.storageType.title': 'Depolama Türü Yok',
'noresult.gpuservice.storageType.subTitle': 'Henüz depolama türü eklenmedi.',
'noresult.gpuservice.storageType.nofound':
'Eşleşen depolama türü bulunamadı.',
'noresult.gpuservice.sshkey.title': 'SSH Açık Anahtarı Yok',
'noresult.gpuservice.sshkey.subTitle': 'Henüz SSH açık anahtarı eklenmedi.',
'noresult.gpuservice.sshkey.nofound': 'Eşleşen SSH açık anahtarı bulunamadı.'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
+15
View File
@@ -0,0 +1,15 @@
export default {
'organizations.upsell.title': 'Organizations are an Enterprise feature',
'organizations.upsell.subtitle':
'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to GPUStack Enterprise to manage organizations.',
'organizations.upsell.featuresTitle': 'What you get in Enterprise',
'organizations.upsell.feature.orgs':
'Create organizations to group users and isolate workloads',
'organizations.upsell.feature.members':
'Manage members and roles per organization',
'organizations.upsell.feature.quotas':
'Set resource and token quotas per organization',
'organizations.upsell.feature.isolation':
'Scope API keys, model deployments, and resources per organization',
'organizations.upsell.cta': 'Learn about Enterprise'
};
+1
View File
@@ -98,6 +98,7 @@ export default {
'resources.worker.download.privatekey': 'Özel Anahtarı İndir', 'resources.worker.download.privatekey': 'Özel Anahtarı İndir',
'resources.modelfiles.form.exsting': 'İndirilmiş', 'resources.modelfiles.form.exsting': 'İndirilmiş',
'resources.modelfiles.form.added': 'Eklenmiş', 'resources.modelfiles.form.added': 'Eklenmiş',
'resources.modelfiles.form.isLora': 'Is LoRA',
'resources.worker.maintenance.title': 'Sistem Bakımı', 'resources.worker.maintenance.title': 'Sistem Bakımı',
'resources.worker.maintenance.enable': 'Bakım Moduna Gir', 'resources.worker.maintenance.enable': 'Bakım Moduna Gir',
'resources.worker.maintenance.disable': 'Bakım Modundan Çık', 'resources.worker.maintenance.disable': 'Bakım Modundan Çık',
+71 -1
View File
@@ -17,9 +17,15 @@ export default {
'usage.table.user.apiKeysUsed': 'API Keys Used', 'usage.table.user.apiKeysUsed': 'API Keys Used',
'usage.table.lastActive': 'Last Active', 'usage.table.lastActive': 'Last Active',
'usage.filter.granularity': 'Granularity', 'usage.filter.granularity': 'Granularity',
'usage.filter.granularity.hour': 'Saat',
'usage.filter.granularity.day': 'Day', 'usage.filter.granularity.day': 'Day',
'usage.filter.granularity.week': 'Week', 'usage.filter.granularity.week': 'Week',
'usage.filter.granularity.month': 'Month', 'usage.filter.granularity.month': 'Month',
'usage.tabs.summary': 'Özet',
'usage.tabs.tokens': 'Token',
'usage.tabs.gpuInstances': 'GPU Örnekleri',
'usage.tabs.storage': 'Depolama',
'usage.tabs.resourceEvents': 'Kaynak Olayları',
'usage.tabs.models': 'Models', 'usage.tabs.models': 'Models',
'usage.tabs.apikeys': 'API Keys', 'usage.tabs.apikeys': 'API Keys',
'usage.tabs.users': 'User', 'usage.tabs.users': 'User',
@@ -32,5 +38,69 @@ export default {
'usage.chart.cached': 'Cached', 'usage.chart.cached': 'Cached',
'usage.chart.uncached': 'Uncached', 'usage.chart.uncached': 'Uncached',
'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)', 'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)',
'usage.table.inputTokensCached': 'Input Tokens Cached' 'usage.table.inputTokensCached': 'Input Tokens Cached',
// --- Resource usage: shared metrics & units ---
'usage.metric.tokens': 'Token',
'usage.metric.input': 'Girdi',
'usage.metric.output': 'Çıktı',
'usage.metric.gpuHours': 'GPU Saati',
'usage.metric.instanceHours': 'Örnek Saati',
'usage.metric.gbDays': 'GB-Gün',
'usage.metric.gbHours': 'GB-Saat',
'usage.metric.activeUsers': 'Aktif Kullanıcılar',
'usage.metric.activeInstances': 'Aktif Örnekler',
'usage.metric.activeStorage': 'Aktif Depolama',
'usage.metric.activeVolumes': 'Aktif Birimler',
'usage.metric.storageTypes': 'Depolama Türleri',
'usage.metric.gpuHours.tip':
'Örnek çalışma süresinin GPU sayısına göre ağırlıklandırılmış hali: N GPU kullanan bir örnek H saat çalıştığında N × H GPU-saat olarak sayılır. Her örnek tek bir GPU kullandığında Örnek Saati ile eşittir.',
'usage.metric.instanceHours.tip':
'Her örneğin kullandığı GPU sayısından bağımsız olarak tüm örneklerin toplam çalışma süresi. 2 saat çalışan bir örnek = 2 örnek-saat.',
'usage.metric.gbDays.tip':
'Depolama kapasitesinin zamana göre integrali, GB × gün cinsinden: 5 gün boyunca tutulan 10 GB = 50 GB-gün. (= GB-Saat ÷ 24)',
'usage.metric.gbHours.tip':
'Depolama kapasitesinin zamana göre integrali, GB × saat cinsinden: 5 saat boyunca tutulan 10 GB = 50 GB-saat.',
// --- Resource usage: common table / labels ---
'usage.common.noData': 'Veri yok',
'usage.common.unknown': 'bilinmiyor',
'usage.table.date': 'Tarih',
'usage.table.name': 'Ad',
'usage.table.user': 'Kullanıcı',
'usage.table.users': 'Kullanıcılar',
'usage.table.type': 'Tür',
'usage.table.instance': 'Örnek',
'usage.table.instanceType': 'Örnek Türü',
'usage.table.instanceTypes': 'Örnek Türleri',
'usage.table.instances': 'Örnekler',
'usage.table.capacity': 'Kapasite',
'usage.export.tableNamed': 'Tablo Verilerini Dışa Aktar — {name}',
// --- Summary tab ---
'usage.summary.compute': 'Hesaplama',
'usage.summary.tokensOverTime': 'Zaman içinde Token',
'usage.summary.gpuHoursOverTime': 'Zaman içinde GPU Saati',
'usage.summary.gbDaysOverTime': 'Zaman içinde GB-Gün',
// --- GPU Instances / Storage filters ---
'usage.filter.instance': 'Örneğe göre filtrele',
'usage.filter.storage': 'Depolamaya göre filtrele',
// --- Resource events ---
'usage.events.resourceType': 'Kaynak türü',
'usage.events.eventType': 'Olay türü',
'usage.events.col.time': 'Zaman',
'usage.events.col.resource': 'Kaynak',
'usage.events.col.event': 'Olay',
'usage.events.col.message': 'Mesaj',
'usage.events.resource.gpuInstance': 'GPU Örneği',
'usage.events.resource.cpuInstance': 'CPU Örneği',
'usage.events.type.created': 'Oluşturuldu',
'usage.events.type.deleted': 'Silindi',
'usage.events.type.started': 'Başlatıldı',
'usage.events.type.stopped': 'Durduruldu',
'usage.events.type.updated': 'Güncellendi',
'usage.events.type.attached': 'Eklendi',
'usage.events.type.detached': 'Ayrıldı'
}; };
+1 -1
View File
@@ -4,7 +4,7 @@ export default {
'vendor.hygon': 'Hygon', 'vendor.hygon': 'Hygon',
'vendor.moorthreads': 'Moore Threads', 'vendor.moorthreads': 'Moore Threads',
'vendor.iluvatar': 'Iluvatar', 'vendor.iluvatar': 'Iluvatar',
'vendor.metax': 'Metax', 'vendor.metax': 'MetaX',
'vendor.cambricon': 'Cambricon', 'vendor.cambricon': 'Cambricon',
'vendor.thead': 'T-Head PPU' 'vendor.thead': 'T-Head PPU'
}; };
+3 -1
View File
@@ -22,5 +22,7 @@ export default {
'apikeys.accessScope.inference': '推理接口', 'apikeys.accessScope.inference': '推理接口',
'apikeys.access.permissions': '访问权限', 'apikeys.access.permissions': '访问权限',
'apikeys.type.auto': '自动生成', 'apikeys.type.auto': '自动生成',
'apikeys.type.custom': '自定义' 'apikeys.type.custom': '自定义',
'apikeys.button.ipConfig': 'IP 访问控制',
'quotaLimits.button.title': '配额限制'
}; };
+6
View File
@@ -23,6 +23,12 @@ export default {
'backend.form.defaultExecuteCommand': '默认执行命令', 'backend.form.defaultExecuteCommand': '默认执行命令',
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}'、'{{'port'}}'、'{{'worker_ip'}}' 和 '{{'model_name'}}' 都是占位符,在部署过程中会被替换为实际的值。`, 'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}'、'{{'port'}}'、'{{'worker_ip'}}' 和 '{{'model_name'}}' 都是占位符,在部署过程中会被替换为实际的值。`,
'backend.form.defaultBackendParameters': '默认后端参数', 'backend.form.defaultBackendParameters': '默认后端参数',
'backend.form.parameterFormat': '参数输出格式',
'backend.form.parameterFormat.default': '后端默认',
'backend.form.parameterFormat.space': '空格分隔 (--key value)',
'backend.form.parameterFormat.equal': '等号连接 (--key=value)',
'backend.form.commonParameters': '常用参数',
'backend.form.commonParameters.tips': '部署模型时作为后端参数候选项展示。',
'backend.form.versionConfig': '版本配置', 'backend.form.versionConfig': '版本配置',
'backend.form.addParameter': '添加参数', 'backend.form.addParameter': '添加参数',
'backend.form.noVersion': '未添加版本', 'backend.form.noVersion': '未添加版本',
+37 -2
View File
@@ -38,9 +38,11 @@ export default {
'clusters.create.noRegions': '无可用的区域', 'clusters.create.noRegions': '无可用的区域',
'clusters.workerpool.batchSize.desc': '节点池中同时创建的节点数量。', 'clusters.workerpool.batchSize.desc': '节点池中同时创建的节点数量。',
'clusters.create.addworker.tips': 'clusters.create.addworker.tips':
'在执行以下命令之前,请确保已满足 {label} 的<a href={link} target="_blank">先决条件</a>。', '在执行以下命令之前,请确保已满足<a href={link} target="_blank">先决条件</a>。',
'clusters.create.addCommand.tips': 'clusters.create.addCommand.tips':
'在需要添加的节点上运行以下命令,将其加入到集群中。', '在需要添加的节点上运行以下命令,将其加入到集群中。',
'clusters.create.addCommand.k8s.tips':
'在需要注册的 Kubernetes 集群中运行以下命令,创建 Kubernetes 资源,注册该集群。',
'clusters.create.register.tips': 'clusters.create.register.tips':
'在需要添加的 Kubernetes 集群上运行以下命令,将其中的节点加入到集群中。', '在需要添加的 Kubernetes 集群上运行以下命令,将其中的节点加入到集群中。',
'cluster.create.checkEnv.tips': '使用以下命令检查环境是否准备妥当。', 'cluster.create.checkEnv.tips': '使用以下命令检查环境是否准备妥当。',
@@ -65,6 +67,7 @@ export default {
'clusters.addworker.selectCluster.tips': 'clusters.addworker.selectCluster.tips':
'<span class="bold-text">非 Docker</span> 集群请前往集群页面注册集群或管理节点池。', '<span class="bold-text">非 Docker</span> 集群请前往集群页面注册集群或管理节点池。',
'clusters.addworker.selectGPU': '选择 GPU 厂商', 'clusters.addworker.selectGPU': '选择 GPU 厂商',
'clusters.addworker.selectGPU.multiTag': '可多选',
'clusters.addworker.checkEnv': '检查环境', 'clusters.addworker.checkEnv': '检查环境',
'clusters.addworker.specifyArgs': '指定参数', 'clusters.addworker.specifyArgs': '指定参数',
'clusters.addworker.runCommand': '运行指令', 'clusters.addworker.runCommand': '运行指令',
@@ -105,6 +108,10 @@ export default {
'已将 {count} 个新节点添加到集群中。', '已将 {count} 个新节点添加到集群中。',
'clusters.create.serverUrl': 'GPUStack Server 节点地址', 'clusters.create.serverUrl': 'GPUStack Server 节点地址',
'clusters.create.workerConfig': '节点配置', 'clusters.create.workerConfig': '节点配置',
'clusters.edit.k8sOptions.changed.tip':
'您已修改 Kubernetes 选项,需要在目标集群上重新运行注册命令才会生效。',
'clusters.edit.workerConfig.tip':
'修改节点配置后,需要重启对应节点才会生效。',
'clusters.addworker.containerName': '节点容器名称', 'clusters.addworker.containerName': '节点容器名称',
'clusters.addworker.containerName.tips': '为节点容器指定一个名称。', 'clusters.addworker.containerName.tips': '为节点容器指定一个名称。',
'clusters.addworker.dataVolume': 'GPUStack 数据卷', 'clusters.addworker.dataVolume': 'GPUStack 数据卷',
@@ -151,5 +158,33 @@ export default {
'clusters.volume.pvc.readOnly': '只读', 'clusters.volume.pvc.readOnly': '只读',
'clusters.volume.configMap.name': '配置名称', 'clusters.volume.configMap.name': '配置名称',
'clusters.volume.configMap.optional': '可选', 'clusters.volume.configMap.optional': '可选',
'clusters.volume.add': '添加卷挂载' 'clusters.volume.add': '添加卷挂载',
'clusters.systemDefaultContainerRegistry.title': '默认容器镜像仓库',
'clusters.systemDefaultContainerRegistry.tip':
'用于解析该集群 GPUStack 镜像的默认镜像仓库。未设置时回退到服务端默认值。',
'clusters.k8sOptions.title': 'Kubernetes 部署选项',
'clusters.imageCredentials.title': '镜像仓库凭证',
'clusters.imageCredentials.add': '添加凭证',
'clusters.imageCredentials.registry': '镜像仓库地址',
'clusters.imageCredentials.username': '用户名',
'clusters.imageCredentials.password': '密码',
'clusters.nodeSelector.title': '节点选择器',
'clusters.nodeSelector.tip':
'应用到每个 worker DaemonSet 的 Pod nodeSelector,只有标签匹配的节点才会被调度运行 worker。',
'clusters.operatorImage.title': 'Operator 镜像',
'clusters.operatorImage.tip':
'GPUStack Operator 容器镜像的覆盖值。留空则使用服务端默认值。',
'clusters.namespace.title': '命名空间',
'clusters.namespace.tip':
'集群清单渲染所使用的 Kubernetes 命名空间。留空则使用 gpustack-system。',
'clusters.clusterType.title': '集群类型',
'clusters.modelService.title': '模型服务',
'clusters.modelService.tip':
'适用于大模型推理与 API 服务化场景,例如对外提供模型 API 与 Token 服务能力。',
'clusters.gpuInstances.title': 'GPU 服务',
'clusters.gpuInstances.tip':
'适用于按需分配 GPU 计算资源的场景,例如交互式开发、训练任务或自定义运行环境。',
'clusters.gpuInstances.staticAddress': 'GPU 服务静态访问地址',
'clusters.gpuInstances.staticAddress.tip':
'Operator 访问该集群 GPU 实例所使用的静态地址(例如 LoadBalancer VIP)。可选。'
}; };
+10 -1
View File
@@ -44,16 +44,20 @@ export default {
'common.button.rollback': '回滚', 'common.button.rollback': '回滚',
'common.button.new': '新建{ text }', 'common.button.new': '新建{ text }',
'common.button.upgrade': '升级', 'common.button.upgrade': '升级',
'common.enterprise.feature': 'GPUStack 企业版可用',
'common.input.holder': '请输入', 'common.input.holder': '请输入',
'common.holder.search': '搜索', 'common.holder.search': '搜索',
'common.button.edit': '编辑', 'common.button.edit': '编辑',
'common.button.authorize': '角色授权', 'common.button.authorize': '角色授权',
'common.button.confirm': '确定', 'common.button.confirm': '确定',
'common.button.viewlog': '查看日志', 'common.button.viewlog': '查看日志',
'common.button.viewevent': '查看事件',
'common.table.operation': '操作', 'common.table.operation': '操作',
'common.table.creator': '创建者',
'common.table.createTime': '创建时间', 'common.table.createTime': '创建时间',
'common.table.updateTime': '更新时间', 'common.table.updateTime': '更新时间',
'common.table.description': '描述', 'common.table.description': '描述',
'common.table.displayName': '显示名称',
'common.table.name': '名称', 'common.table.name': '名称',
'common.table.status': '状态', 'common.table.status': '状态',
'common.table.name.list': '{type}名称', 'common.table.name.list': '{type}名称',
@@ -230,6 +234,7 @@ export default {
'common.text.tips': '提示', 'common.text.tips': '提示',
'settings.system': '系统设置', 'settings.system': '系统设置',
'common.filter.byId': '按 ID 查询', 'common.filter.byId': '按 ID 查询',
'common.filter.byCreator': '按创建者筛选',
'common.appearance': '外观', 'common.appearance': '外观',
'common.appearance.dark': '深色', 'common.appearance.dark': '深色',
'common.appearance.light': '浅色', 'common.appearance.light': '浅色',
@@ -273,5 +278,9 @@ export default {
'common.file.format.limit': '文件格式不正确,仅支持{formats}。', 'common.file.format.limit': '文件格式不正确,仅支持{formats}。',
'common.image.limit.width': '图片宽度须为{width}。', 'common.image.limit.width': '图片宽度须为{width}。',
'common.image.limit.height': '图片高度须为{height}。', 'common.image.limit.height': '图片高度须为{height}。',
'common.max': '最大 {count}' 'common.remaining': '剩余 {count}',
'common.max': '最大 {count}',
'common.max.count': '{label} 数量',
'common.validate.group': '请填写完整的{group}配置',
'common.preferences': '偏好设置'
}; };
+6 -15
View File
@@ -1,32 +1,23 @@
export default { export default {
'dashboard.title': '概览',
'dashboard.workers': '节点', 'dashboard.workers': '节点',
'dashboard.models': '模型', 'dashboard.deployments': '部署',
'dashboard.clusters': '集群', 'dashboard.clusters': '集群',
'dashboard.totalgpus': 'GPUs', 'dashboard.totalgpus': 'GPU',
'dashboard.allocategpus': '已分配 GPU 数量',
'dashboard.instances': '实例',
'dashboard.systemload': '系统负载', 'dashboard.systemload': '系统负载',
'dashboard.memory': '内存', 'dashboard.memory': '内存',
'dashboard.disk': '磁盘',
'dashboard.vram': '显存', 'dashboard.vram': '显存',
'dashboard.cpuutilization': '平均 CPU 利用率', 'dashboard.cpuutilization': '平均 CPU 利用率',
'dashboard.memoryutilization': '平均内存利用率', 'dashboard.memoryutilization': '平均内存利用率',
'dashboard.diskutilization': '磁盘利用率',
'dashboard.vramutilization': '平均显存利用率', 'dashboard.vramutilization': '平均显存利用率',
'dashboard.gpuutilization': '平均 GPU 利用率', 'dashboard.gpuutilization': '平均 GPU 利用率',
'dashboard.usage': '使用量', 'dashboard.usage': '使用量',
'dashboard.apirequest': 'API 请求', 'dashboard.usage.title': '最近 {days} 天使用量',
'dashboard.usage.others': '其他',
'dashboard.tokens': 'Token 使用量', 'dashboard.tokens': 'Token 使用量',
'dashboard.topusers': '用户排行', 'dashboard.topusers': '用户排行',
'dashboard.activeModels': '活跃模型', 'dashboard.activeDeployments': '活跃部署',
'dashboard.activeUsers': '活跃用户', 'dashboard.usageByModel': '按模型统计使用量',
'dashboard.tokenUsageByModel': '按模型统计 Token 使用量',
'dashboard.apiRequestsByModel': '按模型统计 API 请求数',
'dashboard.topTokenUsageByUser': '用户 Token 使用量 Top 10', 'dashboard.topTokenUsageByUser': '用户 Token 使用量 Top 10',
'dashboard.topTokenUsageByApiKey': 'API 密钥 Token 使用量 Top 10',
'dashboard.activeModels.name': '模型名称',
'dashboard.runninginstances': '运行实例',
'dashboard.allocatevram': '已分配显存 / 内存', 'dashboard.allocatevram': '已分配显存 / 内存',
'dashboard.usage.selectuser': '选择用户', 'dashboard.usage.selectuser': '选择用户',
'dashboard.usage.selectmodel': '选择模型', 'dashboard.usage.selectmodel': '选择模型',
+92 -8
View File
@@ -13,11 +13,21 @@ export default {
'gpuservice.template.command.placeholder': 'gpuservice.template.command.placeholder':
'使用空格分隔参数;含空格的参数请用引号包裹,例如:/bin/bash -c "echo hello world"', '使用空格分隔参数;含空格的参数请用引号包裹,例如:/bin/bash -c "echo hello world"',
'gpuservice.template.mountPath': '挂载路径', 'gpuservice.template.mountPath': '挂载路径',
'gpuservice.template.containerDisk': '容器磁盘(GB)', 'gpuservice.template.mountPath.tips':
'gpuservice.template.memory': '内存(GB)', '在使用该模板创建实例时,存储卷默认挂载的路径,可用于持久化实例运行过程中需要保留的数据。',
'gpuservice.template.containerDisk': '容器磁盘(GB',
'gpuservice.template.containerDisk.tips': '容器系统盘大小。',
'gpuservice.template.memory': '内存(GB',
'gpuservice.instance.containerDisk.remaining': '容器磁盘(最大 {count} GB',
'gpuservice.instance.memory.remaining': '内存(最大 {count} GB',
'gpuservice.template.displayName': '显示名称',
'gpuservice.template.displayName.max': '显示名称不能超过 63 个字符',
'gpuservice.template.ports': '端口', 'gpuservice.template.ports': '端口',
'gpuservice.template.ports.add': '添加端口', 'gpuservice.template.ports.add': '添加端口',
'gpuservice.template.ports.invalid': '请填写完整的端口配置', 'gpuservice.template.ports.invalid': '请填写完整的端口配置',
'gpuservice.template.ports.name': '名称',
'gpuservice.template.ports.name.max': '端口名称不能超过 16 个字符',
'gpuservice.template.ports.name.duplicate': '端口名称不能重复',
'gpuservice.template.env': '环境变量', 'gpuservice.template.env': '环境变量',
'gpuservice.template.env.add': '添加环境变量', 'gpuservice.template.env.add': '添加环境变量',
'gpuservice.template.env.invalid': '请填写完整的环境变量', 'gpuservice.template.env.invalid': '请填写完整的环境变量',
@@ -27,7 +37,49 @@ export default {
'gpuservice.template.card.mount': '挂载', 'gpuservice.template.card.mount': '挂载',
'gpuservice.template.card.resources': '资源', 'gpuservice.template.card.resources': '资源',
'gpuservice.template.card.ports': '端口', 'gpuservice.template.card.ports': '端口',
'gpuservice.storageType': '存储类型',
'gpuservice.storageType.add': '添加存储类型',
'gpuservice.storageType.edit': '编辑存储类型',
'gpuservice.storageType.filter.name': '按名称搜索',
'gpuservice.storageType.kind': '类型',
'gpuservice.storageType.mountOptions': '挂载参数',
'gpuservice.storageType.nfs.server': 'NFS 服务器',
'gpuservice.storageType.nfs.server.tips':
'确保所有 Kubernetes 集群都能访问该 NFS 服务地址。',
'gpuservice.storageType.nfs.share': '共享路径',
'gpuservice.storageType.nfs.share.tips':
'系统会在该共享路径下自动创建一个基于组织名称和存储名称的目录。如果指定了子目录,则生成的目录会创建在该子目录下。',
'gpuservice.storageType.nfs.subDirectory': '子目录',
'gpuservice.storageType.nfs.subDirectory.tips':
'如果为空,会以持久卷的卷名创建一个子目录;如果不为空,会在同名子目录下,以持久卷的卷名创建一个孙目录。',
'gpuservice.storageType.nfs.mountPermissions': '挂载权限',
'gpuservice.storageType.nfs.mountPermissions.tips':
'沿用 NFS 服务器文件的权限。',
'gpuservice.storageType.s3.endpoint': 'Endpoint',
'gpuservice.storageType.s3.endpoint.tips':
'确保所有 Kubernetes 集群都能访问该 S3 Endpoint。',
'gpuservice.storageType.s3.endpoint.rule': '必须以 http 或 https 开头',
'gpuservice.storageType.s3.region': '区域',
'gpuservice.storageType.s3.bucket': '存储桶',
'gpuservice.storageType.s3.bucket.tips':
'如果为空,会以持久卷的卷名创建一个新桶;如果不为空,会在同名桶下,以持久卷的卷名创建一个子目录。',
'gpuservice.storageType.s3.bucket.tips1':
'系统会在该 Bucket 中自动创建一个基于组织名称和存储名称的前缀目录。',
'gpuservice.storageType.s3.bucket.tips2':
'例如,若组织名称为 <span class="desc-block">awesome-group</span>,存储名称为 <span class="desc-block">storage-1</span>,则生成的前缀为:<span class="desc-block">awesome-group/storage-1</span>。',
'gpuservice.storageType.s3.accessKey': 'Access Key',
'gpuservice.storageType.s3.secretKey': 'Secret Key',
'gpuservice.storageType.s3.insecure': '跳过 TLS/SSL 证书验证',
'gpuservice.storageType.s3.insecure.tips':
'开启后将忽略 S3 服务端的证书安全检查。适用于内网测试或使用自签名证书的场景,生产环境请谨慎勾选。',
'gpuservice.publicKey': 'SSH 公钥',
'gpuservice.publicKey.add': '添加 SSH 公钥',
'gpuservice.publicKey.edit': '编辑 SSH 公钥',
'gpuservice.publicKey.filter.name': '按名称搜索',
'gpuservice.publicKey.label': 'SSH 公钥', 'gpuservice.publicKey.label': 'SSH 公钥',
'gpuservice.instance.ssh.enable': '启用 SSH 访问',
'gpuservice.instance.ssh.assignKey': '分配 SSH 公钥',
'gpuservice.instance.ssh.addKey': '添加 SSH 公钥',
'gpuservice.publicKey.placeholder': 'gpuservice.publicKey.placeholder':
'以 ssh-rsa 或 ssh-ed25519 开头,每个公钥单独一行\n\n查看公钥:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub', '以 ssh-rsa 或 ssh-ed25519 开头,每个公钥单独一行\n\n查看公钥:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub',
'gpuservice.instance': 'GPU 实例', 'gpuservice.instance': 'GPU 实例',
@@ -43,20 +95,43 @@ export default {
'gpuservice.instance.templates': '实例模板', 'gpuservice.instance.templates': '实例模板',
'gpuservice.instance.section.storage': '存储卷', 'gpuservice.instance.section.storage': '存储卷',
'gpuservice.instance.type.required': '请选择实例类型', 'gpuservice.instance.type.required': '请选择实例类型',
'gpuservice.instance.type.noAvailable': '无可用的实例类型',
'gpuservice.instance.gpuCount': 'GPU 数量', 'gpuservice.instance.gpuCount': 'GPU 数量',
'gpuservice.instance.gpuCount.required': '请输入 GPU 数量', 'gpuservice.instance.gpuCount.required': '请输入 GPU 数量',
'gpuservice.instance.gpuCount.max': '当前实例类型最多支持 {count} 个 GPU', 'gpuservice.instance.gpuCount.max': '最多选择 {count} 张卡',
'gpuservice.instance.gpuCount.min': '至少选择 {count} 张卡',
'gpuservice.instance.cpuCount.max': '最多选择 {count} 核',
'gpuservice.instance.cpuCount.min': '至少选择 {count} 核',
'gpuservice.instance.gpuCount.noAvailable':
'没有可用的 GPU 资源,请选择其他实例类型。',
'gpuservice.instance.gpuCount.zero': '仅使用 CPU,用于环境准备。',
'gpuservice.instance.stock': '库存', 'gpuservice.instance.stock': '库存',
'gpuservice.instance.sliced': '切分', 'gpuservice.instance.sliced': '切分',
'gpuservice.instance.memory': '显存', 'gpuservice.instance.memory': '显存',
'gpuservice.instance.ram': '内存', 'gpuservice.instance.ram': '内存',
'gpuservice.instance.search.type.placeholder': '搜索名称、显存、内存或 vCPU', 'gpuservice.instance.disk': '磁盘',
'gpuservice.table.count': '数量',
'gpuservice.instance.disk.system': '系统盘',
'gpuservice.instance.disk.ephemeral': '临时存储',
'gpuservice.instance.disk.persistent': '持久存储',
'gpuservice.instance.search.type.placeholder': '搜索名称',
'gpuservice.instance.search.template.placeholder': 'gpuservice.instance.search.template.placeholder':
'搜索模板名称、镜像或挂载路径', '搜索模板名称、镜像或挂载路径',
'gpuservice.instance.template.image': '镜像', 'gpuservice.instance.template.image': '镜像',
'gpuservice.instance.template.mount': '挂载', 'gpuservice.instance.template.mount': '挂载',
'gpuservice.instance.connect': '连接', 'gpuservice.instance.connect': '连接',
'gpuservice.instance.connect.copySshCommand': '复制 SSH 命令', 'gpuservice.instance.connect.copySshCommand': '复制 SSH 命令',
'gpuservice.instance.event.reason': '原因',
'gpuservice.instance.event.message': '消息',
'gpuservice.instance.event.source': '来源',
'gpuservice.instance.event.count': '次数',
'gpuservice.instance.event.lastSeen': '最近发生',
'gpuservice.instance.event.recentHourTip': '仅显示最近一小时的事件。',
'gpuservice.instance.event.tab.instance': '实例事件',
'gpuservice.instance.event.tab.volume': '存储卷事件',
'gpuservice.instance.recreate.confirm.title': '确认重新创建',
'gpuservice.instance.recreate.confirm.content':
'系统将先删除当前实例,然后使用当前配置重新创建。\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
'gpuservice.storage': '存储', 'gpuservice.storage': '存储',
'gpuservice.storage.add': '添加存储', 'gpuservice.storage.add': '添加存储',
'gpuservice.storage.edit': '编辑存储', 'gpuservice.storage.edit': '编辑存储',
@@ -69,8 +144,17 @@ export default {
'gpuservice.storage.accessMode': '访问模式', 'gpuservice.storage.accessMode': '访问模式',
'gpuservice.storage.persistent': '持久', 'gpuservice.storage.persistent': '持久',
'gpuservice.storage.temporary': '临时', 'gpuservice.storage.temporary': '临时',
'gpuservice.storage.persistentVolume': '持久', 'gpuservice.storage.persistentVolume': '持久',
'gpuservice.storage.persistentVolume.required': '请选择持久卷', 'gpuservice.storage.temporary.tips': '实例停止后,数据将被清除。',
'gpuservice.storage.tempCapacity': '存储容量 (GB)', 'gpuservice.storage.persistentVolume.tips':
'gpuservice.storage.tempCapacity.required': '请输入本地临时存储容量' '数据在实例重启后仍会保留。持久卷不会随实例终止而删除,并可被多个实例共享。',
'gpuservice.storage.persistentVolume.required': '请选择存储',
'gpuservice.storage.persistentVolume.capacity': '容量(GB',
'gpuservice.storage.persistentVolume.capacity.required': '请输入容量',
'gpuservice.storage.persistentVolume.releaseWithInstance': '随实例释放',
'gpuservice.storage.tempCapacity': '容量(GB',
'gpuservice.storage.tempCapacity.required': '请输入临时存储容量',
'gpuservice.form.rule.name':
'由小写字母、数字和 "-" 组成,以字母或数字开头和结尾,不能包含连续的 "-",最多 63 个字符。',
'gpuservice.form.storage.select': '选择存储'
}; };
+14 -10
View File
@@ -8,7 +8,7 @@ export default {
'menu.playground.text2images': '图像', 'menu.playground.text2images': '图像',
'menu.playground.video': '视频', 'menu.playground.video': '视频',
'menu.compare': '多模型对比', 'menu.compare': '多模型对比',
'menu.models': '模型', 'menu.models': '模型服务',
'menu.models.modelList': '部署与管理', 'menu.models.modelList': '部署与管理',
'menu.models.modelCatalog': '模型库', 'menu.models.modelCatalog': '模型库',
'menu.models.deployment': '部署', 'menu.models.deployment': '部署',
@@ -25,25 +25,29 @@ export default {
'menu.resources': '资源', 'menu.resources': '资源',
'menu.apikeys': 'API 密钥', 'menu.apikeys': 'API 密钥',
'menu.users': '用户', 'menu.users': '用户',
'menu.profile': '个人信息', 'menu.profile': '偏好',
'menu.login': '登录', 'menu.login': '登录',
'menu.usage': '使用量', 'menu.usage': '使用量',
'menu.usage.usage': '使用量',
'menu.billingAndUsage': '用量与计费',
'menu.billingAndUsage.usage': '用量统计',
'menu.404': '404', 'menu.404': '404',
'menu.resources.workers': '节点', 'menu.resources.workers': '节点',
'menu.resources.gpus': 'GPUs', 'menu.resources.gpus': 'GPU',
'menu.resources.modelfiles': '模型文件', 'menu.models.modelfiles': '模型文件',
'menu.accessControl': '访问控制', 'menu.accessControl': '访问控制',
'menu.accessControl.apikeys': 'API 密钥', 'menu.accessControl.apikeys': 'API 密钥',
'menu.accessControl.users': '用户', 'menu.accessControl.users': '用户',
'menu.clusterManagement': '集群管理', 'menu.accessControl.organizations': '组织',
'menu.clusterManagement.clusters': '集群', 'menu.resources.clusters': '集群',
'menu.clusterManagement.credentials': '云凭证', 'menu.resources.credentials': '云凭证',
'menu.clusterManagement.clusterDetail': '集群详情', 'menu.resources.clusterDetail': '集群详情',
'menu.clusterManagement.clusterCreate': '创建集群', 'menu.resources.clusterCreate': '创建集群',
'menu.resources.backendsList': '推理后端', 'menu.models.backendsList': '推理后端',
'menu.gpuService': 'GPU 服务', 'menu.gpuService': 'GPU 服务',
'menu.gpuService.instances': 'GPU 实例', 'menu.gpuService.instances': 'GPU 实例',
'menu.gpuService.templates': '实例模板', 'menu.gpuService.templates': '实例模板',
'menu.gpuService.storage': '存储', 'menu.gpuService.storage': '存储',
'menu.gpuService.storageTypes': '存储类型',
'menu.gpuService.publicKeys': 'SSH 公钥' 'menu.gpuService.publicKeys': 'SSH 公钥'
}; };
+8 -2
View File
@@ -62,7 +62,7 @@ export default {
'models.form.backend': '后端', 'models.form.backend': '后端',
'models.form.backend_parameters': '后端参数', 'models.form.backend_parameters': '后端参数',
'models.instance.params.configured': '用户配置', 'models.instance.params.configured': '用户配置',
'models.instance.params.autoInjected': '自动注入', 'models.instance.params.autoInjected': '自动注入参数',
'models.search.gguf.tips': 'models.search.gguf.tips':
'GGUF 模型用 llama-box(支持 Linux, macOS 和 Windows)。', 'GGUF 模型用 llama-box(支持 Linux, macOS 和 Windows)。',
'models.search.vllm.tips': 'models.search.vllm.tips':
@@ -274,5 +274,11 @@ export default {
'models.instance.previousRun': '上一次运行', 'models.instance.previousRun': '上一次运行',
'models.instance.startHistory': '运行记录', 'models.instance.startHistory': '运行记录',
'models.instance.startHistory.tips': 'models.instance.startHistory.tips':
'显示上一次因错误自动重启之前的那次运行的日志。' '显示上一次因错误自动重启之前的那次运行的日志。',
'models.form.lora.label': 'LoRA 适配器',
'models.form.lora.add': '添加 LoRA 适配器',
'models.form.lora.select': '选择 LoRA',
'models.form.lora.name': 'LoRA 名称',
'models.form.lora.rule.empty': '输入不能为空',
'models.form.lora.rule.duplicate': 'LoRA name 不能重复'
}; };
+10 -1
View File
@@ -35,8 +35,11 @@ export default {
'noresult.catalog.subTitle': '尚未配置任何模型。', 'noresult.catalog.subTitle': '尚未配置任何模型。',
'noresult.catalog.nofound': '未找到匹配的模型', 'noresult.catalog.nofound': '未找到匹配的模型',
'noresult.resources.cluster': '暂无可用集群,请添加集群以开始使用。', 'noresult.resources.cluster': '暂无可用集群,请添加集群以开始使用。',
'noresult.resources.k8sCluster':
'暂无可用集群,请添加 Kubernetes 集群以开始使用。',
'noresult.resources.worker': '暂无可用节点,请添加节点以开始使用。', 'noresult.resources.worker': '暂无可用节点,请添加节点以开始使用。',
'noresult.resources.gotocluster': '创建您的第一个集群', 'noresult.resources.gotocluster': '创建您的第一个集群',
'noresult.resources.addk8scluster': '添加 Kubernetes 集群',
'noresult.resources.gotoworker': '添加节点', 'noresult.resources.gotoworker': '添加节点',
'noresult.benchmark.title': '暂无基准测试', 'noresult.benchmark.title': '暂无基准测试',
'noresult.benchmark.subTitle': '尚未添加任何基准测试。', 'noresult.benchmark.subTitle': '尚未添加任何基准测试。',
@@ -59,5 +62,11 @@ export default {
'noresult.gpuservice.instance.nofound': '未找到匹配的 GPU 实例', 'noresult.gpuservice.instance.nofound': '未找到匹配的 GPU 实例',
'noresult.gpuservice.storage.title': '暂无存储', 'noresult.gpuservice.storage.title': '暂无存储',
'noresult.gpuservice.storage.subTitle': '尚未添加任何存储。', 'noresult.gpuservice.storage.subTitle': '尚未添加任何存储。',
'noresult.gpuservice.storage.nofound': '未找到匹配的存储' 'noresult.gpuservice.storage.nofound': '未找到匹配的存储',
'noresult.gpuservice.storageType.title': '暂无存储类型',
'noresult.gpuservice.storageType.subTitle': '尚未添加任何存储类型。',
'noresult.gpuservice.storageType.nofound': '未找到匹配的存储类型',
'noresult.gpuservice.sshkey.title': '暂无 SSH 公钥',
'noresult.gpuservice.sshkey.subTitle': '尚未添加任何 SSH 公钥。',
'noresult.gpuservice.sshkey.nofound': '未找到匹配的 SSH 公钥'
}; };
+12
View File
@@ -0,0 +1,12 @@
export default {
'organizations.upsell.title': '组织是企业版功能',
'organizations.upsell.subtitle':
'多租户可在团队间隔离用户、资源与配额。升级到 GPUStack 企业版即可管理组织。',
'organizations.upsell.featuresTitle': '企业版包含的能力',
'organizations.upsell.feature.orgs': '创建组织来分组用户并隔离工作负载',
'organizations.upsell.feature.members': '为每个组织管理成员与角色',
'organizations.upsell.feature.quotas': '为每个组织设置资源与 Token 配额',
'organizations.upsell.feature.isolation':
'按组织隔离 API 密钥、模型部署与资源',
'organizations.upsell.cta': '了解企业版'
};

Some files were not shown because too many files have changed in this diff Show More