Compare commits

..
Author SHA1 Message Date
jialin 5f405e4823 feat: cluster topology entry 2026-05-14 10:43:29 +08:00
510 changed files with 8084 additions and 27455 deletions
-143
View File
@@ -1,143 +0,0 @@
---
name: create-crud-page
description: Scaffold a CRUD list/table page module in the gpustack-ui monorepo. Use when creating a new page module, building a list/table page, adding a create/edit drawer, or setting up the components/config/forms/hooks/services structure for a feature.
---
# Create a CRUD Table Page
## Inputs (do this first)
- The argument passed to this skill is the **module name** (e.g. `/create-crud-page api-keys` → module `api-keys`). If no name was given, ask for it.
- **Always ask the user for the API documentation before generating any code**, even if a module name was provided:
> Where is the API documentation for this module? (OpenAPI/Swagger URL, schema file path, or an interface description)
- Wait for the answer, then read/fetch it. Derive `config/types.ts` (`FormData`, `ListItem`), the `services` request hooks, and form fields from that schema. Do not guess field names or endpoints — if the doc is missing details, ask.
- **Also ask which form layout to scaffold:**
> Should the form use tabs? (1) a plain form without tabs, or (2) a tabbed form
Choose the form structure in section 3 accordingly. Default to **no tabs** unless the user picks tabs or the schema clearly has many grouped sections.
---
Before anything: **reuse common `components`, `hooks`, and `utils` from `@gpustack/core-ui` whenever possible.**
Reference implementation for sections below: `src/pages/model-routes`.
## Module structure
Create the module under `src/pages/{module}`:
```text
{module}
├── components
├── config
├── forms
├── hooks
├── index.tsx
└── services
```
## 1. components
Module-specific components.
- The create/edit form component is named `add-xxx-modal.tsx` (repo convention — keep the `-modal` suffix even though it is built with `FormDrawer`).
- Use `FormDrawer` from `@gpustack/core-ui`.
- If a table cell's render logic/structure is complex, extract it into `xxx-cell.tsx`.
## 2. config
```text
config
├── index.ts # static configs & constants
└── types.ts # TypeScript types
```
Naming: form types → `FormData`; table list item types → `ListItem`.
## 3. forms
Main form component goes in `forms/index.tsx`.
- **Complex interactions** (Form.Item split across components): create a dedicated Form Context and wrap with `FormContext.Provider`.
- **Tab-based forms**: use `ScrollSpyTabs` from `@gpustack/core-ui`, wrapping the `Form` or `FormContext.Provider`. Do not use tabs unless necessary.
- **Required-field validation**: use `getRuleMessage` for standard `input`/`select`.
- For cascading selectors and async race protection, follow the **form-patterns** skill.
## 4. hooks
- Table columns → `use-xxx-columns.tsx`.
- Open/close hooks for `add-xxx-modal.tsx``use-create-xxx.ts`.
## 5. index.tsx (list page entry)
- **Data fetching**: `useTableFetch` from `@gpustack/core-ui`.
- **Data display**:
- Standard table → Ant Design `Table`. Ref: `src/pages/users/index.tsx`.
- Expandable/collapsible rows → `Table` from `@gpustack/core-ui`. Ref: `src/pages/model-routes/index.tsx`.
- Card-style lists → use `InfiniteScrollerProvider`. Ref: `src/pages/backends/index.tsx`.
## 6. services
`request` is injected via a provider — do **not** create a centralized `apis` directory like in `gpustack-ui`. Define request hooks directly in `services`.
- Use `useRequest` from `@gpustack/core-ui`, or `useQueryData` (same underlying method).
- Ref: `src/pages/gpu-service/storage-types/services/use-create-storage-type.ts`.
## 7. Empty data
- Page table lists → `NoResult`.
- Simple (non-page) tables → `Empty` with `image={Empty.PRESENTED_IMAGE_SIMPLE}`.
## Common UI conventions
- **Drawer/Modal open/close**: use `useBodyScroll` from `@gpustack/core-ui`. Ref: `src/pages/model-routes/hooks/use-create-route.ts`.
- **Status display** (success/failed/processing/warning): use `StatusTag`, never `Tag` from `antd` directly. See **Status display** below. Ref: `src/pages/llmodels/components/table-list.tsx`.
- **Permission-gated visibility**: use `Access` / `useAccess`. Ref: `src/pages/access/index.tsx`.
- **Styles**: avoid `styled-components` for complex/large styling. Prefer `createStyles` for component-scoped dynamic styles, CSS Modules (`xxx.module.less`) for static structured styles.
## Status display
Use `StatusTag` from `@gpustack/core-ui` for status display. Do not use `Tag` from `antd` directly.
1. Define the mapping from business status values to UI status values in the module's `config/index.ts`:
```ts
import { StatusMaps } from '@/config';
import { StatusType } from '@/config/types';
export const XxxStatusValueMap = {
Running: 'running',
Pending: 'pending',
Failed: 'failed'
};
export const XxxStatusLabelMap: Record<string, string> = {
[XxxStatusValueMap.Running]: 'Running',
[XxxStatusValueMap.Pending]: 'Pending',
[XxxStatusValueMap.Failed]: 'Failed'
};
export const status: Record<string, StatusType> = {
[XxxStatusValueMap.Running]: StatusMaps.success,
[XxxStatusValueMap.Pending]: StatusMaps.transitioning,
[XxxStatusValueMap.Failed]: StatusMaps.error
};
```
2. In table columns, pass only the UI status value and display text required by `StatusTag`:
```tsx
<StatusTag
statusValue={{
status: status[value],
text: XxxStatusLabelMap[value] || value,
message: record.state_message
}}
/>
```
`statusValue.status` must be a value mapped from `StatusMaps` (`success`, `transitioning`, `warning`, `error`, `inactive`). Do not pass business status values such as `running` or `pending` directly.
-103
View File
@@ -1,103 +0,0 @@
---
name: form-patterns
description: Patterns for forms with cascading/dependent selections in the gpustack-ui monorepo. Use when building a form where picking one field derives another (pick A → auto-pick B → write form), handling async option loading on modal open, or protecting against stale async results.
---
# Form Patterns
Theme: keep cascading selections (pick A → derive B → write form) on a single, predictable path.
## Accessing the form: `form` vs `form.current`
This is **not** absolute — it depends on the call site:
- **Inside the form component** (`forms/index.tsx`), or anywhere holding a `Form.useForm()` instance → call it directly: `form.setFieldsValue(...)`.
- **In the outer Drawer/Modal wrapper** that opens the form and holds it via `ref={form}` (`const form = useRef(null)`), driven by an `open` prop → go through the ref: `form.current?.setFieldsValue(...)`.
The reference template below is written for the **Drawer-wrapper scenario** (it reacts to `open` and owns the shared `selection` state), so it uses `form.current?` throughout. If you lift this logic into the form body with a `useForm()` instance, drop the `.current`.
## 1. No fallback for derived selection
When "pick A then auto-pick B", match by rule and return `undefined` if no match — let the form field stay empty. Do **not** silently fall back to `list[0]`; a fallback hides data issues and fakes a valid selection.
```ts
const findB = (key, list) =>
key ? list.find((x) => x.key === key) : undefined;
```
For form fields, clear with `undefined`, not `''`. In 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 rotated (modal closed and re-opened) before 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
Two cascading selectors backed by a single shared state, with a single atomic write (state + form together):
```ts
type Selection = { a?: string; b?: number };
const [selection, setSelection] = useState<Selection>({});
const sessionRef = useRef(0);
const form = useRef<any>(null); // wrapper holds the form via <Form ref={form} /> — see "Accessing the form" above
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 });
};
```
## Related
- Module/file structure for forms lives in the **create-crud-page** skill (section 3).
- Required-field validation: use `getRuleMessage`.
-18
View File
@@ -1,7 +1,6 @@
name: CI
on:
workflow_dispatch: {}
push:
branches:
- 'main'
@@ -118,20 +117,3 @@ jobs:
remote_path: releases/${{ steps.version.outputs.version }}.tar.gz
accelerate: true
clean: false
trigger-backend:
needs: build-publish
if: (github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-22.04
steps:
- name: Dispatch backend build
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.DISPATCH_PAT }}
repository: gpustack/gpustack
event-type: ui-built
client-payload: |
{
"ref": "${{ github.ref }}",
"sha": "${{ github.sha }}"
}
+1 -2
View File
@@ -13,6 +13,5 @@
.swc
.DS_Store
.idea
.claude/settings.local.json
.claude
/dist.zip
.cache
+2 -1
View File
@@ -3,6 +3,7 @@ node_modules
.umi-production
public/static/*.js
public/static/*.css
src/components/iconfont/
src/components/icon-font/iconfont/iconfont.js
src/components/icon-font/iconfont/*.css
+1 -1
View File
@@ -3,5 +3,5 @@ module.exports = {
rules: {
'selector-class-pattern': null
},
ignoreFiles: ['public/static/*.css', 'src/components/iconfont/iconfont.css']
ignoreFiles: ['public/static/*.css']
};
-74
View File
@@ -1,74 +0,0 @@
# Agent Instructions
This project keeps a single source of truth for agent/contributor conventions in [`CLAUDE.md`](./CLAUDE.md). **Read [`CLAUDE.md`](./CLAUDE.md) and follow it.**
@CLAUDE.md
## Downstream fork workflow
This repo is a **downstream fork** that customizes the product appearance on top of
upstream `gpustack/gpustack-ui`. The mirror chain is:
```
upstream https://github.com/gpustack/gpustack-ui.git
| (fetch)
origin ssh://git@192.168.0.23:11022/root/gpustack-ui.git (private registry, also https://git.digiman.live)
```
### Goal
Track upstream releases. When upstream changes, we pull it in, then apply our own
appearance/customization changes on a dedicated branch so we keep our look-and-feel
on top of the latest upstream product.
### Branch naming
Customization work lives on `v<upstream-version>-lofyer` branches (e.g.
`v2.2.0-lofyer`). Each time upstream ships a new version we want to follow, create a
new `v<version>-lofyer` branch from the corresponding upstream tag/branch and re-apply
(or rebase) our customizations onto it.
### Syncing from upstream
`scripts/sync-github` mirrors upstream into the private `origin` (full mirror, force
push of all branches + tags). It auto-configures the `upstream` remote on first run.
```bash
# preview only, no push
DRY_RUN=1 ./scripts/sync-github
# real sync (force-pushes every upstream branch + tag to origin)
./scripts/sync-github
# also delete origin branches that no longer exist upstream (true mirror, destructive)
PRUNE_BRANCHES=1 ./scripts/sync-github
```
Env knobs: `UPSTREAM_URL`, `ORIGIN_REMOTE`, `UPSTREAM_REMOTE`, `PRUNE_BRANCHES`,
`DRY_RUN`. After syncing, branch a fresh `v<version>-lofyer` off the updated upstream
ref and apply the appearance changes there.
### Re-applying brand customizations
`scripts/rebrand` swaps the standalone brand word `GPUStack` for our brand
(`MesaStack`) across user-facing text. It is the first appearance change to re-apply
on every new `v<version>-lofyer` branch.
```bash
# preview hits, no writes
DRY_RUN=1 ./scripts/rebrand
# apply (default GPUStack -> MesaStack)
./scripts/rebrand
# custom brand words
FROM=GPUStack TO=AcmeStack ./scripts/rebrand
```
It deliberately **does not** touch functional references — lowercase `gpustack`
(npm pkg / URLs / paths / k8s namespace), ALL-CAPS `GPUSTACK_*` constants, JS
identifiers like `getGPUStackPlugin`, and `X-*` HTTP headers — and carries a
line-level skip list for backend-contract strings matched at runtime (see
`SKIP_LINE_PATTERNS` in the script). Always review `git diff` afterwards. Logo
images under `src/assets/images/` are NOT changed by the script — replace those PNGs
separately when new brand assets are available.
-129
View File
@@ -1,129 +0,0 @@
# Repo
This is the **open source UI** (`gpustack-ui`). Common `components`, `hooks`, and `utils` are published as `@gpustack/core-ui` and consumed throughout `src`.
**Always prioritize reusing common `components`, `hooks`, and `utils` from `@gpustack/core-ui`.**
Task-specific conventions live in skills: use **create-crud-page** when building a page module, **form-patterns** when building cascading/dependent forms.
# React State and Request Patterns
Keep data flow explicit, predictable, and performant. The triggering **action** is the source of truth for UI updates — not effect-driven synchronization.
## 1. Avoid effect-driven requests
Do not use request functions as `useEffect` dependencies. Trigger requests explicitly from user actions or lifecycle entry points.
```ts
// Avoid
useEffect(() => {
fetchData();
}, [fetchData]);
```
## 2. Form requests should be action-driven
- Fetch form data (e.g. `Select` options) when the form first opens.
- If later requests depend on interactions, trigger them inside the interaction handler.
- Do not rely on `useEffect` dependency changes.
```ts
// Recommended
const handleOnChange = (value) => {
fetchData(value);
};
```
## 3. Update related states together
When one action updates multiple related states, update them all directly in the handler. Do not sync via `useEffect` or derive indirectly via `useMemo`.
```ts
const handleOnChange = (value) => {
setState1(...);
setState2(...);
buildState(...);
};
```
## 4. Group strongly related state
If multiple states always update together, use a single state object instead of multiple `useState` calls — fewer rerenders, more predictable transitions.
```ts
const [state, setState] = useState({ state1: ..., state2: ..., state3: ... });
```
## 5. Prefer explicit state flow
Keep request execution, state updates, and derived calculations close to the triggering action. Avoid chaining business logic through multiple `useEffect` hooks.
```ts
// Prefer
const handleAction = () => {
fetchData();
setTableData(...);
setSelectedRow(...);
};
```
## 6. Avoid premature memoization
Do not use `useMemo` / `useCallback` unless there is a confirmed bottleneck. Overuse adds complexity, obscures state flow, and risks stale dependencies. Optimize only when necessary.
## 7. Keep request logic predictable
A user interaction should clearly show: what request fires, which states update, how the UI changes. Avoid indirect update chains from dependency-driven effects.
## 8. Prefer action-driven architecture
Prefer action-driven updates, explicit handlers, and localized state transitions over effect-driven synchronization, cross-hook implicit updates, and reactive chains between states.
# Styles
**Future direction (apply to all new code):** avoid `styled-components`. Prefer:
1. `createStyles` for component-scoped dynamic styles
2. CSS Modules (`xxx.module.less`) for structured static styles
Existing `styled-components` usage is legacy tech debt — do not migrate it wholesale, but do not add new `styled-components` either. Theme tokens (`var(--ant-color-*)`) work in all three approaches.
# Naming conventions
A page module lives under `src/pages/{module}` with this sub-structure: `components/`, `config/`, `forms/`, `hooks/`, `services/`, `index.tsx`. File naming:
- **Create/edit modal**: `add-{feature}-modal.tsx` (keep the `-modal` suffix even when built with `FormDrawer`).
- **Table columns hook**: `use-{feature}-columns.tsx`.
- **Open/close & request hooks**: `use-{verb}-{noun}.ts` (e.g. `use-create-user.ts`, `use-query-user-list.ts`).
- **Complex table cell**: extract into `{feature}-cell.tsx`.
# Config & types
- `config/types.ts` — TypeScript types. Form shape → `FormData`; table/list row → `ListItem`.
- `config/index.ts` — static constants, enums, and value/label maps (e.g. `XxxStatusValueMap`, `XxxStatusLabelMap`). Keep constants out of `types.ts`.
# Common components
Always check `@gpustack/core-ui` first. Frequently reused:
- **Drawer/Modal open/close**: `useBodyScroll`.
- **Form drawer / footer**: `FormDrawer`, `ModalFooter`.
- **Delete confirmation**: `DeleteModal`.
- **Search + bulk actions bar**: `FilterBar`.
- **Form fields**: `BaseSelect`, `Input` (labeled).
- **Text overflow**: `AutoTooltip`.
- **Icons**: `IconFont`.
- **Status display** (success/failed/processing/warning): `StatusTag`.
- **Permission-gated visibility**: `Access` / `useAccess`.
- **Request hooks**: `useRequest` / `useQueryData` / `useQueryDataList`.
- **Table data fetching**: `useTableFetch`.
- **Submit guard** (prevent double-submit): `useSubmitLock`.
- **Tabbed forms**: `ScrollSpyTabs`.
# Dynamic add-item form fields
When building a form, select the add-item component from the **shape of the field's data** (its schema). Match the schema, don't hand-roll a list UI:
- **Plain object** (key→value map) → `LabelSelector`.
- **String array** → `ListInput`. Ref `src/pages/llmodels/forms/backend-parameters-list.tsx`.
- **Object array** → `MetadataList` with a custom item renderer per entry. Ref `src/pages/llmodels/forms/model-lora-list.tsx`.
+46
View File
@@ -0,0 +1,46 @@
## Create form table list
## Create a form
## StatusTag
Use `StatusTag` from `@gpustack/core-ui` for status display. Do not use `Tag` from `antd` directly.
1. Define the mapping from business status values to UI status values in the module's `config/index.ts`:
```ts
import { StatusMaps } from '@/config';
import { StatusType } from '@/config/types';
export const XxxStatusValueMap = {
Running: 'running',
Pending: 'pending',
Failed: 'failed'
};
export const XxxStatusLabelMap: Record<string, string> = {
[XxxStatusValueMap.Running]: 'Running',
[XxxStatusValueMap.Pending]: 'Pending',
[XxxStatusValueMap.Failed]: 'Failed'
};
export const status: Record<string, StatusType> = {
[XxxStatusValueMap.Running]: StatusMaps.success,
[XxxStatusValueMap.Pending]: StatusMaps.transitioning,
[XxxStatusValueMap.Failed]: StatusMaps.error
};
```
2. In table columns, pass only the UI status value and display text required by `StatusTag`:
```tsx
<StatusTag
statusValue={{
status: status[value],
text: XxxStatusLabelMap[value] || value,
message: record.state_message
}}
/>
```
`statusValue.status` must be a value mapped from `StatusMaps`, such as `success`, `transitioning`, `warning`, `error`, or `inactive`. Do not pass business status values such as `running` or `pending` directly.
+2 -2
View File
@@ -1,6 +1,6 @@
# MesaStack UI
# GPUStack UI
UI for [MesaStack](https://github.com/gpustack/gpustack).
UI for [GPUStack](https://github.com/gpustack/gpustack).
## Installation
+1 -1
View File
@@ -77,7 +77,7 @@ export default defineConfig({
antd: {
style: 'less'
},
title: 'MesaStack',
title: 'GPUStack',
hash: true,
access: {},
model: {},
-1
View File
@@ -31,6 +31,5 @@ export default function createProxyTable(target?: string) {
},
{}
);
return proxyTable;
}
+62 -109
View File
@@ -140,6 +140,15 @@ const baseRoutes = [
access: 'canSeeOrgAdmin',
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',
path: '/models/providers',
@@ -170,26 +179,6 @@ const baseRoutes = [
access: 'canSeeOrgAdmin',
hideInMenu: true,
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'
}
]
},
@@ -197,7 +186,6 @@ const baseRoutes = [
name: 'gpuService',
path: '/gpu-service',
key: 'gpuService',
access: 'canSeeGpuService',
routes: [
{
path: '/gpu-service',
@@ -225,25 +213,10 @@ const baseRoutes = [
name: 'storage',
path: '/gpu-service/storage',
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',
// 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',
defaultIcon: 'icon-storage-outlined',
component: './gpu-service/storage-types'
component: './gpu-service/storage'
},
{
name: 'publicKeys',
@@ -266,16 +239,6 @@ const baseRoutes = [
path: '/resources',
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',
path: '/resources/workers',
@@ -295,63 +258,67 @@ const baseRoutes = [
component: './resources/components/gpus'
},
{
name: 'credentials',
path: '/resources/credentials',
key: 'credentials',
icon: 'icon-credential-outline',
selectedIcon: 'icon-credential-filled',
defaultIcon: 'icon-credential-outline',
component: './cluster-management/credentials'
name: 'backendsList',
path: '/resources/backends',
key: 'backendsList',
icon: 'icon-backend',
selectedIcon: 'icon-backend-filled',
defaultIcon: 'icon-backend',
access: 'canSeeOrgAdmin',
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',
path: '/resources/clusters/detail',
path: '/cluster-management/clusters/detail',
key: 'clusterDetail',
icon: 'icon-cluster2-outline',
selectedIcon: 'icon-cluster2-filled',
defaultIcon: 'icon-cluster2-outline',
hideInMenu: true,
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: 'usage',
path: '/usage/overview',
key: 'usage',
icon: 'icon-usage-outlined',
selectedIcon: 'icon-usage-filled',
defaultIcon: 'icon-usage-outlined',
component: './usage/index'
},
{
name: 'billing',
path: '/usage/billing',
key: 'billing',
icon: 'icon-billing-outlined',
selectedIcon: 'icon-billing-filled',
defaultIcon: 'icon-billing-outlined',
hideInMenu: process.env.ENABLE_ENTERPRISE === 'true',
// OSS exposes the menu as a teaser for the enterprise billing
// module. The page itself just renders an upsell notice — the real
// billing UI lives in the enterprise plugin and shadows this route
// via `routes.extensions.ts`.
component: './billing'
name: 'credentials',
path: '/cluster-management/credentials',
key: 'credentials',
icon: 'icon-credential-outline',
selectedIcon: 'icon-credential-filled',
defaultIcon: 'icon-credential-outline',
component: './cluster-management/credentials'
}
]
},
@@ -364,20 +331,6 @@ const baseRoutes = [
path: '/access-control',
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',
path: '/access-control/users',
@@ -411,8 +364,8 @@ const baseRoutes = [
},
{
name: 'profile',
path: '/preferences',
key: 'preferences',
path: '/profile',
key: 'profile',
hideInMenu: true,
component: './profile',
icon: 'User'
+10 -24
View File
@@ -1,27 +1,13 @@
import { execSync } from 'child_process';
const child_process = require('child_process');
export const getBranchInfo = () => {
// git may be absent (source archive, bare container) or this tree may
// not be a git checkout. Swallow the failure and fall back to the env
// overrides below — losing build info shouldn't fail the build.
let latestCommit = '';
let versionTag = '';
try {
latestCommit = execSync('git rev-parse HEAD').toString().trim();
versionTag = execSync(`git tag --contains ${latestCommit}`)
.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)
};
const latestCommit = child_process
.execSync('git rev-parse HEAD')
.toString()
.trim();
const versionTag = child_process
.execSync(`git tag --contains ${latestCommit}`)
.toString()
.trim();
return { version: versionTag || '', commitId: latestCommit.slice(0, 7) };
};
+1 -2
View File
@@ -14,8 +14,7 @@ export default defineConfig([
'dist',
'src/.umi/',
'src/.umi-production/',
'src/.umi-test/',
'src/components/iconfont/'
'src/.umi-test/'
]),
{
files: ['**/*.{ts,tsx,js,jsx}'],
+1 -1
View File
@@ -17,7 +17,7 @@
"@ant-design/pro-components": "3.1.0-0",
"@antv/g6": "^5.0.51",
"@braintree/sanitize-url": "^7.1.1",
"@gpustack/core-ui": "^1.0.32",
"@gpustack/core-ui": "^1.0.10",
"@huggingface/gguf": "^0.1.7",
"@huggingface/hub": "^0.15.1",
"@huggingface/tasks": "^0.11.6",
-2
View File
@@ -5,8 +5,6 @@ export default (api: IApi) => {
const info = JSON.parse(process.env.VERSION || '{}');
const env = process.env.NODE_ENV;
$('html').attr('lang', 'en');
$('html').attr('data-env', env);
$('html').attr(
+135 -176
View File
@@ -24,8 +24,8 @@ importers:
specifier: ^7.1.1
version: 7.1.2
'@gpustack/core-ui':
specifier: ^1.0.32
version: 1.0.32(czdvzceysqw7iv6pct2ucnb23e)
specifier: ^1.0.10
version: 1.0.10(czdvzceysqw7iv6pct2ucnb23e)
'@huggingface/gguf':
specifier: ^0.1.7
version: 0.1.18
@@ -49,7 +49,7 @@ importers:
version: 4.17.24
'@umijs/max':
specifier: ^4.6.15
version: 4.6.51(@babel/core@7.23.6)(@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.23.6)(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))
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))
'@xterm/addon-fit':
specifier: ^0.10.0
version: 0.10.0(@xterm/xterm@5.5.0)
@@ -109,7 +109,7 @@ importers:
version: 3.3.0
jotai:
specifier: ^2.8.4
version: 2.20.0(@babel/core@7.23.6)(@babel/template@7.28.6)(@types/react@18.3.28)(react@18.3.1)
version: 2.20.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@18.3.28)(react@18.3.1)
js-yaml:
specifier: ^4.1.0
version: 4.1.1
@@ -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)
umi-presets-pro:
specifier: ^2.0.3
version: 2.0.3(@babel/core@7.23.6)(@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.23.6)(@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.23.6)(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)))
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)))
wavesurfer.js:
specifier: ^7.8.8
version: 7.12.6
@@ -233,10 +233,10 @@ importers:
version: 1.0.1
'@umijs/plugins':
specifier: ^4.4.11
version: 4.6.51(@babel/core@7.23.6)(@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)
version: 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)
babel-plugin-named-asset-import:
specifier: ^0.3.8
version: 0.3.8(@babel/core@7.23.6)
version: 0.3.8(@babel/core@7.29.0)
case-sensitive-paths-webpack-plugin:
specifier: ^2.4.0
version: 2.4.0
@@ -1484,24 +1484,24 @@ packages:
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
'@gpustack/core-ui@1.0.32':
resolution: {integrity: sha512-kGTazoqbK2KyZgOP6gmQaRxTiQVfF2IKLGDXjJq6w6BbmJgALXFJA2v2ROjAbjEVyfTdBzyYeXfPo/JgISpMNw==, tarball: https://registry.npmjs.org/@gpustack/core-ui/-/core-ui-1.0.32.tgz}
'@gpustack/core-ui@1.0.10':
resolution: {integrity: sha512-gw1dlkb0NzcKy23aGa+4EPe9tHI0hgxoUf/ZJZkjvjQQjAZ+zxZFtLscHGvlTVxwc7GE5FOeGjOIGlbIxW+7EA==, tarball: https://registry.npmjs.org/@gpustack/core-ui/-/core-ui-1.0.10.tgz}
peerDependencies:
'@ant-design/icons': ^6.1.0
'@ant-design/icons': '>=6.0.0'
'@ant-design/pro-components': 3.1.0-0
'@monaco-editor/react': ^4.6.0
ahooks: ^3.8.5
antd: ^6.3.3
antd-style: ^3.6.2
axios: ^1.8.2
echarts: ^5.5.1
ahooks: '>=3.0.0'
antd: '>=6.0.0'
antd-style: '>=3.0.0'
axios: '>=1.8.0'
echarts: '>=5.0.0'
file-saver: ^2.0.5
monaco-editor: ^0.30.1
monaco-yaml: ^4.0.0
overlayscrollbars-react: ^0.5.6
react: ^18.2.0
react-dom: ^18.2.0
styled-components: ^6.1.15
react: '>=18.0.0'
react-dom: '>=18.0.0'
styled-components: '>=6.0.0'
'@hono/node-server@1.19.14':
resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==, tarball: https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz}
@@ -2463,9 +2463,6 @@ packages:
'@types/node@25.6.2':
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':
resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==, tarball: https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz}
@@ -2495,9 +2492,6 @@ packages:
'@types/react@18.3.28':
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':
resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==, tarball: https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz}
@@ -4408,10 +4402,6 @@ packages:
resolution: {integrity: sha512-xe9vQb5kReirPUxgQrXA3ihgbCqssmTiM7cOZ+Gzu+VeGWgpV98lLZvp0dl4yriyAePcewxGUs9UpKD8PET9KQ==, tarball: https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.2.tgz}
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:
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'}
@@ -8615,11 +8605,6 @@ packages:
engines: {node: '>=10'}
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:
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'}
@@ -8833,9 +8818,6 @@ packages:
undici-types@7.19.2:
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:
resolution: {integrity: sha512-3xM2c89siXg0nHvlmYsQ2zkLASvVMBisZm5lF3gFDqfF2xonNStDJyMpvaOBe0a1Edxmqrf2E0HBdmy9QyZaeg==, tarball: https://registry.npmjs.org/unfetch/-/unfetch-5.0.0.tgz}
@@ -9068,8 +9050,8 @@ packages:
engines: {node: '>= 10.13.0'}
hasBin: true
webpack-sources@3.5.0:
resolution: {integrity: sha512-HPuy+uuoTCaaoEoI1LQ3JN9+vrPBvEesnnX1jADHy728cHSMlq4wUc4afYqahq2B1mhQVZxCXOkNTnXltr+2vQ==, tarball: https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.5.0.tgz}
webpack-sources@3.4.1:
resolution: {integrity: sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==, tarball: https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.4.1.tgz}
engines: {node: '>=10.13.0'}
webpack@5.106.2:
@@ -9257,14 +9239,14 @@ snapshots:
'@radix-ui/popper': 0.0.10
react: 18.3.1
'@alita/plugins@3.5.5(@babel/core@7.23.6)(@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)':
dependencies:
'@alita/babel-transform-jsx-class': 0.0.2
'@alita/inspx': 0.0.2(react@18.3.1)
'@alita/request': 3.1.2
'@alita/types': 3.1.2
'@umijs/bundler-utils': 4.4.11
'@umijs/plugins': 4.4.11(@babel/core@7.23.6)(@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.4.11(@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/utils': 4.4.11
ahooks: 3.9.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
antd-mobile-alita: 2.3.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -10134,90 +10116,90 @@ snapshots:
dependencies:
'@babel/types': 7.29.0
'@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.6)':
'@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.6)':
'@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.6)':
'@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.23.6)':
'@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.23.6)':
'@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.6)':
'@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.6)':
'@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.23.6)':
'@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.6)':
'@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.6)':
'@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.6)':
'@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.6)':
'@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.6)':
'@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.6)':
'@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.23.6)':
'@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.6)':
'@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/helper-plugin-utils': 7.28.6
'@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.23.6)':
'@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.29.0)':
dependencies:
'@babel/core': 7.23.6
'@babel/helper-module-transforms': 7.28.6(@babel/core@7.23.6)
'@babel/core': 7.29.0
'@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
'@babel/helper-plugin-utils': 7.28.6
'@babel/helper-simple-access': 7.27.1
transitivePeerDependencies:
@@ -10808,7 +10790,7 @@ snapshots:
'@formatjs/intl-utils@2.3.0': {}
'@gpustack/core-ui@1.0.32(czdvzceysqw7iv6pct2ucnb23e)':
'@gpustack/core-ui@1.0.10(czdvzceysqw7iv6pct2ucnb23e)':
dependencies:
'@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)
@@ -11883,10 +11865,6 @@ snapshots:
dependencies:
undici-types: 7.19.2
'@types/node@25.9.1':
dependencies:
undici-types: 7.24.6
'@types/normalize-package-data@2.4.4': {}
'@types/parse-json@4.0.2': {}
@@ -11907,31 +11885,26 @@ snapshots:
'@types/react-router-dom@4.3.5':
dependencies:
'@types/history': 5.0.0
'@types/react': 18.3.29
'@types/react': 18.3.28
'@types/react-router': 5.1.20
'@types/react-router-redux@5.0.27':
dependencies:
'@types/history': 4.7.11
'@types/react': 18.3.29
'@types/react': 18.3.28
'@types/react-router': 5.1.20
redux: 4.2.1
'@types/react-router@5.1.20':
dependencies:
'@types/history': 4.7.11
'@types/react': 18.3.29
'@types/react': 18.3.28
'@types/react@18.3.28':
dependencies:
'@types/prop-types': 15.7.15
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/semver@7.7.1': {}
@@ -12281,11 +12254,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@umijs/bundler-utoopack@4.6.51(styled-jsx@5.1.7(@babel/core@7.23.6)(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))':
dependencies:
'@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))
'@utoo/pack': 1.4.3(less-loader@11.1.0(less@4.1.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)))(less@4.1.3)(postcss@8.5.14)(resolve-url-loader@5.0.0)(sass-loader@13.2.0(sass@1.54.0)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)))(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.23.6)(react@18.3.1))
'@utoo/pack': 1.4.3(less-loader@11.1.0(less@4.1.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)))(less@4.1.3)(postcss@8.5.14)(resolve-url-loader@5.0.0)(sass-loader@13.2.0(sass@1.54.0)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)))(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))
compression: 1.8.1
connect-history-api-fallback: 2.0.0
cors: 2.8.6
@@ -12314,18 +12287,18 @@ snapshots:
- webpack-hot-middleware
- 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.48.0)':
'@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)':
dependencies:
'@svgr/core': 6.5.1
'@umijs/bundler-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.48.0))
'@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))
core-js: 3.34.0
less: 4.1.3
postcss-preset-env: 7.5.0(postcss@8.5.14)
rollup-plugin-visualizer: 5.9.0(rollup@3.30.0)
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.48.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)
transitivePeerDependencies:
- '@types/node'
- lightningcss
@@ -12553,14 +12526,14 @@ snapshots:
- supports-color
- typescript
'@umijs/max@4.6.51(@babel/core@7.23.6)(@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.23.6)(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/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))':
dependencies:
'@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.23.6)(@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)
eslint: 8.35.0
stylelint: 14.8.2
umi: 4.6.51(@babel/core@7.23.6)(@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.23.6)(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))
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))
transitivePeerDependencies:
- '@babel/core'
- '@rspack/core'
@@ -12642,7 +12615,7 @@ snapshots:
dependencies:
tsx: 3.12.2
'@umijs/plugins@4.4.11(@babel/core@7.23.6)(@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.4.11(@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)':
dependencies:
'@ahooksjs/use-request': 2.8.15(react@18.3.1)
'@ant-design/antd-theme-variable': 1.0.0
@@ -12657,7 +12630,7 @@ snapshots:
antd-dayjs-webpack-plugin: 1.0.6(dayjs@1.11.20)
axios: 0.27.2
babel-plugin-import: 1.13.8
babel-plugin-styled-components: 2.1.4(@babel/core@7.23.6)(styled-components@6.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
babel-plugin-styled-components: 2.1.4(@babel/core@7.29.0)(styled-components@6.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
dayjs: 1.11.20
dva-core: 2.0.4(redux@4.2.1)
dva-immer: 1.0.2(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
@@ -12687,7 +12660,7 @@ snapshots:
- react-native
- supports-color
'@umijs/plugins@4.6.51(@babel/core@7.23.6)(@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)':
dependencies:
'@ahooksjs/use-request': 2.8.15(react@18.3.1)
'@ant-design/antd-theme-variable': 1.0.0
@@ -12702,7 +12675,7 @@ snapshots:
antd-dayjs-webpack-plugin: 1.0.6(dayjs@1.11.20)
axios: 0.27.2
babel-plugin-import: 1.13.8
babel-plugin-styled-components: 2.1.4(@babel/core@7.23.6)(styled-components@6.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
babel-plugin-styled-components: 2.1.4(@babel/core@7.29.0)(styled-components@6.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
dayjs: 1.11.20
dva-core: 2.0.4(redux@4.2.1)
dva-immer: 1.0.2(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
@@ -12732,7 +12705,7 @@ snapshots:
- react-native
- supports-color
'@umijs/plugins@4.6.51(@babel/core@7.23.6)(@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)':
dependencies:
'@ahooksjs/use-request': 2.8.15(react@18.3.1)
'@ant-design/antd-theme-variable': 1.0.0
@@ -12747,7 +12720,7 @@ snapshots:
antd-dayjs-webpack-plugin: 1.0.6(dayjs@1.11.20)
axios: 0.27.2
babel-plugin-import: 1.13.8
babel-plugin-styled-components: 2.1.4(@babel/core@7.23.6)(styled-components@6.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
babel-plugin-styled-components: 2.1.4(@babel/core@7.29.0)(styled-components@6.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
dayjs: 1.11.20
dva-core: 2.0.4(redux@4.2.1)
dva-immer: 1.0.2(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
@@ -12777,7 +12750,7 @@ snapshots:
- react-native
- 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.23.6)(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/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))':
dependencies:
'@iconify/utils': 2.1.1
'@stagewise/toolbar': 0.6.2
@@ -12787,8 +12760,8 @@ snapshots:
'@umijs/bundler-esbuild': 4.6.51
'@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-utoopack': 4.6.51(styled-jsx@5.1.7(@babel/core@7.23.6)(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.48.0)
'@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-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/did-you-know': 1.0.4
@@ -12870,13 +12843,13 @@ snapshots:
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)
'@umijs/request-record@1.1.4(umi@4.6.51(@babel/core@7.23.6)(@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.23.6)(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)))':
'@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)))':
dependencies:
chokidar: 3.6.0
express: 4.22.1
lodash: 4.18.1
prettier: 2.8.8
umi: 4.6.51(@babel/core@7.23.6)(@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.23.6)(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))
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))
transitivePeerDependencies:
- supports-color
@@ -12892,13 +12865,13 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@umijs/test@4.6.51(@babel/core@7.23.6)':
'@umijs/test@4.6.51(@babel/core@7.29.0)':
dependencies:
'@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.23.6)
'@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.29.0)
'@jest/types': 27.5.1
'@umijs/bundler-utils': 4.6.51
'@umijs/utils': 4.6.51
babel-jest: 29.7.0(@babel/core@7.23.6)
babel-jest: 29.7.0(@babel/core@7.29.0)
esbuild: 0.21.4
identity-obj-proxy: 3.0.0
isomorphic-unfetch: 4.0.2
@@ -13018,7 +12991,7 @@ snapshots:
'@utoo/pack-win32-x64-msvc@1.4.3':
optional: true
'@utoo/pack@1.4.3(less-loader@11.1.0(less@4.1.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)))(less@4.1.3)(postcss@8.5.14)(resolve-url-loader@5.0.0)(sass-loader@13.2.0(sass@1.54.0)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)))(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.23.6)(react@18.3.1))':
'@utoo/pack@1.4.3(less-loader@11.1.0(less@4.1.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)))(less@4.1.3)(postcss@8.5.14)(resolve-url-loader@5.0.0)(sass-loader@13.2.0(sass@1.54.0)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)))(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))':
dependencies:
'@babel/code-frame': 7.22.5
'@hono/node-server': 1.19.14(hono@4.12.18)
@@ -13039,7 +13012,7 @@ snapshots:
sass-loader: 13.2.0(sass@1.54.0)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
semver: 7.8.0
send: 0.17.1
styled-jsx: 5.1.7(@babel/core@7.23.6)(react@18.3.1)
styled-jsx: 5.1.7(@babel/core@7.29.0)(react@18.3.1)
ws: 8.20.0
optionalDependencies:
'@utoo/pack-darwin-arm64': 1.4.3
@@ -13054,13 +13027,13 @@ snapshots:
- supports-color
- 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.48.0))':
'@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))':
dependencies:
'@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)
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.48.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)
transitivePeerDependencies:
- supports-color
@@ -13555,13 +13528,13 @@ snapshots:
transitivePeerDependencies:
- debug
babel-jest@29.7.0(@babel/core@7.23.6):
babel-jest@29.7.0(@babel/core@7.29.0):
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@jest/transform': 29.7.0
'@types/babel__core': 7.20.5
babel-plugin-istanbul: 6.1.1
babel-preset-jest: 29.6.3(@babel/core@7.23.6)
babel-preset-jest: 29.6.3(@babel/core@7.29.0)
chalk: 4.1.2
graceful-fs: 4.2.11
slash: 3.0.0
@@ -13601,9 +13574,9 @@ snapshots:
cosmiconfig: 7.1.0
resolve: 1.22.12
babel-plugin-named-asset-import@0.3.8(@babel/core@7.23.6):
babel-plugin-named-asset-import@0.3.8(@babel/core@7.29.0):
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
babel-plugin-react-compiler@0.0.0-experimental-c23de8d-20240515:
dependencies:
@@ -13615,11 +13588,11 @@ snapshots:
zod: 3.25.76
zod-validation-error: 2.1.0(zod@3.25.76)
babel-plugin-styled-components@2.1.4(@babel/core@7.23.6)(styled-components@6.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
babel-plugin-styled-components@2.1.4(@babel/core@7.29.0)(styled-components@6.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
dependencies:
'@babel/helper-annotate-as-pure': 7.27.3
'@babel/helper-module-imports': 7.28.6
'@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.23.6)
'@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0)
lodash: 4.18.1
picomatch: 2.3.2
styled-components: 6.1.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -13627,30 +13600,30 @@ snapshots:
- '@babel/core'
- supports-color
babel-preset-current-node-syntax@1.2.0(@babel/core@7.23.6):
babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0):
dependencies:
'@babel/core': 7.23.6
'@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.6)
'@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.23.6)
'@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.6)
'@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.6)
'@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.23.6)
'@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.6)
'@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.6)
'@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.6)
'@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.6)
'@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.6)
'@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.6)
'@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.6)
'@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.6)
'@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.6)
'@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.6)
'@babel/core': 7.29.0
'@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0)
'@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0)
'@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0)
'@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0)
'@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0)
'@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0)
'@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0)
'@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0)
'@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0)
'@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0)
'@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0)
'@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0)
'@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0)
'@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0)
'@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0)
babel-preset-jest@29.6.3(@babel/core@7.23.6):
babel-preset-jest@29.6.3(@babel/core@7.29.0):
dependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
babel-plugin-jest-hoist: 29.6.3
babel-preset-current-node-syntax: 1.2.0(@babel/core@7.23.6)
babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0)
babel-runtime-jsx-plus@0.1.5: {}
@@ -14672,11 +14645,6 @@ snapshots:
graceful-fs: 4.2.11
tapable: 2.3.3
enhanced-resolve@5.22.0:
dependencies:
graceful-fs: 4.2.11
tapable: 2.3.3
enhanced-resolve@5.9.3:
dependencies:
graceful-fs: 4.2.11
@@ -16390,7 +16358,7 @@ snapshots:
jest-worker@27.5.1:
dependencies:
'@types/node': 25.9.1
'@types/node': 25.6.2
merge-stream: 2.0.0
supports-color: 8.1.1
@@ -16410,9 +16378,9 @@ snapshots:
jiti@2.7.0: {}
jotai@2.20.0(@babel/core@7.23.6)(@babel/template@7.28.6)(@types/react@18.3.28)(react@18.3.1):
jotai@2.20.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@18.3.28)(react@18.3.1):
optionalDependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
'@babel/template': 7.28.6
'@types/react': 18.3.28
react: 18.3.1
@@ -19805,12 +19773,12 @@ snapshots:
css-to-react-native: 3.2.0
react-dom: 18.3.1(react@18.3.1)
styled-jsx@5.1.7(@babel/core@7.23.6)(react@18.3.1):
styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1):
dependencies:
client-only: 0.0.1
react: 18.3.1
optionalDependencies:
'@babel/core': 7.23.6
'@babel/core': 7.29.0
stylelint-config-recommended@7.0.0(stylelint@14.8.2):
dependencies:
@@ -19960,7 +19928,7 @@ snapshots:
'@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
schema-utils: 4.3.3
terser: 5.48.0
terser: 5.47.1
webpack: 5.106.2(lightningcss@1.22.1)(postcss@8.5.14)
optionalDependencies:
lightningcss: 1.22.1
@@ -19973,13 +19941,6 @@ snapshots:
commander: 2.20.3
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:
dependencies:
'@istanbuljs/schema': 0.1.6
@@ -20164,12 +20125,12 @@ snapshots:
ua-parser-js@0.7.41: {}
umi-presets-pro@2.0.3(@babel/core@7.23.6)(@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.23.6)(@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.23.6)(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))):
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))):
dependencies:
'@alita/plugins': 3.5.5(@babel/core@7.23.6)(@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/plugins': 4.6.51(@babel/core@7.23.6)(@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.23.6)(@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.23.6)(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)))
'@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)))
swagger-ui-dist: 4.19.1
transitivePeerDependencies:
- '@babel/core'
@@ -20193,17 +20154,17 @@ snapshots:
isomorphic-fetch: 2.2.1
qs: 6.15.1
umi@4.6.51(@babel/core@7.23.6)(@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.23.6)(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)):
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)):
dependencies:
'@babel/runtime': 7.23.6
'@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/core': 4.6.51
'@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.23.6)(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/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/renderer-react': 4.6.51(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@umijs/server': 4.6.51
'@umijs/test': 4.6.51(@babel/core@7.23.6)
'@umijs/test': 4.6.51(@babel/core@7.29.0)
'@umijs/utils': 4.6.51
prettier-plugin-organize-imports: 3.2.4(prettier@3.8.3)(typescript@5.9.3)
prettier-plugin-packagejson: 2.4.3(prettier@3.8.3)
@@ -20247,17 +20208,17 @@ snapshots:
- webpack-hot-middleware
- webpack-plugin-serve
umi@4.6.51(@babel/core@7.23.6)(@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.23.6)(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)):
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)):
dependencies:
'@babel/runtime': 7.23.6
'@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/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/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.23.6)(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/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/renderer-react': 4.6.51(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@umijs/server': 4.6.51
'@umijs/test': 4.6.51(@babel/core@7.23.6)
'@umijs/test': 4.6.51(@babel/core@7.29.0)
'@umijs/utils': 4.6.51
prettier-plugin-organize-imports: 3.2.4(prettier@3.8.3)(typescript@5.9.3)
prettier-plugin-packagejson: 2.4.3(prettier@3.8.3)
@@ -20312,8 +20273,6 @@ snapshots:
undici-types@7.19.2: {}
undici-types@7.24.6: {}
unfetch@5.0.0: {}
unified@11.0.5:
@@ -20498,7 +20457,7 @@ snapshots:
'@types/unist': 3.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.48.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):
dependencies:
esbuild: 0.18.20
postcss: 8.5.14
@@ -20509,7 +20468,7 @@ snapshots:
less: 4.1.3
lightningcss: 1.22.1
sass: 1.54.0
terser: 5.48.0
terser: 5.47.1
vm-browserify@1.1.2: {}
@@ -20571,7 +20530,7 @@ snapshots:
- bufferutil
- utf-8-validate
webpack-sources@3.5.0: {}
webpack-sources@3.4.1: {}
webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14):
dependencies:
@@ -20585,7 +20544,7 @@ snapshots:
acorn-import-phases: 1.0.4(acorn@8.16.0)
browserslist: 4.28.2
chrome-trace-event: 1.0.4
enhanced-resolve: 5.22.0
enhanced-resolve: 5.21.2
es-module-lexer: 2.1.0
eslint-scope: 5.1.1
events: 3.3.0
@@ -20598,7 +20557,7 @@ snapshots:
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))
watchpack: 2.5.1
webpack-sources: 3.5.0
webpack-sources: 3.4.1
transitivePeerDependencies:
- '@minify-html/node'
- '@swc/core'
-102
View File
@@ -1,102 +0,0 @@
#!/bin/bash
#
# rebrand: 把面向用户的品牌标识从 GPUStack 批量替换为 MesaStack(或自定义品牌)。
#
# 设计目标:上游每次更新后,在新的 v<version>-lofyer 分支上跑一次即可重新应用品牌定制。
#
# 只替换「独立的品牌词」FROM,并刻意跳过所有功能性引用:
# - 小写 `gpustack` —— npm 包名 (@gpustack/core-ui)、URL、路径、k8s 命名空间、author(大小写敏感,天然不匹配)
# - 全大写 `GPUSTACK` —— 常量/环境变量/全局 (GPUSTACK_API_BASE_URL, __GPUSTACK_*__, GPUSTACK_UI_*)(大小写敏感,不匹配)
# - 代码标识符 —— getGPUStackPlugin / GPUStackVersionAtom / GPUStackPluginManager / GPUStackLogo 等
# (FROM 紧邻字母时视为标识符的一部分,跳过)
# - HTTP 头 X-GPUStack-* —— 后端契约 (如 X-GPUStack-Model),跳过
#
# 匹配规则:FROM 前后都不是字母(独立单词),且不是 `X-` 前缀的头名。
#
# 环境变量:
# FROM 源品牌词(默认 GPUStack
# TO 目标品牌词(默认 MesaStack
# DRY_RUN 设为 1 时只预览将改动的行,不写文件
#
set -e
FROM="${FROM:-GPUStack}"
TO="${TO:-MesaStack}"
DRY_RUN="${DRY_RUN:-0}"
log() { echo -e "\033[1;34m[rebrand]\033[0m $*"; }
# 大小写敏感、单词边界、排除 X- 头前缀的 Perl 正则。
# (?<![A-Za-z]) 前面不是字母 (?<!X-) 不是 X- 头 (?![A-Za-z]) 后面不是字母
# FROM 为纯字母品牌词(无正则元字符),故直接拼接,不用 \Q\E
# —— \Q\E 在「经变量插值进正则」时不会被求值,反而会破坏匹配。
PATTERN="(?<![A-Za-z])(?<!X-)${FROM}(?![A-Za-z])"
# 行级排除:某些 FROM 出现在「与后端契约绑定的字符串」里,改了会破坏运行时逻辑,
# 即使是独立单词也必须整行跳过。命中下列任一正则的行不替换。
# 已知例外:
# - llmodels/hooks/index.ts 用 startsWith() 比对后端返回的英文兼容性消息
# "... does not exist on the GPUStack server ..."),后端仍发 GPUStack,前端不能改。
SKIP_LINE_PATTERNS=(
'does not exist on the .*server. It'"'"'s recommended'
)
skip_line() {
local line="$1" pat
for pat in "${SKIP_LINE_PATTERNS[@]}"; do
if echo "$line" | grep -qP "$pat"; then return 0; fi
done
return 1
}
# 待处理的已跟踪文本文件(排除 lockfile 与本脚本自身)。
mapfile -t files < <(
git ls-files -- \
'*.ts' '*.tsx' '*.js' '*.jsx' '*.json' '*.less' '*.html' '*.md' \
| grep -v 'pnpm-lock.yaml'
)
log "品牌替换: '${FROM}' -> '${TO}'"
log "候选文件: ${#files[@]}"
# 把行级排除合并成一个 perl 正则,供预览与替换共用(经环境变量传入,免去转义)。
SKIP_RE=""
for pat in "${SKIP_LINE_PATTERNS[@]}"; do
SKIP_RE="${SKIP_RE:+${SKIP_RE}|}(?:${pat})"
done
export REBRAND_SKIP_RE="${SKIP_RE}"
export REBRAND_PATTERN="${PATTERN}"
export REBRAND_TO="${TO}"
# 统计命中行(预览/确认用),已扣除被行级排除的行。
log "命中行预览(最多 40 行):"
grep -rnP "${PATTERN}" "${files[@]}" 2>/dev/null \
| { [[ -n "${SKIP_RE}" ]] && grep -vP "${SKIP_RE}" || cat; } \
| head -40 || true
total=$(grep -rnP "${PATTERN}" "${files[@]}" 2>/dev/null \
| { [[ -n "${SKIP_RE}" ]] && grep -vP "${SKIP_RE}" || cat; } | wc -l)
log "命中总行数: ${total}"
if [[ -n "${SKIP_RE}" ]]; then
skipped=$(grep -rnP "${PATTERN}" "${files[@]}" 2>/dev/null | grep -cP "${SKIP_RE}" || true)
log "行级排除(后端契约,保留 ${FROM}: ${skipped} 行"
fi
if [[ "${DRY_RUN}" == "1" ]]; then
log "DRY_RUN=1:未写入任何文件。"
exit 0
fi
# 实际替换(in-place)。命中行级排除的行整行跳过。
changed=0
for f in "${files[@]}"; do
if grep -qP "${PATTERN}" "$f" 2>/dev/null; then
perl -i -pe '
my $skip = $ENV{REBRAND_SKIP_RE};
next if length($skip) && /$skip/;
s/$ENV{REBRAND_PATTERN}/$ENV{REBRAND_TO}/g;
' "$f"
changed=$((changed + 1))
fi
done
log "已修改文件数: ${changed}"
log "完成。请用 'git diff' 复核改动。"
-83
View File
@@ -1,83 +0,0 @@
#!/bin/bash
#
# sync-github: 从上游 GitHub 拉取最新源码,并全量镜像同步到 192.168.0.23 私有源。
#
# 默认行为(全量镜像 / 强制):
# 1. 确保存在 upstream remote 指向 GitHub(不存在则自动添加,URL 不一致则更新)。
# 2. 从 upstream 抓取所有分支与 tag(--prune 清理已删除的远端引用)。
# 3. 将上游每个分支强制推送到私有源同名分支(force push)。
# 4. 将上游所有 tag 强制推送到私有源。
#
# 可用环境变量:
# UPSTREAM_URL 上游 GitHub 仓库地址(默认 https://github.com/gpustack/gpustack-ui.git
# ORIGIN_REMOTE 私有源 remote 名称(默认 origin
# UPSTREAM_REMOTE 上游 remote 名称(默认 upstream
# PRUNE_BRANCHES 设为 1 时,删除私有源上「上游已不存在」的分支(真·镜像,破坏性,默认关闭)
# DRY_RUN 设为 1 时,仅打印将要执行的推送动作,不实际推送
#
set -e
UPSTREAM_URL="${UPSTREAM_URL:-https://github.com/gpustack/gpustack-ui.git}"
ORIGIN_REMOTE="${ORIGIN_REMOTE:-origin}"
UPSTREAM_REMOTE="${UPSTREAM_REMOTE:-upstream}"
PRUNE_BRANCHES="${PRUNE_BRANCHES:-0}"
DRY_RUN="${DRY_RUN:-0}"
log() { echo -e "\033[1;34m[sync-github]\033[0m $*"; }
warn() { echo -e "\033[1;33m[sync-github]\033[0m $*" >&2; }
run() {
if [[ "${DRY_RUN}" == "1" ]]; then
echo " (dry-run) git $*"
else
git "$@"
fi
}
# 1. 确保 upstream remote 指向 GitHub。
if git remote get-url "${UPSTREAM_REMOTE}" >/dev/null 2>&1; then
current_url=$(git remote get-url "${UPSTREAM_REMOTE}")
if [[ "${current_url}" != "${UPSTREAM_URL}" ]]; then
log "更新 ${UPSTREAM_REMOTE} 地址: ${current_url} -> ${UPSTREAM_URL}"
git remote set-url "${UPSTREAM_REMOTE}" "${UPSTREAM_URL}"
fi
else
log "添加 upstream remote: ${UPSTREAM_REMOTE} -> ${UPSTREAM_URL}"
git remote add "${UPSTREAM_REMOTE}" "${UPSTREAM_URL}"
fi
origin_url=$(git remote get-url "${ORIGIN_REMOTE}")
log "上游 (拉取): ${UPSTREAM_URL}"
log "私有源 (推送): ${origin_url}"
# 2. 抓取上游所有分支与 tag。
log "抓取上游分支与 tag..."
git fetch --prune --tags "${UPSTREAM_REMOTE}"
# 3. 逐个分支强制推送到私有源。
log "强制同步分支到私有源..."
upstream_branches=$(git for-each-ref --format='%(refname:strip=3)' "refs/remotes/${UPSTREAM_REMOTE}/" | grep -v '^HEAD$')
for branch in ${upstream_branches}; do
log " -> ${branch}"
run push --force "${ORIGIN_REMOTE}" \
"refs/remotes/${UPSTREAM_REMOTE}/${branch}:refs/heads/${branch}"
done
# 4. 强制同步所有 tag。
log "强制同步 tag 到私有源..."
run push --force --tags "${ORIGIN_REMOTE}"
# 5. 可选:删除私有源上、上游已不存在的分支(真·镜像)。
if [[ "${PRUNE_BRANCHES}" == "1" ]]; then
warn "PRUNE_BRANCHES=1:将删除私有源上上游已不存在的分支"
origin_branches=$(git ls-remote --heads "${ORIGIN_REMOTE}" | sed 's@.*refs/heads/@@')
for branch in ${origin_branches}; do
if ! echo "${upstream_branches}" | grep -qx "${branch}"; then
warn " 删除私有源分支: ${branch}"
run push "${ORIGIN_REMOTE}" --delete "${branch}"
fi
done
fi
log "同步完成。"
+1 -22
View File
@@ -1,10 +1,6 @@
import { applyAccessExtensions } from './access.extensions';
export default (initialState: {
currentUser?: Global.UserInfo;
hasKubernetesCluster?: boolean;
hasResourceEvents?: boolean;
}) => {
export default (initialState: { currentUser?: Global.UserInfo }) => {
const isPlatformAdmin = !!(
initialState &&
initialState.currentUser &&
@@ -15,16 +11,6 @@ export default (initialState: {
initialState.currentUser &&
!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:
// * `canSeeAdmin` — strictly platform admin (`users.is_admin`).
@@ -32,11 +18,6 @@ export default (initialState: {
// * `canSeeOrgAdmin` — admin-style menus that work cross-org
// (Dashboard, Resources, Models, Cluster Management). Defaults
// 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
// specific org context (member / group management). Defaults to
// `false`; extensions widen when both an org is selected AND
@@ -46,8 +27,6 @@ export default (initialState: {
return applyAccessExtensions({
canSeeAdmin: isPlatformAdmin,
canSeeOrgAdmin: isPlatformAdmin,
canSeeGpuService:
isPlatformAdmin || hasKubernetesCluster !== false || hasResourceEvents,
canManageCurrentOrg: false,
canSeeUser,
canDelete: true,
+3 -55
View File
@@ -1,9 +1,8 @@
import { userSettingsHelperAtom } from '@/atoms/settings';
import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user';
import { GPUStackVersionAtom, UpdateCheckAtom } from '@/atoms/user';
import { setAtomStorage } from '@/atoms/utils';
import { DEFAULT_ENTER_PAGE, GPUSTACK_API_BASE_URL } from '@/config/settings';
import { COLOR_PRIMARY } from '@/config/theme/constants';
import { getGPUStackPlugin } from '@/plugins';
import { enterprisePluginReady } from '@/plugins/enterprise-ready';
import { GPUStackPluginManager } from '@/plugins/manager';
import { requestConfig } from '@/request-config';
@@ -14,11 +13,6 @@ import {
} from '@/services/profile/apis';
import { fetchSystemConfig } from '@/services/system/query-system-config';
import { isOnline } from '@/utils';
import {
markInitialStateProbed,
probeAccessFlags
} from '@/utils/access-probes';
import { installTenantFetch } from '@/utils/install-fetch';
import {
IS_FIRST_LOGIN,
readState,
@@ -28,8 +22,6 @@ import '@gpustack/core-ui/style.css';
import { RequestConfig, history, request as umiRequest } from '@umijs/max';
import { message } from 'antd';
installTenantFetch();
// only for the first login and access from http://localhost
const checkDefaultPage = async (userInfo: any) => {
@@ -47,8 +39,6 @@ export async function getInitialState(): Promise<{
fetchUserInfo: () => Promise<Global.UserInfo>;
currentUser?: Global.UserInfo;
pluginData?: Record<string, any>;
hasKubernetesCluster?: boolean;
hasResourceEvents?: boolean;
}> {
const { location } = history;
@@ -93,36 +83,6 @@ export async function getInitialState(): Promise<{
getUpdateCheck();
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;
} catch (error: any) {
const data = error?.response?.data;
@@ -162,24 +122,12 @@ export async function getInitialState(): Promise<{
getAppVersionInfo();
if (![DEFAULT_ENTER_PAGE.login].includes(location.pathname)) {
const [userInfo, accessFlags] = await Promise.all([
fetchUserInfo(),
probeAccessFlags()
]);
// Record that the probes ran for an authenticated user this page load
// (the refresh path) so the layout doesn't re-probe. A failed
// fetch (empty user — e.g. unauthenticated deep link that bounces to
// login) is NOT marked: the user will log in via SPA afterwards and
// the layout becomes responsible for probing.
if (userInfo?.username) {
markInitialStateProbed();
}
const userInfo = await fetchUserInfo();
checkDefaultPage(userInfo);
return {
fetchUserInfo,
currentUser: userInfo,
pluginData,
...accessFlags
pluginData
};
}
return {
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

-1
View File
@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

@@ -1 +0,0 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>AlibabaCloud</title><path d="M14.752 4.64h5.274C22.242 4.64 24 6.475 24 8.691V15.8a3.947 3.947 0 01-3.974 3.975h-5.274l1.299-1.835 3.822-1.222c.688-.23 1.146-.918 1.146-1.605v-5.81c0-.687-.458-1.375-1.146-1.605L16.05 6.475l-1.3-1.835zM2.98 15.111c0 .688.46 1.376 1.147 1.606l3.822 1.146 1.3 1.835H3.974A3.947 3.947 0 010 15.723V8.69c0-2.216 1.758-4.05 3.975-4.05h5.273L7.95 6.474 4.127 7.697c-.688.23-1.146.918-1.146 1.606v5.808z" fill="#FF6A00"></path><path d="M16.051 11.213H8.025v1.835h8.026v-1.835z" fill="#FF6A00"></path></svg>

Before

Width:  |  Height:  |  Size: 656 B

-1
View File
@@ -1 +0,0 @@
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>BaiLian</title><path d="M6.336 8.919v6.162l5.335-3.083L6.337 8.92z" fill="#1C54E3"></path><path d="M21.394 5.288s-.006-.006-.01-.006L17.01 2.754 6.336 8.92l5.335 3.082 9.701-5.6.016-.01a.635.635 0 00.006-1.1v-.003z" fill="#AA9AFF"></path><path d="M21.71 12.465a.62.62 0 00-.316.085s-.006 0-.009.003l-4.375 2.528 5.05 2.915h.006a2.06 2.06 0 00.28-1.04v-3.855a.637.637 0 00-.636-.636z" fill="#00EAD1"></path><path d="M22.06 17.996l-5.05-2.915L6.34 21.242l4.27 2.465s.016.006.022.012a2.102 2.102 0 002.093 0c.006-.003.016-.006.022-.012l8.538-4.93c.003 0 .006-.003.01-.006.321-.183.589-.45.775-.772h-.006l-.004-.003z" fill="#00CEC9"></path><path d="M11.672 11.998l-5.336 3.083-1.444.832-3.605 2.083H1.28c.173.303.416.555.709.738l.078.044.016.01.02.012 4.232 2.442 10.671-6.161-5.335-3.082z" fill="#00EAD1"></path><path d="M12.74.29c-.1-.06-.208-.107-.315-.148-.02-.006-.038-.016-.057-.022a2.121 2.121 0 00-.7-.12c-.233 0-.457.038-.668.11l-.031.01a2.196 2.196 0 00-.372.17L2.068 5.222s-.003 0-.006.003c-.324.183-.592.451-.781.773h.006l5.049 2.918L17.01 2.758 12.74.29z" fill="#7347FF"></path><path d="M1.287 6.001H1.28A2.06 2.06 0 001 7.041v9.915c0 .378.1.735.28 1.043h.007l5.049-2.918V8.919l-5.05-2.918z" fill="#0423DA"></path></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

-1
View File
@@ -3,7 +3,6 @@
.ant-layout-sider-children {
border-inline: none;
border-radius: 0;
padding-inline-end: 0;
padding-block-end: 8px;
}
-7
View File
@@ -63,13 +63,6 @@ export const fromClusterCreationAtom = atom(false);
export const clusterSessionAtom = atom<{
firstAddWorker: boolean;
firstAddCluster: boolean;
presetClusterType?: 'model' | 'gpu';
// 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);
export const clusterDetailAtom = atom<ClusterListItem | null>(null);
+6
View File
@@ -0,0 +1,6 @@
import { ClusterListItem } from '@/pages/cluster-management/config/types';
import { atom } from 'jotai';
export const currentClusterAtom = atom<
(Partial<ClusterListItem> & { label?: string; value?: number }) | null
>(null);
-7
View File
@@ -55,10 +55,3 @@ export const userSettingsHelperAtom = atom(
}
);
export const hideModalTemporarilyAtom = atom<boolean>(false);
export const collapsedMenuGroupsAtom = atomWithStorage<string[]>(
'collapsedMenuGroups',
[],
undefined,
{ getOnInit: true }
);
+13 -55
View File
@@ -3,15 +3,6 @@ import { atomWithStorage } from 'jotai/utils';
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<{
version: string;
git_commit: string;
@@ -32,15 +23,16 @@ export const UpdateCheckAtom = atom<{
latest_version: ''
});
export const initialPasswordAtom = atom<string>('');
export const initialPasswordAtom = atomWithStorage<string>(
'initialPassword',
''
);
// Namespace the server creates for an Org's resources on each Kubernetes
// cluster. The format must match the backend's ``get_namespace_name``
// helper — ``gpustack-{name}`` — because the GPU-instance / storage CRDs
// helper — ``gpustack-{slug}`` — because the GPU-instance / storage CRDs
// (worker.gpustack.ai/v1) are namespaced and the server-side admission
// 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.
// keys off this exact name.
//
// Resolution path:
// 1. The Org the caller is currently acting under — the enterprise
@@ -83,61 +75,27 @@ const getStoredCurrentOrgId = (): number | null => {
// the caller's member orgs; ``allOrganizations`` is admin-only (every
// Org on the platform) so admin sessions can resolve any owner Org id.
// Both are checked because ``currentOrganizationId`` is null in the
// admin "All" view but a member org's ``name`` might still cover the
// admin "All" view but a member org's slug might still cover the
// cluster-owner fallback.
const ORG_CACHE_KEYS = ['organizationList', 'allOrganizations'] as const;
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 => {
const lookupOrgNamespace = (id: number | null): string | 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);
for (const key of ORG_CACHE_KEYS) {
try {
const raw = localStorage.getItem(key);
if (!raw) continue;
const list = JSON.parse(raw) as CachedOrg[];
const list = JSON.parse(raw) as Array<{ id: number; slug?: string }>;
if (!Array.isArray(list)) continue;
const match = list.find((item) => String(item?.id) === target);
if (match) return match;
if (match?.slug) return `gpustack-${match.slug}`;
} catch {
// ignore malformed cache; continue checking other keys
}
}
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());
};
+2 -7
View File
@@ -6,17 +6,12 @@ export const clearStorageUserSettings = () => {
const savedSettings = JSON.parse(
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(
'userSettings',
JSON.stringify({
...savedSettings,
hideAddResourceModal: false
hideAddResourceModal: false,
colorPrimary: undefined
})
);
} catch (error) {
+25 -2
View File
@@ -1,7 +1,9 @@
import { GPUStackVersionAtom } from '@/atoms/user';
import { getAtomStorage } from '@/atoms/utils';
import VersionInfo, { modalConfig } from '@/components/version-info';
import externalLinks from '@/constants/external-links';
import { useIntl } from '@umijs/max';
import { Divider, Typography } from 'antd';
import { Button, Divider, Modal, Typography } from 'antd';
import { createStyles } from 'antd-style';
import styled from 'styled-components';
@@ -31,10 +33,20 @@ const useStyles = createStyles(({ token, css }) => ({
const Footer: React.FC = () => {
const intl = useIntl();
const [modal, contextHolder] = Modal.useModal();
const { styles } = useStyles();
const showVersion = () => {
modal.info({
...modalConfig,
width: 460,
content: <VersionInfo intl={intl} />
});
};
return (
<>
{contextHolder}
<div className={styles.footer}>
<div className="footer-content">
<div className="footer-content-left">
@@ -51,7 +63,18 @@ const Footer: React.FC = () => {
</Typography.Link>
</CompanyWrapper>
<Divider orientation="vertical" />
<span>{getAtomStorage(GPUStackVersionAtom)?.version}</span>
<Button
type="link"
size="small"
href={externalLinks.documentation}
target="_blank"
>
{intl.formatMessage({ id: 'common.button.help' })}
</Button>
<Divider orientation="vertical" />
<Button type="link" size="small" onClick={showVersion}>
{getAtomStorage(GPUStackVersionAtom)?.version}
</Button>
</div>
</div>
</div>
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,12 +92,6 @@ declare namespace Global {
interface InitialStateType {
fetchUserInfo: () => Promise<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 };
+1 -1
View File
@@ -84,4 +84,4 @@ export const modelNameReg =
*/
export const validateLabelNameRegxFor63 =
/^(?![0-9])(?!.*--)[a-z](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
+2 -4
View File
@@ -54,8 +54,7 @@ export default {
itemHoverColor: 'rgba(0,0,0,1)',
itemColor: 'rgba(0,0,0,1)',
itemHoverBg: 'rgb(24 25 27)',
itemActiveBg: 'rgb(24 25 27)',
menuItemSelectedBg: '#292929'
itemActiveBg: 'rgb(24 25 27)'
},
Progress: {
lineBorderRadius: 4
@@ -109,7 +108,6 @@ export default {
fontSize: 14,
motion: true,
colorFill: '#0A0A0A',
colorBgBase: '#0A0A0A',
menuItemSelectedBg: '#292929'
colorBgBase: '#0A0A0A'
}
};
+1 -3
View File
@@ -31,7 +31,6 @@ export default {
rowSelectedBg: 'transparent',
headerSortActiveBg: 'transparent',
headerSortHoverBg: 'transparent',
headerSplitColor: '#e8e8e8',
headerBg: 'none'
},
Button: {
@@ -56,8 +55,7 @@ export default {
itemHoverColor: 'rgba(0,0,0,1)',
itemColor: 'rgba(0,0,0,1)',
itemHoverBg: 'rgba(0,0,0,0.04)',
itemActiveBg: 'rgba(0,0,0,0.04)',
menuItemSelectedBg: '#e8eaed'
itemActiveBg: 'rgba(0,0,0,0.04)'
},
Progress: {
lineBorderRadius: 3
+40 -27
View File
@@ -6,7 +6,6 @@
html {
--page-header-height: 56px;
--page-content-padding: 8px;
--app-banner-height: 0px;
--color-text-light-1: rgba(255, 255, 255, 90%);
--color-fill-1: var(--ant-color-bg-container);
--color-scrollbar-thumb: rgba(193, 193, 193, 80%);
@@ -171,6 +170,8 @@ body {
}
.ant-table .ant-table-container table {
// border-spacing: 0 20px;
.ant-table-thead th.ant-table-column-sort {
background-color: transparent;
@@ -236,11 +237,12 @@ body {
// ============== new theme style start ===============
.ant-pro-layout {
background-color: var(--color-fill-1);
height: 100%;
.ant-pro-sider-footer {
padding-block: 4px 0;
padding-left: 8px;
padding-block: 2px 0;
padding-left: 6px;
}
.ant-pro-sider-actions-list-item {
@@ -257,26 +259,46 @@ body {
border-block-end: none;
}
.ant-pro-sider-logo {
.collapse-btn {
color: var(--ant-color-text-tertiary);
:hover {
color: var(--ant-color-text);
.ant-pro-sider-logo-collapsed {
padding-left: 12px;
cursor: e-resize;
.collapse-wrap {
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-logo-collapsed {
padding-left: 6px;
.ant-pro-sider .ant-layout-sider-children {
// border-right: 1px solid var(--ant-color-split);
}
}
.ant-table .ant-table-tbody {
.ant-table-row {
border-radius: var(--table-td-radius);
.ant-table-content table {
.ant-table-tbody {
.ant-table-row {
border-radius: var(--table-td-radius);
> td {
border-bottom: 1px solid var(--ant-color-split);
> td {
background-color: unset;
border-bottom: 1px solid var(--ant-color-split);
}
}
}
}
@@ -325,6 +347,7 @@ body {
.ant-pro-layout-container {
overflow-x: auto;
min-height: 100vh;
// background-color: var(--ant-color-bg-container);
}
.ant-pro-sider {
@@ -638,15 +661,10 @@ body {
inset: 53px auto auto 0 !important;
border-radius: 0 0 var(--border-radius-base) var(--border-radius-base);
border-top: none;
.ant-cascader-menus {
align-items: stretch;
}
max-height: 240px;
min-height: 100px;
&.gpu-selector {
.ant-cascader-menu {
max-height: 400px;
height: unset;
}
.ant-cascader-menu:last-child {
flex: auto;
@@ -794,8 +812,6 @@ body {
}
.ant-pro-sider-logo + div {
display: flex;
overflow: hidden;
&::-webkit-scrollbar {
width: 0;
}
@@ -851,6 +867,3 @@ body {
padding-right: 8px;
padding-block: 8px;
}
.ant-select-multiple .ant-select-content {
height: 100%;
}
+33 -111
View File
@@ -1,132 +1,54 @@
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
import { clampChroma, formatHex, modeOklch, modeRgb, useMode } from 'culori/fn';
import useUserSettings from './use-user-settings';
import { converter, modeHsl, modeRgb, useMode } from 'culori/fn';
// Linear-style minimal/neutral palette: a TIGHT blue -> violet band in OKLCH
// (perceptually uniform), kept moderate-chroma so fills read as clean and gentle
// rather than candy-colored. We deliberately stay narrow and DON'T fan out to
// green/magenta — in this aesthetic series are separated by LIGHTNESS, not by
// spreading across the hue wheel. Every series (including the first) is generated
// from this one ramp, so the whole palette stays in a single cohesive color
// family — no special high-saturation brand color that clashes with the rest.
const COOL_HUE_START = 250; // blue
const COOL_HUE_END = 315; // violet (stops short of magenta/pink, stays neutral-cool)
const toHsl = converter('hsl');
const DEFAULT_BRAND_HUE = 211; // brand color
const COOL_HUE_START = 180;
const COOL_HUE_END = 280;
const COOL_HUE_RANGE = COOL_HUE_END - COOL_HUE_START;
const GOLDEN_RATIO_CONJUGATE = 0.618033988749895;
/**
* Vivid, distinct cool accents — for places that need a handful of "primary"
* colors, one per card/section (e.g. the summary trend cards), NOT a stacked
* multi-series palette. Every color is anchor-quality (bright + saturated) and
* spread evenly across the blue→violet band, so the set reads as several equally
* strong primaries rather than one bold + several washed-out fills.
*/
export function useCoolAccents() {
useMode(modeRgb);
useMode(modeOklch);
const { isDarkTheme } = useUserSettings();
return useMemoizedFn((count: number): string[] => {
if (count <= 0) return [];
const l = isDarkTheme ? 0.62 : 0.64;
const c = isDarkTheme ? 0.16 : 0.2;
const out: string[] = [];
for (let i = 0; i < count; i++) {
const t = count <= 1 ? 0 : i / (count - 1);
const hue = COOL_HUE_START + t * COOL_HUE_RANGE;
out.push(
formatHex(clampChroma({ mode: 'oklch', l, c, h: hue }, 'oklch'))
);
}
return out;
});
function colorStringToHue(input?: string): number | undefined {
if (!input) return undefined;
const color = toHsl(input);
if (!color || typeof color.h !== 'number') return undefined;
return color.h;
}
export default function useCoolColors() {
// const brandHue = useMemo(
// () => colorStringToHue(userSettings.colorPrimary) ?? DEFAULT_BRAND_HUE,
// [userSettings.colorPrimary]
// );
useMode(modeRgb);
useMode(modeOklch);
useMode(modeHsl);
const { isDarkTheme } = useUserSettings();
const brandHue = DEFAULT_BRAND_HUE;
return useMemoizedFn((count: number): string[] => {
if (count <= 0) return [];
// Moderate chroma: clean and crisp, but gentle (not neon). Too low reads as
// muddy/dirty; too high reads as harsh. Dark mode a touch lower so fills
// stay calm against the dark canvas.
const baseChroma = isDarkTheme ? 0.085 : 0.12;
const colors: string[] = [];
// First series is the "primary" anchor: SAME blue family (the bluest end of
// the ramp, nearest the brand hue) but clearly brighter and more saturated —
// a vivid, clean brand-blue that reads as the base color. The rest of the
// palette stays low-chroma, so the anchor pops as the primary while the
// family still feels cohesive.
const colors: string[] = [
formatHex(
clampChroma(
{
mode: 'oklch',
l: isDarkTheme ? 0.62 : 0.66,
c: isDarkTheme ? 0.16 : 0.2,
h: COOL_HUE_START
},
'oklch'
)
)
];
for (let i = 0; i < count; i++) {
let hue: number;
const rest = count - 1;
if (rest <= 0) return colors;
if (i === 0) {
hue = brandHue - 5;
} else {
const offset = (i * GOLDEN_RATIO_CONJUGATE) % 1;
hue = COOL_HUE_START + offset * COOL_HUE_RANGE;
// Separation is driven by LIGHTNESS, not hue. The hue band is narrow, so as
// the count grows we add lightness TIERS — each tier reuses the same narrow
// hue ramp at a distinct lightness level, multiplying how many separable
// colors fit while keeping the whole palette in one cohesive family.
const tiers = rest <= 6 ? 1 : rest <= 12 ? 2 : 3;
const steps = Math.ceil(rest / tiers);
if (Math.abs(hue - brandHue) < 5) {
hue = (hue + 10) % COOL_HUE_END;
}
}
// Distinct lightness levels per tier count (index 0 → 2 tiers, 1 → 3 tiers).
// Lighter, airier levels for a fresh/crisp feel; kept in the upper-mid range
// so fills stay clean and legible.
const lightTiers = isDarkTheme
? [
[0.68, 0.5],
[0.72, 0.6, 0.48]
]
: [
[0.82, 0.64],
[0.84, 0.72, 0.6]
];
// Single-tier light/dark zig-zag for the common small-count case.
const [lightLo, lightHi] = isDarkTheme ? [0.5, 0.68] : [0.64, 0.82];
const s = i === 0 ? 100 : 75 + (i % 3) * 5;
const l = i === 0 ? 50 : 55 + (i % 2) * 5;
for (let i = 0; i < rest; i++) {
// Cycle the tier on every step so consecutive series always differ in
// lightness — exactly where stacked bars are hardest to tell apart.
const tier = i % tiers;
const step = Math.floor(i / tiers);
// Even hue ramp across the narrow band. Each tier is offset by a fraction
// of a step so same-step colors in different tiers don't share a hue.
const t = steps <= 1 ? 0.5 : (step + tier / tiers) / steps;
const hue = COOL_HUE_START + t * COOL_HUE_RANGE;
// 1 tier (≤6 series): light/dark zig-zag. 2-3 tiers: the tier's level.
const lightness =
tiers === 1 ? (i % 2 ? lightLo : lightHi) : lightTiers[tiers - 2][tier];
// Clamp chroma into the sRGB gamut so values don't get distorted by a raw
// channel clip when serialized to hex.
colors.push(
formatHex(
clampChroma(
{ mode: 'oklch', l: lightness, c: baseChroma, h: hue },
'oklch'
)
)
);
colors.push(`hsl(${Math.round(hue)}, ${s}%, ${l}%)`);
}
return colors;
+3 -7
View File
@@ -18,7 +18,6 @@ export function useQueryDataList<
Response = Array<ListItem>
>(option: {
key: string;
manual?: boolean;
responseType?: 'array' | 'object';
fetchList: (
params: Params,
@@ -39,7 +38,6 @@ export function useQueryDataList<
fetchList,
getLabel,
getValue,
manual = true,
responseType = 'array',
errorMsg
} = option;
@@ -72,7 +70,7 @@ export function useQueryDataList<
return responseType === 'array' ? res.items || [] : res;
},
{
manual: manual,
manual: true,
debounceWait: option.debounceWait || 300,
onSuccess: () => {},
onError: (error) => {
@@ -107,18 +105,16 @@ export function useQueryDataList<
export function useQueryData<Detail, Params = any>(option: {
key: string;
delay?: number;
manual?: boolean;
fetchDetail: (params: Params, options?: any) => Promise<Detail>;
getData?: (response: Detail, params?: any) => any;
errorMsg?: string;
}): {
loading: boolean;
detailData: Detail;
manual?: boolean;
cancelRequest: () => void;
fetchData: (params: Params, extra?: any) => Promise<Detail>;
} {
const { key, fetchDetail, getData, errorMsg, delay, manual = true } = option;
const { key, fetchDetail, getData, errorMsg, delay } = option;
const axiosTokenRef = useRef<CancelTokenSource | null>(null);
const [detailData, setDetailData] = useState<Detail>({} as Detail);
@@ -146,7 +142,7 @@ export function useQueryData<Detail, Params = any>(option: {
return res;
},
{
manual: manual,
manual: true,
onSuccess: () => {},
onError: (error) => {
message.error(
-46
View File
@@ -1,46 +0,0 @@
import { useMemoizedFn } from 'ahooks';
import { useRef, useState } from 'react';
/**
* Centralizes the anti-double-submit + anti-deadlock logic shared by the
* "add/edit" modals.
*
* The lock has to straddle antd's async field validation, whose result comes
* back through two separate callbacks (`onFinish` / `onFinishFailed`). So the
* three wiring points below are intentional and map 1:1 to those anchors:
*
* - `guard` wrap the submit trigger (button / ModalFooter onOk). Blocks
* re-entry while a submit is already in flight, then fires submit.
* - `run` wrap the `onFinish` handler. Holds the lock + loading until the
* `onOk` request settles (success or error).
* - `release` pass as the form's `onFinishFailed`. Releases the lock when
* validation fails, otherwise the button would dead-lock.
*/
export default function useSubmitLock() {
const [loading, setLoading] = useState<boolean>(false);
const lockRef = useRef<boolean>(false);
const release = useMemoizedFn(() => {
setLoading(false);
lockRef.current = false;
});
const guard = useMemoizedFn((submit: () => void) => {
if (lockRef.current) {
return;
}
lockRef.current = true;
submit();
});
const run = useMemoizedFn(async (task: () => void | Promise<void>) => {
setLoading(true);
try {
await task();
} finally {
release();
}
});
return { loading, guard, run, release };
}
+2 -8
View File
@@ -1,4 +1,4 @@
import { PaginationKey, TABLE_SORT_DIRECTIONS } from '@/config/settings';
import { TABLE_SORT_DIRECTIONS } from '@/config/settings';
import useSetChunkRequest, {
createAxiosToken
} from '@/hooks/use-chunk-request';
@@ -8,6 +8,7 @@ import { handleBatchRequest } from '@/utils';
import _ from 'lodash';
import qs from 'query-string';
import { useEffect, useRef, useState } from 'react';
import { PaginationKey } from '../config/settings';
import { usePaginationStatus } from './use-pagination-status';
import { useTableMultiSort } from './use-table-sort';
@@ -37,7 +38,6 @@ export default function useTableFetch<T>(
key?: (typeof PaginationKey)[keyof typeof PaginationKey];
fetchAPI: (params: any, options?: any) => Promise<Global.PageResponse<T>>;
deleteAPI?: (id: number, params?: any) => Promise<any>;
afterDelete?: (id?: number | number[]) => void;
contentForDelete?: string;
defaultData?: any[];
events?: EventsType[];
@@ -49,7 +49,6 @@ export default function useTableFetch<T>(
const {
fetchAPI,
deleteAPI,
afterDelete,
contentForDelete,
API,
polling = false,
@@ -266,7 +265,6 @@ export default function useTableFetch<T>(
url: `${API}?${qs.stringify(_.pickBy(query, (val: any) => !!val))}`,
handler: updateHandler
});
// eslint-disable-next-line react-hooks/purity
triggerAtRef.current = Date.now();
} catch (error) {
// ignore
@@ -363,9 +361,6 @@ export default function useTableFetch<T>(
...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 =======
if (!updateManually) {
fetchData();
@@ -392,7 +387,6 @@ export default function useTableFetch<T>(
successIds.push(id);
}
);
afterDelete?.(successIds);
rowSelection.removeSelectedKeys(successIds);
fetchData();
return res;
+18 -14
View File
@@ -14,6 +14,7 @@ type EventsType = 'CREATE' | 'UPDATE' | 'DELETE' | 'INSERT';
export function useUpdateChunkedList(options: {
events?: EventsType[];
dataList?: any[];
triggerAt?: React.MutableRefObject<number>;
limit?: number;
onCreate?: (args: any) => void;
onUpdate?: (args: any) => void;
@@ -23,9 +24,9 @@ export function useUpdateChunkedList(options: {
filterFun?: (args: any) => boolean;
mapFun?: (args: any) => any;
computedID?: (d: object) => string;
isNewItem?: (item: any) => boolean;
}) {
const { events = ['CREATE', 'DELETE', 'UPDATE', 'INSERT'] } = options;
const { events = ['CREATE', 'DELETE', 'UPDATE', 'INSERT'], triggerAt } =
options;
const deletedIdsRef = useRef<Set<number | string>>(new Set());
const cacheDataListRef = useRef<any[]>(options.dataList || []);
const timerRef = useRef<any>(null);
@@ -70,14 +71,17 @@ export function useUpdateChunkedList(options: {
(sItem: any) => sItem.id === item.id
);
const updateItem = { ...item };
if (updateIndex === -1) {
if (updateIndex === -1 && !triggerAt?.current) {
acc.push(updateItem);
} else {
} else if (!triggerAt?.current) {
cacheDataListRef.current[updateIndex] = updateItem;
}
// only push items created after the watch started
if (options.isNewItem?.(item)) {
// TODO only push items created after triggerAt
if (
triggerAt?.current &&
Date.parse(item.created_at) >= triggerAt.current
) {
latestCreateList.push(updateItem);
}
@@ -89,10 +93,6 @@ export function useUpdateChunkedList(options: {
...cacheDataListRef.current
].slice(0, limit);
options.setDataList?.([...cacheDataListRef.current], {
createdIds: newDataList.map((item) => item.id)
});
options.onCreate?.(latestCreateList);
}
@@ -103,8 +103,10 @@ export function useUpdateChunkedList(options: {
cacheDataListRef.current = cacheDataListRef.current?.filter(
(item: any) => {
// collect deleted items
if (ids?.includes(item.id) && !options.isNewItem?.(item)) {
deletedList.push(item);
if (triggerAt?.current) {
if (ids?.includes(item.id)) {
deletedList.push(item);
}
}
return !ids?.includes(item.id);
}
@@ -131,8 +133,10 @@ export function useUpdateChunkedList(options: {
updateItem,
...cacheDataListRef.current.slice(0, limit - 1)
];
if (options.onUpdate && options.isNewItem?.(item)) {
options.onUpdate?.([updateItem]);
if (options.onUpdate && triggerAt?.current) {
if (Date.parse(item.created_at) >= triggerAt.current) {
options.onUpdate?.([updateItem]);
}
}
}
});
+2 -2
View File
@@ -63,7 +63,8 @@ export default function useWatchList<T = Record<string, any>>(API: string) {
listRequestTokenRef.current?.cancel?.();
listRequestTokenRef.current = createAxiosToken();
const params = {
page: -1
page: 1,
perPage: 100
};
const res: any = await queryAllDataList(params, {
token: listRequestTokenRef.current.token
@@ -85,7 +86,6 @@ export default function useWatchList<T = Record<string, any>>(API: string) {
return {
watchDataList,
setWatchDataList,
deleteItemFromCache: handleDeleteItemFromCache
};
}
+26 -8
View File
@@ -1,6 +1,8 @@
import { GPUStackVersionAtom, UpdateCheckAtom } from '@/atoms/user';
import PluginExtraField from '@/components/plugin-extra-fields';
import VersionInfo, { modalConfig } from '@/components/version-info';
import externalLinks from '@/constants/external-links';
import useBodyScroll from '@/hooks/use-body-scroll';
import { logout } from '@/pages/login/apis';
import { useModel } from '@@/plugin-model';
import {
@@ -11,7 +13,7 @@ import {
} from '@ant-design/icons';
import { DropdownActions, IconFont } from '@gpustack/core-ui';
import { history, useIntl, useNavigate } from '@umijs/max';
import { Avatar, Divider } from 'antd';
import { Avatar, Button, Divider, Modal } from 'antd';
import { useAtom } from 'jotai';
import { useMemo } from 'react';
import styled from 'styled-components';
@@ -95,9 +97,11 @@ const CustomItem = styled.div`
export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
const { isDarkTheme } = props;
const intl = useIntl();
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll();
const [modal, contextHolder] = Modal.useModal();
const [version] = useAtom(GPUStackVersionAtom);
const [updateCheck] = useAtom(UpdateCheckAtom);
const intl = useIntl();
const initialInfo = useModel('@@initialState') || {
initialState: undefined,
loading: false,
@@ -135,6 +139,16 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
return {};
}, [isDarkTheme]);
const showVersion = () => {
saveScrollHeight();
modal.info({
...modalConfig,
width: 460,
content: <VersionInfo intl={intl} />,
onCancel: restoreScrollHeight
});
};
const handleLogout = async () => {
await logout();
navigate(loginPath);
@@ -144,7 +158,7 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
{
key: 'site',
icon: <HomeOutlined />,
label: 'MesaStack',
label: 'GPUStack',
url: externalLinks.site
},
{
@@ -196,14 +210,14 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
key: 'settings',
label: (
<span className="flex flex-center">
<IconFont type="icon-preferences" />
<IconFont type="icon-settings-02" />
<span className="m-l-8" style={{ marginLeft: 8 }}>
{intl?.formatMessage?.({ id: 'common.preferences' })}
{intl?.formatMessage?.({ id: 'common.button.settings' })}
</span>
</span>
),
onClick: () => {
history.push('/preferences');
history.push('/profile');
}
}
]
@@ -244,6 +258,7 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
return (
<Wrapper>
{contextHolder}
<PluginExtraField name="OrgSwitcher" isDarkTheme={isDarkTheme} />
<div
style={{
@@ -251,13 +266,16 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
alignItems: 'center'
}}
>
<span
<Button
type="text"
size="small"
onClick={showVersion}
style={{
color: 'var(--ant-color-text-tertiary)'
}}
>
{version.version}
</span>
</Button>
{showUpgrade && (
<NewLabel>
<span className="text">
-128
View File
@@ -1,128 +0,0 @@
import { IconFont } from '@gpustack/core-ui';
import { Link, useLocation, useNavigate } from '@umijs/max';
import { Menu } from 'antd';
import { createStyles } from 'antd-style';
import React, { useMemo } from 'react';
interface MenuItem {
icon?: string;
selectedIcon?: string;
defaultIcon?: string;
children?: MenuItem[];
[key: string]: any;
}
interface HeaderMenuProps {
menuData: MenuItem[];
initialState?: Global.InitialStateType;
}
const useStyles = createStyles(({ css }) => {
return {
headerMenu: css`
flex: 1;
min-width: 0;
background: transparent;
border-bottom: none;
line-height: inherit;
&.ant-menu-horizontal {
border-bottom: none;
}
&.ant-menu-horizontal > .ant-menu-item::after,
&.ant-menu-horizontal > .ant-menu-submenu::after {
display: none;
}
.ant-menu-title-content {
display: inline-flex;
align-items: center;
gap: 8px;
}
.anticon {
font-size: 16px;
}
`
};
});
const isItemSelected = (item: MenuItem, pathname: string) => {
return (
pathname === item.path ||
(Array.isArray(item.subMenu) && item.subMenu.includes(pathname))
);
};
const HeaderMenu: React.FC<HeaderMenuProps> = (props) => {
const { menuData } = props;
const { styles } = useStyles();
const location = useLocation();
const navigate = useNavigate();
const buildLeaf = (item: MenuItem) => {
const selected = isItemSelected(item, location.pathname);
return {
key: item.path as string,
label: (
<Link
prefetch="intent"
to={(item.path as string).replace('/*', '')}
target={item.target}
>
<span className="flex-center gap-8">
<IconFont
type={selected ? item.selectedIcon || '' : item.defaultIcon || ''}
/>
<span>{item.name}</span>
</span>
</Link>
)
};
};
const items = useMemo(() => {
return menuData.map((item) => {
if (item.children && item.children.length > 0) {
return {
key: item.key,
label: item.name,
children: item.children.map((child) => buildLeaf(child))
};
}
return buildLeaf(item);
});
}, [menuData, location.pathname]);
const selectedKeys = useMemo(() => {
const keys: string[] = [];
for (const item of menuData) {
const leaves =
item.children && item.children.length > 0 ? item.children : [item];
for (const leaf of leaves) {
if (isItemSelected(leaf, location.pathname)) {
keys.push(leaf.path as string);
}
}
}
return keys;
}, [menuData, location.pathname]);
const handleClick = ({ key }: { key: string }) => {
if (key.startsWith('/')) {
navigate(key.replace('/*', ''));
}
};
return (
<Menu
className={styles.headerMenu}
mode="horizontal"
selectedKeys={selectedKeys}
items={items}
onClick={handleClick}
triggerSubMenuAction="hover"
/>
);
};
export default HeaderMenu;
+97 -102
View File
@@ -1,8 +1,6 @@
import { routeCacheAtom, setRouteCache } from '@/atoms/route-cache';
import { userAtom } from '@/atoms/user';
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 { DEFAULT_ENTER_PAGE, GPUSTACK_API_BASE_URL } from '@/config/settings';
import { COLOR_PRIMARY } from '@/config/theme';
@@ -12,7 +10,6 @@ import useUserSettings from '@/hooks/use-user-settings';
import useUserSettingsStorage from '@/hooks/use-user-settings-storage';
import useAddResource from '@/pages/dashboard/hooks/use-add-resource';
import { logout } from '@/pages/login/apis';
import { didInitialStateProbe, probeAccessFlags } from '@/utils/access-probes';
import {
readColumnSettings,
readState,
@@ -22,9 +19,12 @@ import {
import { useAccessMarkedRoutes } from '@@/plugin-access';
import { useModel } from '@@/plugin-model';
import { ProLayout } from '@ant-design/pro-components';
import { CoreUIProvider } from '@gpustack/core-ui';
import {
Access,
CoreUIProvider,
IconFont,
useOverlayScroller
} from '@gpustack/core-ui';
import {
Outlet,
dropByCacheKey,
getAllLocales,
@@ -39,18 +39,18 @@ import {
useNavigate,
type IRoute
} from '@umijs/max';
import { ConfigProvider, Modal, theme } from 'antd';
import { Button, ConfigProvider, Modal, theme } from 'antd';
import { useAtom } from 'jotai';
import 'overlayscrollbars/overlayscrollbars.css';
import { useEffect, useMemo, useRef } from 'react';
import { useMemo, useRef } from 'react';
import { PageContainerInner } from '../pages/_components/page-box';
import Exception from './Exception';
import './Layout.css';
import { LogoIcon } from './Logo';
import { LogoIcon, SLogoIcon } from './Logo';
import ErrorBoundary from './error-boundary';
import { ExtraContent } from './extraRender';
import HeaderMenu from './header-menu';
import { patchRoutes } from './runtime';
import SiderMenu from './sider-menu';
// Pages that use the page container in the page
const NO_CONTAINER_PAGES = [
@@ -64,14 +64,16 @@ const NO_CONTAINER_PAGES = [
'clusterCreate',
'benchmarkDetail',
'deployment',
'video'
'video',
'instances',
'storage'
];
const CHECK_RESOURCE_PATH = [
'/resources/workers',
'/resources/clusters/list',
'/resources/credentials',
'/resources/clusters/create'
'/cluster-management/clusters/list',
'/cluster-management/credentials',
'/cluster-management/clusters/create'
];
type NewRoute = IRoute & {
@@ -132,8 +134,11 @@ const mapRoutes = (routes: IRoute[], role: string) => {
};
export default (props: any) => {
const { initialize: initialize } = useOverlayScroller({
defer: false
});
const [, contextHolder] = Modal.useModal();
const { themeData, userSettings } = useUserSettings();
const { themeData, setUserSettings, userSettings } = useUserSettings();
const [userInfo] = useAtom(userAtom);
const [routeCache] = useAtom(routeCacheAtom);
const location = useLocation();
@@ -156,50 +161,6 @@ export default (props: any) => {
const { initialState, loading, setInitialState } = initialInfo;
const access = useAccess();
const probedForUserRef = useRef<string | null>(null);
// Backfill the access probes (cluster / resource-events) once we're in
// the authenticated shell. `getInitialState` runs only once at app
// boot and can't probe on the login page (no session => 401), so after
// a first login the flags arrive here as `undefined` — which the
// access predicate treats as "don't restrict", flashing GPU Service /
// the full Usage page on until a manual refresh. This layout mounts
// only post-auth (login is `layout:false`) and on every entry, so it's
// the reliable place to resolve them.
//
// The effect is keyed on the user IDENTITY, not on the flag values —
// gating on the flags is what made this fragile (on a refresh
// `getInitialState` commits `currentUser` and the flags in the same
// update, so a flag-gated effect sees them already-known and never
// fires). Whether to actually hit the network is decided by the
// module-scoped `didInitialStateProbe()` marker, which is true only
// when `getInitialState` already probed for an authenticated user this
// page load (the refresh path) — so we skip the duplicate request there
// but still probe on the SPA-login path. Keying on identity also
// re-probes correctly if the signed-in user changes.
const currentUser = initialState?.currentUser;
const username = currentUser?.username;
useEffect(() => {
if (!username || !setInitialState) {
return;
}
if (probedForUserRef.current === username) {
return;
}
probedForUserRef.current = username;
// The refresh path already resolved the flags inside `getInitialState`
// for this user — don't issue a duplicate probe.
if (didInitialStateProbe()) {
return;
}
probeAccessFlags().then((accessFlags) => {
setInitialState((prev: any) => ({
...prev,
...accessFlags
}));
});
}, [username, setInitialState]);
const userConfig = {
title: '',
@@ -238,6 +199,13 @@ export default (props: any) => {
const coreUISlots = useMemo(() => ({ ExtraContent }), []);
const handleToggleCollapse = (e: any) => {
e.stopPropagation();
setUserSettings({
...userSettings,
collapsed: !userSettings.collapsed
});
};
const newRoutes = filterRoutes(
// @ts-ignore
clientRoutes.filter((route) => route.id === 'max-tabs'),
@@ -267,19 +235,39 @@ export default (props: any) => {
return NO_CONTAINER_PAGES.includes(matchedRoute?.name as string);
}, [matchedRoute]);
const collapsed = useMemo(() => {
return userSettings.collapsed || false;
}, [userSettings.collapsed]);
const renderMenuHeader = (logo: React.ReactNode, title: React.ReactNode) => {
return <>{logo}</>;
return (
<>
{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 headerContentRender = (
headerProps: any,
defaultDom: React.ReactNode
) => {
return <HeaderMenu {...headerProps}></HeaderMenu>;
};
const actionsRender = () => {
return <ExtraContent isDarkTheme={userSettings.isDarkTheme} />;
const menuContentRender = (menuProps: any, defaultDom: React.ReactNode) => {
return <SiderMenu {...menuProps}></SiderMenu>;
};
const onPageChange = async (route: any) => {
@@ -333,6 +321,17 @@ export default (props: any) => {
navigate(pagepath);
};
const onCollapse = (value: boolean) => {
// only trigger by window resize
if (!value) {
return;
}
setUserSettings({
...userSettings,
collapsed: value
});
};
return (
<ConfigProvider
componentSize="large"
@@ -357,7 +356,7 @@ export default (props: any) => {
config={{
apiBaseUrl: GPUSTACK_API_BASE_URL,
theme: userSettings.theme,
iconUrl: '',
iconUrl: '//at.alicdn.com/t/c/font_4613488_r6z6oew38db.js',
isDarkTheme: userSettings.isDarkTheme,
defaultColorPrimary: COLOR_PRIMARY
}}
@@ -388,62 +387,58 @@ export default (props: any) => {
writeState
}}
slots={coreUISlots}
access={{ Access, useAccess }}
>
<DarkMask></DarkMask>
<ProLayout
fixSiderbar
fixedHeader
fixedHeader={false}
headerRender={false}
breadcrumbRender={false}
route={route}
location={location}
title={userConfig.title}
navTheme={userSettings.theme}
layout="top"
layout="side"
contentStyle={{
paddingBlock: 0,
paddingInline: 0
}}
openKeys={false}
disableMobile={true}
siderWidth={220}
onCollapse={onCollapse}
onMenuHeaderClick={onMenuHeaderClick}
menuHeaderRender={renderMenuHeader}
collapsed={userSettings.collapsed}
onPageChange={onPageChange}
formatMessage={formatMessage}
menu={{
locale: true
locale: true,
type: 'group'
}}
logo={<LogoIcon />}
headerContentRender={headerContentRender}
actionsRender={actionsRender}
splitMenus={true}
logo={userSettings.collapsed ? <SLogoIcon /> : <LogoIcon />}
menuContentRender={menuContentRender}
{...runtimeConfig}
ErrorBoundary={ErrorBoundary}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
height: '100%',
overflow: 'hidden'
}}
<Exception
route={matchedRoute}
notFound={runtimeConfig?.notFound}
noFound={runtimeConfig?.noFound}
unAccessible={runtimeConfig?.unAccessible}
noAccessible={runtimeConfig?.noAccessible}
>
<PluginExtraFields name="GlobalLicenseBanner" />
<Exception
route={matchedRoute}
notFound={runtimeConfig?.notFound}
noFound={runtimeConfig?.noFound}
unAccessible={runtimeConfig?.unAccessible}
noAccessible={runtimeConfig?.noAccessible}
>
{isNoContainerPage ? (
<Outlet />
) : (
<PageContainerInner>
<div>
<Outlet />
</div>
</PageContainerInner>
)}
</Exception>
</div>
{isNoContainerPage ? (
<Outlet />
) : (
<PageContainerInner>
<div>
<Outlet />
</div>
</PageContainerInner>
)}
</Exception>
{NoResourceModal}
{contextHolder}
</ProLayout>
+2 -2
View File
@@ -82,7 +82,7 @@ export const getRightRenderContent = (opts: {
{
key: 'site',
icon: <HomeOutlined />,
label: 'MesaStack',
label: 'GPUStack',
url: externalLinks.site
},
{
@@ -256,7 +256,7 @@ export const getRightRenderContent = (opts: {
</span>
),
onClick: () => {
history.push('/preferences');
history.push('/profile');
}
},
{
+152 -180
View File
@@ -1,11 +1,9 @@
import { collapsedMenuGroupsAtom } from '@/atoms/settings';
import { CaretDownOutlined } from '@ant-design/icons';
import { IconFont, OverlayScroller } from '@gpustack/core-ui';
import { IconFont } from '@gpustack/core-ui';
import { Link, useLocation } from '@umijs/max';
import { Tooltip } from 'antd';
import { createStyles, type FullToken } from 'antd-style';
import { useAtom } from 'jotai';
import React, { useMemo } from 'react';
import { createStyles } from 'antd-style';
import React, { useMemo, useState } from 'react';
interface MenuItem {
icon?: string;
@@ -22,143 +20,125 @@ interface SiderMenuProps {
initialState: Global.InitialStateType;
}
const useStyles = createStyles(
({ css, token }: { css: any; token: FullToken }) => {
console.log('useStyles', token);
const useStyles = createStyles(({ css, token }) => {
console.log('useStyles', token);
// @ts-ignore
const { Menu } = token;
// @ts-ignore
const { Menu } = token;
return {
siderMenu: css`
width: 100%;
&.sider-menu-collapsed {
.menu-item {
justify-content: center;
padding: 0;
}
}
.os-scrollbar-vertical .os-scrollbar-handle {
min-width: 4px;
max-width: 4px;
}
`,
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);
return {
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 {
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`
}
.anticon {
transform: scale(0.8);
}
.group-title-text {
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;
gap: 4px;
font-size: 12px;
color: var(--ant-color-text-tertiary);
&:hover {
background-color: ${Menu.itemHoverBg};
color: ${Menu.itemHoverColor};
}
&.menu-item-selected {
background-color: ${Menu.menuItemSelectedBg};
color: ${Menu.itemSelectedColor};
font-weight: 400;
}
.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`
&.menu-item-group-title-collapsed {
position: relative;
height: 1px;
margin-block: 6px;
background-color: ${token.colorSplit};
position: absolute;
left: -2px;
right: -2px;
`
};
}
);
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%;
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 { menuData, collapsed } = props;
const { menuData, collapsed, initialState } = props;
const is_admin = initialState?.currentUser?.is_admin || false;
const { styles, cx } = useStyles();
const location = useLocation();
const [storedCollapsedGroups, setCollapsedGroups] = useAtom(
collapsedMenuGroupsAtom
);
// atomWithStorage falls back to the initial value on JSON parse
// errors, but not when the stored value is valid JSON of another
// shape — normalize so array methods below can't throw.
const collapsedGroups = Array.isArray(storedCollapsedGroups)
? storedCollapsedGroups
: [];
const collapseKeys = useMemo(
() => new Set(collapsedGroups),
[collapsedGroups]
);
const [collapseKeys, setCollapseKeys] = useState<Set<string>>(new Set());
console.log('SiderMenu', location.pathname);
const dividerStyles = useMemo(() => {
if (collapsed) {
@@ -177,11 +157,14 @@ const SiderMenu: React.FC<SiderMenuProps> = (props) => {
const handleToggleGroup = (e: any, menuGroup: any) => {
e.stopPropagation();
setCollapsedGroups(
collapsedGroups.includes(menuGroup.key)
? collapsedGroups.filter((key) => key !== menuGroup.key)
: [...collapsedGroups, menuGroup.key]
);
console.log('handleToggleGroup', menuGroup.key);
if (collapseKeys.has(menuGroup.key)) {
collapseKeys.delete(menuGroup.key);
} else {
collapseKeys.add(menuGroup.key);
}
setCollapseKeys(new Set(collapseKeys));
};
const menuItemRender = (menuItem: MenuItem, key: string) => {
@@ -237,55 +220,44 @@ const SiderMenu: React.FC<SiderMenuProps> = (props) => {
'sider-menu-collapsed': collapsed
})}
>
<OverlayScroller
styles={{
wrapper: {
paddingInline: 0,
maxHeight: '100%'
}
}}
>
<div style={{ paddingRight: 8 }}>
{menuData.map((item: MenuItem, index: number) => (
<div key={item.key}>
{item.children && item.children.length > 0 ? (
<>
<div
className={cx(styles.groupTitle, {
'menu-item-group-title-collapsed': collapsed
})}
onClick={(e) => handleToggleGroup(e, item)}
>
{!collapsed ? (
<span className="group-title-text">
<span>{item.name}</span>
<CaretDownOutlined
rotate={collapseKeys.has(item.key) ? -90 : 0}
></CaretDownOutlined>
</span>
) : (
<span className={styles.line}></span>
)}
</div>
<div
className={cx(styles.menuItemGroup, {
'menu-item-group-collapsed': collapsed,
'menu-item-group-hidden':
!collapsed && collapseKeys.has(item.key)
})}
>
{item.children?.map((child: MenuItem) =>
menuItemRender(child, child.key)
)}
</div>
</>
) : (
menuItemRender(item, item.key)
)}
</div>
))}
{menuData.map((item: MenuItem, index: number) => (
<div key={item.key}>
{item.children && item.children.length > 0 ? (
<>
<div
className={cx(styles.groupTitle, {
'menu-item-group-title-collapsed': collapsed
})}
onClick={(e) => handleToggleGroup(e, item)}
>
{!collapsed ? (
<span className="group-title-text">
<span>{item.name}</span>
<CaretDownOutlined
rotate={collapseKeys.has(item.key) ? -90 : 0}
></CaretDownOutlined>
</span>
) : is_admin ? (
<span className={styles.line}></span>
) : null}
</div>
<div
className={cx(styles.menuItemGroup, {
'menu-item-group-collapsed': collapsed,
'menu-item-group-hidden':
!collapsed && collapseKeys.has(item.key)
})}
>
{item.children?.map((child: MenuItem) =>
menuItemRender(child, child.key)
)}
</div>
</>
) : (
menuItemRender(item, item.key)
)}
</div>
</OverlayScroller>
))}
</div>
);
};
+1 -1
View File
@@ -25,7 +25,7 @@ export default {
'ai.provider.ollama': 'Ollama',
'ai.provider.openai': 'OpenAI',
'ai.provider.openrouter': 'OpenRouter',
'ai.provider.qwen': 'Alibaba Cloud Model Studio',
'ai.provider.qwen': 'Qwen',
'ai.provider.spark': 'Spark',
'ai.provider.stepfun': 'StepFun',
'ai.provider.together-ai': 'TogetherAI',
+1 -3
View File
@@ -23,7 +23,5 @@ export default {
'apikeys.accessScope.inference': 'Inference APIs',
'apikeys.access.permissions': 'Access Permissions',
'apikeys.type.auto': 'Auto-generated',
'apikeys.type.custom': 'Custom',
'apikeys.button.ipConfig': 'IP Access Control',
'quotaLimits.button.title': 'Quota Limit'
'apikeys.type.custom': 'Custom'
};
-8
View File
@@ -23,14 +23,6 @@ export default {
'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.defaultBackendParameters': 'Default Backend Parameters',
'backend.form.flagFormat': 'Flag Format',
'backend.form.flagFormat.tips':
'The format applied between an option and its value. Leave empty to keep each parameter as entered, without normalizing it.',
'backend.form.flagFormat.space': 'Space Separated (--key value)',
'backend.form.flagFormat.equal': 'Equal Sign (--key=value)',
'backend.form.commonParameters': 'Common Backend Parameters',
'backend.form.commonParameters.tips':
'Shown as suggestions in the backend parameters input during deployment.',
'backend.form.versionConfig': 'Versions Config',
'backend.form.addParameter': 'Add Parameter',
'backend.form.noVersion': 'No versions added',
+3 -3
View File
@@ -37,10 +37,10 @@ export default {
'Stress test for long-context handling. Evaluates KV cache behavior, memory usage, and backend stability.',
'benchmark.form.profile.heavy.tips':
'Decode-heavy generation benchmark. Measures sustained decoding speed and output token throughput.',
'benchmark.table.filter.bygpu': 'Search by GPU',
'benchmark.table.filter.bymodel': 'Search by model',
'benchmark.table.filter.bygpu': 'Filter by GPU',
'benchmark.table.filter.bymodel': 'Filter by Model',
'benchmark.table.filter.bydataset': 'Filter by Dataset',
'benchmark.table.filter.byProfile': 'Filter by profile',
'benchmark.table.filter.byProfile': 'Filter by Profile',
'benchmark.table.avg': 'Avg',
'benchmark.table.columnSettings': 'Column Settings',
'benchmark.detail.summary.results': 'Test Results',
-15
View File
@@ -1,15 +0,0 @@
export default {
'billing.upsell.title': 'Billing is an Enterprise feature',
'billing.upsell.subtitle':
'Track spend, generate invoices, and enforce budgets across teams. Upgrade to MesaStack Enterprise to manage billing.',
'billing.upsell.featuresTitle': 'What you get in Enterprise',
'billing.upsell.feature.usage':
'See cost breakdowns by organization, user, and model',
'billing.upsell.feature.invoices':
'Generate invoices and export billing reports',
'billing.upsell.feature.budgets':
'Set budgets and spending limits with alerts',
'billing.upsell.feature.chargeback':
'Attribute and charge back usage to teams and projects',
'billing.upsell.cta': 'Learn about Enterprise'
};
+8 -46
View File
@@ -39,11 +39,9 @@ export default {
'clusters.workerpool.batchSize.desc':
'Number of workers created simultaneously in the Worker pool',
'clusters.create.addworker.tips':
'Please make sure the <a href={link} target="_blank">prerequisites</a> are met before executing the following command.',
'Please make sure the <a href={link} target="_blank">prerequisites</a> for {label} are met before executing the following command.',
'clusters.create.addCommand.tips':
'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':
'On the Kubernetes cluster that needs to be added, run the following command to join its nodes to the cluster.',
'cluster.create.checkEnv.tips':
@@ -69,14 +67,8 @@ export default {
'clusters.addworker.selectCluster.tips':
'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.multiTag': 'Multi-select',
'clusters.addworker.selectGPU.subtitle':
'You can select multiple GPU Vendors or none for CPU-only clusters',
'clusters.addworker.checkEnv': 'Check Environment',
'clusters.addworker.checkEnv.cpuOnlyTips':
'Use the following command to verify that the Kubernetes cluster has at least one ready node. You are registering a CPU-only cluster.',
'clusters.addworker.specifyArgs': 'Specify Arguments',
'clusters.addworker.dtkVersion': 'DTK Version',
'clusters.addworker.runCommand': 'Run Command',
'clusters.addworker.specifyWorkerIP': 'Worker IP',
'clusters.addworker.detectWorkerIP': 'Auto-detect Worker IP',
@@ -85,7 +77,7 @@ export default {
'clusters.addworker.detectWorkerAddress.tips':
'Defaults to Worker IP if not specified.',
'clusters.addworker.externalIP.tips':
'If running in a VPC or private network, please specify the Worker external address reachable by the MesaStack Server.',
'If running in a VPC or private network, please specify the Worker external address reachable by the GPUStack Server.',
'clusters.addworker.enterWorkerIP': 'Enter worker IP',
'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.',
'clusters.addworker.enterWorkerAddress': 'Enter worker external address',
@@ -113,20 +105,18 @@ export default {
'{count} new worker has been added to the cluster.',
'clusters.addworker.message.success_multiple':
'{count} new workers have been added to the cluster.',
'clusters.create.serverUrl': 'MesaStack Server URL',
'clusters.create.serverUrl': 'GPUStack Server URL',
'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.addworker.containerName': 'Worker Container Name',
'clusters.addworker.containerName.tips':
'Specify a name for the worker container.',
'clusters.addworker.dataVolume': 'MesaStack Data Volume',
'clusters.addworker.dataVolume': 'GPUStack Data Volume',
'clusters.addworker.dataVolume.tips':
'Specify a data storage path for MesaStack.',
'Specify a data storage path for GPUStack.',
'clusters.table.ip.internal': 'Internal',
'clusters.table.ip.external': 'External',
'clusters.form.serverUrl.tips':
'Specify an externally accessible MesaStack service URL if the worker cannot access MesaStack Server directly.',
'Specify an externally accessible GPUStack service URL if the worker cannot access GPUStack Server directly.',
'clusters.form.setDefault': 'Set as Default',
'clusters.form.setDefault.tips': 'Default for deployment.',
'clusters.addworker.noClusters': 'No available Docker clusters found',
@@ -144,7 +134,7 @@ export default {
'clusters.addworker.theadNotes-02':
'T-Head PPU uses the Container Device Interface (CDI) for device injection and requires the <span class="bold-text">/var/run/cdi</span> directory to be available for CDI generation.',
'clusters.addworker.nvidiaNotes':
'The built-in inference backends in MesaStack require <span class="bold-text">CUDA 12.8+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">570</span> or newer.',
'The built-in inference backends in GPUStack v2.1 require <span class="bold-text">CUDA 12.6+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">560</span> or newer.',
'clusters.volume.title': 'Volume Mounts',
'clusters.volume.name': 'Volume Name',
'clusters.volume.mountPath': 'Container Path',
@@ -168,33 +158,5 @@ export default {
'clusters.volume.pvc.readOnly': 'Read Only',
'clusters.volume.configMap.name': 'ConfigMap Name',
'clusters.volume.configMap.optional': 'Optional',
'clusters.volume.add': 'Add Volume Mount',
'clusters.systemDefaultContainerRegistry.title': 'Default Container Registry',
'clusters.systemDefaultContainerRegistry.tip':
'Default registry used to resolve MesaStack 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 MesaStack 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.'
'clusters.volume.add': 'Add Volume Mount'
};
+3 -24
View File
@@ -46,28 +46,22 @@ export default {
'common.button.enabled': 'Enabled',
'common.button.disabled': 'Disabled',
'common.button.upgrade': 'Upgrade',
'common.enterprise.feature': 'Available in MesaStack Enterprise',
'common.input.holder': 'Please enter',
'common.validate.value': '{name} value is required',
'common.button.edit': 'Edit',
'common.button.authorize': 'Role Authorization',
'common.button.confirm': 'Confirm',
'common.button.viewlog': 'View Logs',
'common.button.viewevent': 'View Events',
'common.button.recreate': 'Recreate',
'common.table.operation': 'Operations',
'common.table.creator': 'Creator',
'common.table.createTime': 'Created',
'common.table.updateTime': 'Updated',
'common.table.description': 'Description',
'common.table.displayName': 'Display Name',
'common.table.name': 'Name',
'common.table.status': 'Status',
'common.table.name.list': '{type} Name',
'common.search.name.placeholder': 'filter by name',
'common.search.id.placeholder': 'filter by ID',
'common.filter.byId': 'filter by ID',
'common.filter.byCreator': 'Filter by creator',
'common.table.type': 'Type',
'common.table.default': 'Default Value',
'common.copy.success': 'Copied success!',
@@ -169,7 +163,6 @@ export default {
'common.time.hour': 'hour',
'common.time.minute': 'minutes',
'common.issue.report': 'Report an issue',
'common.github.star.tooltip': 'Star us on GitHub',
'common.social.discord': 'Join Our Discord',
'common.table.mark': 'Comment',
'common.table.rollback.mark': 'Rollback Comment',
@@ -198,8 +191,6 @@ export default {
'common.table.user': 'User',
'common.settings.instructions': 'Instructions',
'common.settings.language': 'Language',
'common.settings.language.tips':
'Set the display language for the interface.',
'common.delete.confirm':
'Are you sure you want to delete the selected {type}?',
'common.delete.single.confirm':
@@ -214,7 +205,7 @@ export default {
'common.form.password': 'Password',
'common.form.username': 'Username',
'common.login.rember': 'Remember me',
'settings.company': 'MesaStack',
'settings.company': 'GPUStack.ai',
'common.button.help': 'Help',
'common.button.feedback': 'Feedback',
'common.button.docs': 'Documentation',
@@ -232,6 +223,7 @@ export default {
'common.text.latest': 'Latest',
'common.text.new': 'New',
'common.text.changelog': 'Release Notes',
'common.button.recreate': 'Recreate',
'common.button.delrecreate': 'Delete (Recreate)',
'common.options.all': 'All',
'common.options.none': 'None',
@@ -253,11 +245,6 @@ export default {
'common.appearance.tips': 'Default follows system preference.',
'common.button.forgotpassword': 'Forgot password?',
'common.appearance.theme': 'Theme',
'common.appearance.description':
'Customize how the interface looks on your device.',
'common.security': 'Security',
'common.security.description':
'Manage the password used to sign in to your account.',
'common.page.wentwrong': 'Something went wrong.',
'common.page.refresh.tips':
'The page may need to be updated. Try refreshing it!',
@@ -268,10 +255,6 @@ export default {
'common.login.auth': 'Authenticating...',
'common.login.auth.failed': 'Authentication failed',
'common.login.password': 'Log in with Password',
'common.login.username.holder': 'Please enter username',
'common.login.password.holder': 'Please enter password',
'common.login.newpassword.holder': 'Please enter new password',
'common.login.confirm.holder': 'Please enter password again',
'common.external.login': 'Log in with {type}',
'common.sso.noConfig':
'Single sign-on is not enabled on this system. Please contact your administrator.',
@@ -298,9 +281,5 @@ export default {
'common.file.format.limit': 'Invalid file format. Allowed: {formats}.',
'common.image.limit.width': 'Image width must be {width}.',
'common.image.limit.height': 'Image height must be {height}.',
'common.remaining': 'Remaining {count}',
'common.max': 'Max {count}',
'common.max.count': '{label} Count',
'common.validate.group': 'Please complete the {group} configuration',
'common.preferences': 'Preferences'
'common.max': 'Max {count}'
};
+14 -5
View File
@@ -1,23 +1,32 @@
export default {
'dashboard.title': 'Dashboard',
'dashboard.workers': 'Workers',
'dashboard.deployments': 'Deployments',
'dashboard.models': 'Models',
'dashboard.clusters': 'Clusters',
'dashboard.totalgpus': 'GPUs',
'dashboard.allocategpus': 'Allocated GPUs',
'dashboard.instances': 'Instances',
'dashboard.systemload': 'System Load',
'dashboard.memory': 'RAM',
'dashboard.disk': 'Storage',
'dashboard.vram': 'VRAM',
'dashboard.cpuutilization': 'Average CPU Utilization',
'dashboard.memoryutilization': 'Average RAM Utilization',
'dashboard.diskutilization': 'Storage Utilization',
'dashboard.vramutilization': 'Average VRAM Utilization',
'dashboard.gpuutilization': 'Average GPU Utilization',
'dashboard.usage': 'Usage',
'dashboard.usage.title': 'Last {days} days usage',
'dashboard.usage.others': 'Others',
'dashboard.apirequest': 'API Requests',
'dashboard.tokens': 'Token Usage',
'dashboard.topusers': 'Top Users',
'dashboard.activeDeployments': 'Active Deployments',
'dashboard.usageByModel': 'Usage by Model',
'dashboard.activeModels': 'Active Models',
'dashboard.activeUsers': 'Active Users',
'dashboard.tokenUsageByModel': 'Token Usage by Model',
'dashboard.apiRequestsByModel': 'API Requests by Model',
'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.usage.selectuser': 'Select users',
'dashboard.usage.selectmodel': 'Select models',
+10 -112
View File
@@ -13,26 +13,12 @@ export default {
'gpuservice.template.command.placeholder':
'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.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.tips':
'The size of the container system disk.',
'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.add': 'Add Port',
'gpuservice.template.ports.invalid':
'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.add': 'Add Environment Variable',
'gpuservice.template.env.invalid':
@@ -43,51 +29,7 @@ export default {
'gpuservice.template.card.mount': 'Mount',
'gpuservice.template.card.resources': 'Resources',
'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.delete.tips':
'Deleting an SSH Public Key will not revoke access for existing attached Instances. To remove access, edit those Instances separately.',
'gpuservice.publicKey.filter.name': 'Search by name',
'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':
'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',
@@ -103,51 +45,22 @@ export default {
'gpuservice.instance.templates': 'Instance Templates',
'gpuservice.instance.section.storage': 'Storage',
'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.required': 'Please enter the GPU count',
'gpuservice.instance.gpuCount.max':
'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.',
'The current instance type supports at most {count} GPU(s)',
'gpuservice.instance.stock': 'Stock',
'gpuservice.instance.sliced': 'Sliced',
'gpuservice.instance.memory': 'VRAM',
'gpuservice.instance.memory': 'Memory',
'gpuservice.instance.ram': 'RAM',
'gpuservice.instance.os': 'OS',
'gpuservice.instance.arch': 'Arch',
'gpuservice.instance.disk': 'Disk',
'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.type.placeholder':
'Search by name, VRAM, memory or vCPU',
'gpuservice.instance.search.template.placeholder':
'Search by template name, image or mount path',
'gpuservice.instance.template.image': 'Image',
'gpuservice.instance.template.mount': 'Mount',
'gpuservice.instance.connect': 'Connect',
'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.add': 'Add Storage',
'gpuservice.storage.edit': 'Edit Storage',
@@ -159,26 +72,11 @@ export default {
'gpuservice.storage.capacity': 'Capacity',
'gpuservice.storage.accessMode': 'Access Mode',
'gpuservice.storage.persistent': 'Persistent',
'gpuservice.storage.temporary': 'Ephemeral',
'gpuservice.storage.persistentVolume': 'Persistent',
'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.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.temporary': 'Temporary',
'gpuservice.storage.persistentVolume': 'Persistent Volume',
'gpuservice.storage.persistentVolume.required':
'Please select a persistent volume',
'gpuservice.storage.tempCapacity': 'Storage Capacity (GB)',
'gpuservice.storage.tempCapacity.required':
'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',
'gpuservice.creator': 'Creator',
'gpuservice.owner.global': 'Global',
'gpuservice.template.group.yours': 'Your Templates',
'gpuservice.template.group.global': 'Global Templates'
'Please enter the local temporary storage capacity'
};
+9 -14
View File
@@ -8,7 +8,7 @@ export default {
'menu.playground.text2images': 'Image',
'menu.playground.video': 'Video',
'menu.compare': 'Compare',
'menu.models': 'Model Service',
'menu.models': 'Models',
'menu.models.modelList': 'Deploy & Manage',
'menu.models.modelCatalog': 'Catalog',
'menu.models.catalog': 'Model Catalog',
@@ -27,28 +27,23 @@ export default {
'menu.users': 'Users',
'menu.resources.workers': 'Workers',
'menu.resources.gpus': 'GPUs',
'menu.models.modelfiles': 'Model Files',
'menu.resources.modelfiles': 'Model Files',
'menu.accessControl': 'Access Control',
'menu.accessControl.apikeys': 'API Keys',
'menu.accessControl.users': 'Users',
'menu.accessControl.organizations': 'Organizations',
'menu.profile': 'Preferences',
'menu.profile': 'Profile',
'menu.login': 'Login',
'menu.usage': 'Usage',
'menu.usage.usage': 'Usage',
'menu.billingAndUsage': 'Usage & Billing',
'menu.billingAndUsage.usage': 'Usage',
'menu.billingAndUsage.billing': 'Billing',
'menu.404': '404',
'menu.resources.clusters': 'Clusters',
'menu.resources.credentials': 'Cloud Credentials',
'menu.resources.clusterDetail': 'Cluster Detail',
'menu.resources.clusterCreate': 'Create Cluster',
'menu.models.backendsList': 'Inference Backends',
'menu.clusterManagement': 'Cluster Management',
'menu.clusterManagement.clusters': 'Clusters',
'menu.clusterManagement.credentials': 'Cloud Credentials',
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
'menu.clusterManagement.clusterCreate': 'Create Cluster',
'menu.resources.backendsList': 'Inference Backends',
'menu.gpuService': 'GPU Service',
'menu.gpuService.instances': 'GPU Instances',
'menu.gpuService.templates': 'Instance Templates',
'menu.gpuService.storage': 'Storage',
'menu.gpuService.storageTypes': 'Storage Types',
'menu.gpuService.publicKeys': 'SSH Public Keys'
};
+6 -12
View File
@@ -14,7 +14,7 @@ export default {
'models.form.env': 'Environment Variables',
'models.form.configurations': 'Configurations',
'models.form.s3address': 'S3 Address',
'models.form.partialoffload.tips': `When CPU offloading is enabled, MesaStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`,
'models.form.partialoffload.tips': `When CPU offloading is enabled, GPUStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`,
'models.form.distribution.tips': `Allows for offloading part of the model's layers to single or multiple remote workers when the resources of a worker are insufficient.`,
'models.openinplayground': 'Open in Playground',
'models.instances': 'instances',
@@ -24,7 +24,7 @@ export default {
'model.deploy.sort': 'Sort',
'model.deploy.search.placeholder': 'Type <kbd>/</kbd> to search models',
'model.form.ollamatips':
'Tip: The following are the preconfigured Ollama models in MesaStack. Please select the model you want, or directly enter the model you wish to deploy in the 【{name}】 input box on the right.',
'Tip: The following are the preconfigured Ollama models in GPUStack. Please select the model you want, or directly enter the model you wish to deploy in the 【{name}】 input box on the right.',
'models.sort.name': 'Name',
'models.sort.size': 'Size',
'models.sort.likes': 'Likes',
@@ -62,7 +62,7 @@ export default {
'models.form.backend': 'Backend',
'models.form.backend_parameters': 'Backend Parameters',
'models.instance.params.configured': 'User Configured',
'models.instance.params.autoInjected': 'Auto-injected Parameters',
'models.instance.params.autoInjected': 'Auto-injected',
'models.search.gguf.tips':
'GGUF models use llama-box(supports Linux, macOS and Windows).',
'models.search.vllm.tips':
@@ -87,7 +87,7 @@ export default {
'models.form.filePath': 'Model Path',
'models.form.backendVersion': 'Backend Version',
'models.form.backendVersion.tips':
'To use the desired version of {backend}{version}, the system will automatically create a virtual environment in the online environment to install the corresponding version. After a MesaStack upgrade, the backend version will remain fixed. {link}',
'To use the desired version of {backend}{version}, the system will automatically create a virtual environment in the online environment to install the corresponding version. After a GPUStack upgrade, the backend version will remain fixed. {link}',
'models.form.gpuselector': 'GPU Selector',
'models.form.backend.llamabox':
'For GGUF format models, supports Linux, macOS, and Windows.',
@@ -277,7 +277,7 @@ export default {
'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`,
'models.catalog.nogpus.tips':
'No compatible GPUs are available in the selected cluster for this model.',
'models.form.modelfile.notfound': `The model file path you specified does not exist on the MesaStack server. It's recommended to place the model file at the same path on both the MesaStack server and MesaStack workers. This helps MesaStack make better decisions.`,
'models.form.modelfile.notfound': `The model file path you specified does not exist on the GPUStack server. It's recommended to place the model file at the same path on both the GPUStack server and GPUStack workers. This helps GPUStack make better decisions.`,
'models.form.readyWorkers': 'workers ready',
'models.form.maxContextLength': 'Maximum Context Length',
'models.form.backend.helperText':
@@ -290,11 +290,5 @@ export default {
'models.instance.previousRun': 'Previous Run',
'models.instance.startHistory': 'Run History',
'models.instance.startHistory.tips':
'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'
'Shows logs from the run before the last error-triggered restart.'
};
+1 -12
View File
@@ -36,12 +36,9 @@ export default {
'noresult.catalog.nofound': 'No matching models found.',
'noresult.resources.cluster':
'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':
'No workers available. Add a worker to get started.',
'noresult.resources.gotocluster': 'Create Your First Cluster',
'noresult.resources.addk8scluster': 'Add a Kubernetes Cluster',
'noresult.resources.gotoworker': 'Add Worker',
'noresult.benchmark.title': 'No Benchmarks',
'noresult.benchmark.subTitle': 'No benchmarks have been added yet.',
@@ -67,13 +64,5 @@ export default {
'noresult.gpuservice.instance.nofound': 'No matching GPU instances found.',
'noresult.gpuservice.storage.title': 'No Storage',
'noresult.gpuservice.storage.subTitle': 'No storage has been added yet.',
'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.'
'noresult.gpuservice.storage.nofound': 'No matching storage found.'
};
-15
View File
@@ -1,15 +0,0 @@
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 MesaStack 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'
};
+3 -4
View File
@@ -52,7 +52,7 @@ export default {
'resources.worker.container.supported': 'Do not support macOS or Windows.',
'resources.worker.current.version': 'Current version is {version}.',
'resources.worker.driver.install':
'Install <a href="https://docs.gpustack.ai/latest/installation/installation-requirements/" target="_blank">required drivers and libraries</a> prior to MesaStack installation.',
'Install <a href="https://docs.gpustack.ai/latest/installation/installation-requirements/" target="_blank">required drivers and libraries</a> prior to GPUStack installation.',
'resources.worker.select.command':
'Select a label to generate the command and copy it using the copy button.',
'resources.worker.script.install': 'Script Installation',
@@ -89,7 +89,7 @@ export default {
'Paste the <span class="bold-text">Token</span>.',
'resources.register.worker.step7':
'Click <span class="bold-text">Restart</span> to apply the settings.',
'resources.register.install.title': 'Install MesaStack on {os}',
'resources.register.install.title': 'Install GPUStack on {os}',
'resources.register.download':
'Download and install the <a href={url} target="_blank">installer</a>. Only supported: {versions}.',
'resource.register.maos.support': 'Apple Silicon (M series), macOS 14+',
@@ -98,7 +98,6 @@ export default {
'resources.worker.download.privatekey': 'Download Private Key',
'resources.modelfiles.form.exsting': 'Downloaded',
'resources.modelfiles.form.added': 'Added',
'resources.modelfiles.form.isLora': 'Is LoRA',
'resources.worker.maintenance.title': 'System Maintenance',
'resources.worker.maintenance.enable': 'Enter Maintenance Mode',
'resources.worker.maintenance.disable': 'Exit Maintenance Mode',
@@ -111,7 +110,7 @@ export default {
'No available clusters. Please create a cluster before adding a node.',
'resources.metrics.details': 'Monitoring',
'resoureces.worker.upgrade.tips':
'Please upgrade to match the MesaStack Server version.',
'Please upgrade to match the GPUStack Server version.',
'resources.worker.version': 'Worker Version: {version}',
'resources.server.version': 'Server Version: {version}',
'resources.worker.currentVersion': 'Current Version: {version}',
+1 -72
View File
@@ -17,15 +17,9 @@ export default {
'usage.table.user.apiKeysUsed': 'API Keys Used',
'usage.table.lastActive': 'Last Active',
'usage.filter.granularity': 'Granularity',
'usage.filter.granularity.hour': 'Hour',
'usage.filter.granularity.day': 'Day',
'usage.filter.granularity.week': 'Week',
'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.apikeys': 'API Keys',
'usage.tabs.users': 'User',
@@ -38,70 +32,5 @@ export default {
'usage.chart.cached': 'Cached',
'usage.chart.uncached': 'Uncached',
'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)',
'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.resourceName': 'Filter by name',
'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'
'usage.table.inputTokensCached': 'Input Tokens Cached'
};
+3 -5
View File
@@ -28,18 +28,16 @@ export default {
'users.password.modify.title': 'Modify Password',
'users.password.modify.description':
"For your account's security, please change your initial password.",
'users.password.modify.tips':
'Regularly updating your password helps keep your account secure.',
'users.password.confirm': 'Confirm New Password',
'users.password.confirm.empty': 'Please confirm the new password.',
'users.password.confirm.error': 'The two passwords entered do not match.',
'users.login.title': 'Log in to',
'users.version.islatest': 'MesaStack {version} is the latest version',
'users.version.update': 'MesaStack {version} is available',
'users.version.islatest': 'GPUStack {version} is the latest version',
'users.version.update': 'GPUStack {version} is available',
'users.settings.title': 'User Settings',
'users.status.activate': 'Activate Account',
'users.status.deactivate': 'Deactivate Account',
'users.status.inactiveAccount': 'Inactive Account',
'users.login.getInitialPassword':
'Run the following command on your MesaStack Server to retrieve the initial admin password.'
'Run the following command on your GPUStack Server to retrieve the initial admin password.'
};
+1 -1
View File
@@ -4,7 +4,7 @@ export default {
'vendor.hygon': 'Hygon',
'vendor.moorthreads': 'Moore Threads',
'vendor.iluvatar': 'Iluvatar',
'vendor.metax': 'MetaX',
'vendor.metax': 'Metax',
'vendor.cambricon': 'Cambricon',
'vendor.thead': 'T-Head PPU'
};
+1 -1
View File
@@ -25,7 +25,7 @@ export default {
'ai.provider.ollama': 'Ollama',
'ai.provider.openai': 'OpenAI',
'ai.provider.openrouter': 'OpenRouter',
'ai.provider.qwen': 'Alibaba Cloud Model Studio',
'ai.provider.qwen': 'Qwen',
'ai.provider.spark': 'Spark',
'ai.provider.stepfun': 'StepFun',
'ai.provider.together-ai': 'TogetherAI',
+1 -3
View File
@@ -23,9 +23,7 @@ export default {
'apikeys.accessScope.inference': 'Inference APIs',
'apikeys.access.permissions': 'Access Permissions',
'apikeys.type.auto': 'Auto-generated',
'apikeys.type.custom': 'Custom',
'apikeys.button.ipConfig': 'IP Access Control',
'quotaLimits.button.title': 'Quota Limit'
'apikeys.type.custom': 'Custom'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
-8
View File
@@ -23,14 +23,6 @@ export default {
'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.defaultBackendParameters': 'Default Backend Parameters',
'backend.form.flagFormat': 'フラグ形式',
'backend.form.flagFormat.tips':
'オプションとその値を連結する形式。空欄の場合は各パラメータを入力されたまま保持し、形式を統一しません。',
'backend.form.flagFormat.space': 'スペース区切り (--key value)',
'backend.form.flagFormat.equal': 'イコール記号 (--key=value)',
'backend.form.commonParameters': 'よく使うバックエンドパラメータ',
'backend.form.commonParameters.tips':
'モデルのデプロイ時にバックエンドパラメータ入力欄の候補として表示されます。',
'backend.form.versionConfig': 'Versions Config',
'backend.form.addParameter': 'Add Parameter',
'backend.form.noVersion': 'No versions added',
+2 -2
View File
@@ -37,8 +37,8 @@ export default {
'Stress test for long-context handling. Evaluates KV cache behavior, memory usage, and backend stability.',
'benchmark.form.profile.heavy.tips':
'Decode-heavy generation benchmark. Measures sustained decoding speed and output token throughput.',
'benchmark.table.filter.bygpu': 'GPU 検索',
'benchmark.table.filter.bymodel': 'モデル検索',
'benchmark.table.filter.bygpu': 'Filter by GPU',
'benchmark.table.filter.bymodel': 'Filter by Model',
'benchmark.table.filter.bydataset': 'Filter by Dataset',
'benchmark.table.filter.byProfile': 'Filter by Profile',
'benchmark.table.avg': 'Avg',
-15
View File
@@ -1,15 +0,0 @@
export default {
'billing.upsell.title': 'Billing is an Enterprise feature',
'billing.upsell.subtitle':
'Track spend, generate invoices, and enforce budgets across teams. Upgrade to MesaStack Enterprise to manage billing.',
'billing.upsell.featuresTitle': 'What you get in Enterprise',
'billing.upsell.feature.usage':
'See cost breakdowns by organization, user, and model',
'billing.upsell.feature.invoices':
'Generate invoices and export billing reports',
'billing.upsell.feature.budgets':
'Set budgets and spending limits with alerts',
'billing.upsell.feature.chargeback':
'Attribute and charge back usage to teams and projects',
'billing.upsell.cta': 'Learn about Enterprise'
};
+12 -50
View File
@@ -39,11 +39,9 @@ export default {
'clusters.workerpool.batchSize.desc':
'Number of workers created simultaneously in the Worker pool',
'clusters.create.addworker.tips':
'Please make sure the <a href={link} target="_blank">prerequisites</a> are met before executing the following command.',
'Please make sure the <a href={link} target="_blank">prerequisites</a> for {label} are met before executing the following command.',
'clusters.create.addCommand.tips':
'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':
'Use the following command to check if the environment is ready.',
'clusters.create.register.tips':
@@ -69,14 +67,8 @@ export default {
'clusters.addworker.selectCluster.tips':
'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.multiTag': 'Multi-select',
'clusters.addworker.selectGPU.subtitle':
'複数の GPU ベンダーを選択するか、CPU クラスター専用の場合は選択不要です',
'clusters.addworker.checkEnv': 'Check Environment',
'clusters.addworker.checkEnv.cpuOnlyTips':
'以下のコマンドを使用して、Kubernetes クラスターに少なくとも 1 つのレディーノードがあることを確認してください。CPU クラスターを登録しています。',
'clusters.addworker.specifyArgs': 'Specify Arguments',
'clusters.addworker.dtkVersion': 'DTK バージョン',
'clusters.addworker.runCommand': 'Run Command',
'clusters.addworker.specifyWorkerIP': 'Worker IP',
'clusters.addworker.detectWorkerIP': 'Worker IP を自動検出',
@@ -113,20 +105,18 @@ export default {
'{count} new worker has been added to the cluster.',
'clusters.addworker.message.success_multiple':
'{count} new workers have been added to the cluster.',
'clusters.create.serverUrl': 'MesaStack Server URL',
'clusters.create.serverUrl': 'GPUStack Server URL',
'clusters.create.workerConfig': 'Worker Configuration',
'clusters.edit.k8sOptions.changed.tip':
'Kubernetes オプションを変更しました。変更を有効にするには、対象クラスターで登録コマンドを再実行してください。',
'clusters.addworker.containerName': 'Worker Container Name',
'clusters.addworker.containerName.tips':
'Specify a name for the worker container.',
'clusters.addworker.dataVolume': 'MesaStack Data Volume',
'clusters.addworker.dataVolume': 'GPUStack Data Volume',
'clusters.addworker.dataVolume.tips':
'Specify a data storage path for MesaStack.',
'Specify a data storage path for GPUStack.',
'clusters.table.ip.internal': 'Internal',
'clusters.table.ip.external': 'External',
'clusters.form.serverUrl.tips':
'Specify an externally accessible MesaStack service URL if the worker cannot access MesaStack Server directly.',
'Specify an externally accessible GPUStack service URL if the worker cannot access GPUStack Server directly.',
'clusters.form.setDefault': 'Set as Default',
'clusters.form.setDefault.tips': 'Default for deployment.',
'clusters.addworker.noClusters': 'No available Docker clusters found',
@@ -144,7 +134,7 @@ export default {
'clusters.addworker.theadNotes-02':
'T-Head PPU uses the Container Device Interface (CDI) for device injection and requires the <span class="bold-text">/var/run/cdi</span> directory to be available for CDI generation.',
'clusters.addworker.nvidiaNotes':
'The built-in inference backends in MesaStack require <span class="bold-text">CUDA 12.8+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">570</span> or newer.',
'The built-in inference backends in GPUStack v2.1 require <span class="bold-text">CUDA 12.6+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">560</span> or newer.',
'clusters.volume.title': 'Volume Mounts',
'clusters.volume.name': 'Volume Name',
'clusters.volume.mountPath': 'Container Path',
@@ -168,35 +158,7 @@ export default {
'clusters.volume.pvc.readOnly': 'Read Only',
'clusters.volume.configMap.name': 'ConfigMap Name',
'clusters.volume.configMap.optional': 'Optional',
'clusters.volume.add': 'Add Volume Mount',
'clusters.systemDefaultContainerRegistry.title': 'Default Container Registry',
'clusters.systemDefaultContainerRegistry.tip':
'Default registry used to resolve MesaStack 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 MesaStack 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.'
'clusters.volume.add': 'Add Volume Mount'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -276,15 +238,15 @@ export default {
// 73. 'clusters.addworker.cacheVolume.holder': 'e.g. /data/cache (path must start with /)',
// 74. 'clusters.addworker.message.success_single': '{count} new worker has been added to the cluster.',
// 75. 'clusters.addworker.message.success_multiple': '{count} new workers have been added to the cluster.',
// 76. 'clusters.create.serverUrl': 'MesaStack Server URL',
// 76. 'clusters.create.serverUrl': 'GPUStack Server URL',
// 77. 'clusters.create.workerConfig': 'Worker Configuration'
// 78. 'clusters.addworker.containerName': 'Worker Container Name',
// 79. 'clusters.addworker.containerName.tips':'Specify a name for the worker container.',
// 77. 'clusters.addworker.dataVolume': 'MesaStack Data Volume',
// 78. 'clusters.addworker.dataVolume.tips': 'Specify a data storage path for MesaStack.',
// 77. 'clusters.addworker.dataVolume': 'GPUStack Data Volume',
// 78. 'clusters.addworker.dataVolume.tips': 'Specify a data storage path for GPUStack.',
// 79. 'clusters.table.ip.internal': 'Internal',
// 80. 'clusters.table.ip.external': 'External',
// 81. 'clusters.form.serverUrl.tips': 'Specify an externally accessible MesaStack service URL if the worker cannot access MesaStack Server directly.',
// 81. 'clusters.form.serverUrl.tips': 'Specify an externally accessible GPUStack service URL if the worker cannot access GPUStack Server directly.',
// 82. 'clusters.addworker.externalIP.tips': 'Specify an external IP if the worker is in a VPC or private network.',
// 83. 'clusters.form.setDefault': 'Set as Default',
// 84. 'clusters.form.setDefault.tips': 'Default for deployment',
@@ -300,5 +262,5 @@ export default {
// 94. 'clusters.create.steps.configure': 'Configure',
// 99. 'clusters.addworker.theadNotes': 'If the <span class="bold-text>/usr/local/PPU_SDK</span> directory does not exist, please create a symbolic link pointing to the T-Head PPU SDK installed path: <span class="bold-text>ln -s /path/to/PPU_SDK /usr/local/PPU_SDK</span>',
// 100. 'clusters.addworker.theadNotes-02': 'T-Head PPU uses the Container Device Interface (CDI) for device injection and requires the <span class="bold-text">/var/run/cdi</span> directory to be available for CDI generation.',
// 101. 'clusters.addworker.nvidiaNotes': 'The built-in inference backends in MesaStack v2.1 require <span class="bold-text">CUDA 12.6+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">560</span> or newer.'
// 101. 'clusters.addworker.nvidiaNotes': 'The built-in inference backends in GPUStack v2.1 require <span class="bold-text">CUDA 12.6+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">560</span> or newer.'
// ========== End of To-Do List ==========
+3 -23
View File
@@ -46,28 +46,22 @@ export default {
'common.button.enabled': '有効',
'common.button.disabled': '無効',
'common.button.upgrade': 'アップグレード',
'common.enterprise.feature': 'Available in MesaStack Enterprise',
'common.input.holder': '入力してください',
'common.validate.value': '{name} の値は必須です',
'common.button.edit': '編集',
'common.button.authorize': 'ロール認可',
'common.button.confirm': '確認',
'common.button.viewlog': 'ログを表示',
'common.button.viewevent': 'イベントを表示',
'common.button.recreate': '再作成',
'common.table.operation': '操作',
'common.table.creator': '作成者',
'common.table.createTime': '作成日時',
'common.table.updateTime': '更新日時',
'common.table.description': '説明',
'common.table.displayName': '表示名',
'common.table.name': '名前',
'common.table.status': 'ステータス',
'common.table.name.list': '{type} 名称',
'common.search.name.placeholder': '名前でフィルタ',
'common.search.id.placeholder': 'IDでフィルタ',
'common.filter.byId': 'IDでフィルタ',
'common.filter.byCreator': '作成者でフィルタ',
'common.table.type': 'タイプ',
'common.table.default': 'デフォルト値',
'common.copy.success': 'コピー成功!',
@@ -170,7 +164,6 @@ export default {
'common.time.hour': '時間',
'common.time.minute': '分',
'common.issue.report': '問題を報告',
'common.github.star.tooltip': 'GitHub でスターをつける',
'common.social.discord': 'Discordに参加',
'common.table.mark': 'コメント',
'common.table.rollback.mark': 'ロールバックコメント',
@@ -199,7 +192,6 @@ export default {
'common.table.user': 'ユーザー',
'common.settings.instructions': '手順',
'common.settings.language': '言語',
'common.settings.language.tips': 'インターフェースの表示言語を設定します。',
'common.delete.confirm': '選択した {type} を削除してもよろしいですか?',
'common.delete.single.confirm':
'<span style="font-size: 13px;font-weight: 700">{name}</span> を削除してもよろしいですか?',
@@ -213,7 +205,7 @@ export default {
'common.form.password': 'パスワード',
'common.form.username': 'ユーザー名',
'common.login.rember': 'ログイン状態を保持',
'settings.company': 'MesaStack',
'settings.company': 'GPUStack.ai',
'common.button.help': 'ヘルプ',
'common.button.feedback': 'フィードバック',
'common.button.docs': 'ドキュメント',
@@ -231,6 +223,7 @@ export default {
'common.text.latest': '最新',
'common.text.new': '新規',
'common.text.changelog': 'リリースノート',
'common.button.recreate': '再作成',
'common.button.delrecreate': '削除(再作成)',
'common.options.all': 'すべて',
'common.options.none': 'なし',
@@ -252,11 +245,6 @@ export default {
'common.appearance.tips': 'Default follows system preference.',
'common.button.forgotpassword': 'Forgot password?',
'common.appearance.theme': 'Theme',
'common.appearance.description':
'デバイス上でのインターフェースの表示をカスタマイズします。',
'common.security': 'セキュリティ',
'common.security.description':
'アカウントへのログインに使用するパスワードを管理します。',
'common.page.wentwrong': 'Something went wrong.',
'common.page.refresh.tips':
'The page may need to be updated. Try refreshing it!',
@@ -267,10 +255,6 @@ export default {
'common.login.auth': 'Authenticating...',
'common.login.auth.failed': 'Authentication failed',
'common.login.password': 'Log in with Password',
'common.login.username.holder': 'Please enter username',
'common.login.password.holder': 'Please enter password',
'common.login.newpassword.holder': 'Please enter new password',
'common.login.confirm.holder': 'Please enter password again',
'common.external.login': 'Log in with {type}',
'common.sso.noConfig':
'Single sign-on is not enabled on this system. Please contact your administrator.',
@@ -297,11 +281,7 @@ export default {
'common.file.format.limit': 'Invalid file format. Allowed: {formats}.',
'common.image.limit.width': 'Image width must be {width}.',
'common.image.limit.height': 'Image height must be {height}.',
'common.remaining': '残り {count}',
'common.max': '最大 {count}',
'common.max.count': '{label} 数',
'common.validate.group': 'Please complete the {group} configuration',
'common.preferences': 'Preferences'
'common.max': '最大 {count}'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
+14 -5
View File
@@ -1,22 +1,31 @@
export default {
'dashboard.title': 'ダッシュボード',
'dashboard.workers': 'ワーカー',
'dashboard.deployments': 'Deployments',
'dashboard.models': 'モデル',
'dashboard.totalgpus': 'GPU数',
'dashboard.allocategpus': '割り当て済みGPU',
'dashboard.instances': 'インスタンス',
'dashboard.systemload': 'システム負荷',
'dashboard.memory': 'メモリ',
'dashboard.disk': 'ストレージ',
'dashboard.vram': 'VRAM',
'dashboard.cpuutilization': '平均CPU使用率',
'dashboard.memoryutilization': '平均メモリ使用率',
'dashboard.diskutilization': 'ストレージ使用率',
'dashboard.vramutilization': '平均VRAM使用率',
'dashboard.gpuutilization': '平均GPU使用率',
'dashboard.usage': '使用状況',
'dashboard.usage.title': '過去 {days} 日間の使用状況',
'dashboard.usage.others': 'その他',
'dashboard.apirequest': 'APIリクエスト',
'dashboard.tokens': 'トークン使用量',
'dashboard.topusers': 'トップユーザー',
'dashboard.activeDeployments': 'Active Deployments',
'dashboard.usageByModel': 'モデル別使用量',
'dashboard.activeModels': 'アクティブなモデル',
'dashboard.activeUsers': 'アクティブユーザー',
'dashboard.tokenUsageByModel': 'モデル別トークン使用量',
'dashboard.apiRequestsByModel': 'モデル別APIリクエスト',
'dashboard.topTokenUsageByUser': 'ユーザー別トークン使用量トップ10',
'dashboard.topTokenUsageByApiKey': 'APIキー別トークン使用量トップ10',
'dashboard.runninginstances': '稼働中のインスタンス',
'dashboard.activeModels.name': 'モデル名',
'dashboard.allocatevram': '割り当て済みVRAM / メモリ',
'dashboard.usage.selectuser': 'Select users',
'dashboard.usage.selectmodel': 'Select models',
+8 -112
View File
@@ -13,25 +13,11 @@ export default {
'gpuservice.template.command.placeholder':
'引数はスペースで区切り、スペースを含む引数は引用符で囲んでください。例:/bin/bash -c "echo hello world"',
'gpuservice.template.mountPath': 'マウントパス',
'gpuservice.template.mountPath.tips':
'このテンプレートからインスタンスを作成する際に、ストレージボリュームがデフォルトでマウントされるパスです。インスタンスの実行中に保持する必要があるデータの永続化に使用できます。',
'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.add': 'ポートを追加',
'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.add': '環境変数を追加',
'gpuservice.template.env.invalid': '環境変数を完成させてください。',
@@ -41,52 +27,7 @@ export default {
'gpuservice.template.card.mount': 'マウント',
'gpuservice.template.card.resources': 'リソース',
'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.delete.tips':
'SSH 公開鍵を削除しても、既にアタッチされているインスタンスのアクセス権は取り消されません。アクセス権を削除するには、対象のインスタンスを個別に編集してください。',
'gpuservice.publicKey.filter.name': '名前で検索',
'gpuservice.publicKey.label': 'SSH 公開鍵',
'gpuservice.instance.ssh.enable': 'SSH アクセスを有効化',
'gpuservice.instance.ssh.assignKey': 'SSH 公開鍵を割り当て',
'gpuservice.instance.ssh.addKey': 'SSH 公開鍵を追加',
'gpuservice.publicKey.placeholder':
'ssh-rsa または ssh-ed25519 で始まり、各公開鍵は1行ずつ記述します\n\n公開鍵を確認:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub',
'gpuservice.instance': 'GPU インスタンス',
@@ -102,51 +43,22 @@ export default {
'gpuservice.instance.templates': 'インスタンステンプレート',
'gpuservice.instance.section.storage': 'ストレージボリューム',
'gpuservice.instance.type.required': 'インスタンスタイプを選択してください',
'gpuservice.instance.type.noAvailable':
'利用可能なインスタンスタイプがありません',
'gpuservice.instance.gpuCount': 'GPU 数',
'gpuservice.instance.gpuCount.required': 'GPU 数を入力してください',
'gpuservice.instance.gpuCount.max':
'最大 {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 のみを使用し、環境準備用です。',
'現在のインスタンスタイプは最大 {count} の GPU をサポートします',
'gpuservice.instance.stock': '在庫',
'gpuservice.instance.sliced': '分割',
'gpuservice.instance.memory': 'VRAM',
'gpuservice.instance.memory': 'Memory',
'gpuservice.instance.ram': 'RAM',
'gpuservice.instance.os': 'OS',
'gpuservice.instance.arch': 'アーキテクチャ',
'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.type.placeholder':
'名前、VRAM、メモリまたは vCPU で検索',
'gpuservice.instance.search.template.placeholder':
'テンプレート名、イメージまたはマウントパスで検索',
'gpuservice.instance.template.image': 'イメージ',
'gpuservice.instance.template.mount': 'マウント',
'gpuservice.instance.connect': '接続',
'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.add': 'ストレージを追加',
'gpuservice.storage.edit': 'ストレージを編集',
@@ -159,26 +71,10 @@ export default {
'gpuservice.storage.accessMode': 'アクセスモード',
'gpuservice.storage.persistent': '永続',
'gpuservice.storage.temporary': '一時',
'gpuservice.storage.persistentVolume': '永続',
'gpuservice.storage.persistentVolume': '永続ボリューム',
'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': 'ストレージ容量 (GB)',
'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': 'ストレージを選択',
'gpuservice.creator': '作成者',
'gpuservice.owner.global': 'グローバル',
'gpuservice.template.group.yours': '自分のテンプレート',
'gpuservice.template.group.global': 'グローバルテンプレート'
'ローカルの一時ストレージ容量を入力してください'
};
+11 -16
View File
@@ -8,7 +8,7 @@ export default {
'menu.playground.text2images': '画像生成',
'menu.playground.video': '動画',
'menu.compare': '比較',
'menu.models': 'モデルサービス',
'menu.models': 'モデル',
'menu.models.modelList': 'デプロイと管理',
'menu.models.modelCatalog': 'カタログ',
'menu.models.catalog': 'モデルカタログ',
@@ -23,33 +23,28 @@ export default {
'menu.resources': 'リソース',
'menu.apikeys': 'APIキー',
'menu.users': 'ユーザー',
'menu.profile': 'Preferences',
'menu.profile': 'プロフィール',
'menu.login': 'ログイン',
'menu.usage': '使用状況',
'menu.usage.usage': '使用状況',
'menu.billingAndUsage': '使用状況と請求',
'menu.billingAndUsage.usage': '使用状況',
'menu.billingAndUsage.billing': '請求',
'menu.404': '404',
'menu.settings': 'Settings',
'menu.resources.workers': 'Workers',
'menu.resources.gpus': 'GPUs',
'menu.models.modelfiles': 'Model Files',
'menu.resources.modelfiles': 'Model Files',
'menu.accessControl': 'Access Control',
'menu.accessControl.apikeys': 'API Keys',
'menu.accessControl.users': 'Users',
'menu.accessControl.organizations': 'Organizations',
'menu.resources.clusters': 'Clusters',
'menu.resources.credentials': 'Cloud Credentials',
'menu.clusterManagement': 'Cluster Management',
'menu.clusterManagement.clusters': 'Clusters',
'menu.clusterManagement.credentials': 'Cloud Credentials',
'menu.models.userModels': 'My Models',
'menu.resources.clusterDetail': 'Cluster Detail',
'menu.resources.clusterCreate': 'Create Cluster',
'menu.models.backendsList': 'Inference Backends',
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
'menu.clusterManagement.clusterCreate': 'Create Cluster',
'menu.resources.backendsList': 'Inference Backends',
'menu.gpuService': 'GPU Service',
'menu.gpuService.instances': 'GPU Instances',
'menu.gpuService.templates': 'Instance Templates',
'menu.gpuService.storage': 'Storage',
'menu.gpuService.storageTypes': 'ストレージタイプ',
'menu.gpuService.publicKeys': 'SSH Public Keys'
};
@@ -57,7 +52,7 @@ export default {
// 1. 'menu.models.deployment': 'Deployment',
// 2. 'menu.resources.workers': 'Workers',
// 3. 'menu.resources.gpus': 'GPUs',
// 4. 'menu.models.modelfiles': 'Model Files',
// 4. 'menu.resources.modelfiles': 'Model Files',
// 5. 'menu.accessControl': 'Access Control',
// 6. 'menu.accessControl.apikeys': 'API Keys',
// 7. 'menu.accessControl.users': 'Users',
@@ -67,7 +62,7 @@ export default {
// 11. 'menu.models.userModels': 'My Models'
// 12. 'menu.clusterManagement.clusterDetail': 'Cluster Detail',
// 13. 'menu.clusterManagement.clusterCreate': 'Create Cluster',
// 14. 'menu.models.backendsList': 'Inference Backends',
// 14. 'menu.resources.backendsList': 'Inference Backends',
// 15. 'menu.models.benchmark': 'Benchmarks',
// 15. 'menu.models.provider': 'Provider',
// 15. 'menu.models.providers': 'Provider',
+9 -21
View File
@@ -14,7 +14,7 @@ export default {
'models.form.env': '環境変数',
'models.form.configurations': '設定',
'models.form.s3address': 'S3アドレス',
'models.form.partialoffload.tips': `When CPU offloading is enabled, MesaStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`,
'models.form.partialoffload.tips': `When CPU offloading is enabled, GPUStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`,
'models.form.distribution.tips':
'ワーカーのリソースが不足している場合、モデルの一部のレイヤーを単一または複数のリモートワーカーにオフロードすることができます。',
'models.openinplayground': 'プレイグラウンドで開く',
@@ -25,7 +25,7 @@ export default {
'model.deploy.sort': '並び替え',
'model.deploy.search.placeholder': '<kbd>/</kbd>を入力してモデルを検索',
'model.form.ollamatips':
'ヒント: 以下はMesaStackで事前設定されたOllamaモデルです。希望するモデルを選択するか、右側の【{name}】入力ボックスにデプロイしたいモデルを直接入力してください。',
'ヒント: 以下はGPUStackで事前設定されたOllamaモデルです。希望するモデルを選択するか、右側の【{name}】入力ボックスにデプロイしたいモデルを直接入力してください。',
'models.sort.name': '名前',
'models.sort.size': 'サイズ',
'models.sort.likes': 'いいね',
@@ -63,7 +63,7 @@ export default {
'models.form.backend': 'バックエンド',
'models.form.backend_parameters': 'バックエンドパラメータ',
'models.instance.params.configured': 'User Configured',
'models.instance.params.autoInjected': '自動注入パラメータ',
'models.instance.params.autoInjected': '自動注入',
'models.search.gguf.tips':
'GGUFモデルはllama-boxを使用します(Linux、macOS、Windowsをサポート)。',
'models.search.vllm.tips':
@@ -88,7 +88,7 @@ export default {
'models.form.filePath': 'モデルパス',
'models.form.backendVersion': 'バックエンドバージョン',
'models.form.backendVersion.tips':
'希望する{backend}{version}バージョンを使用するには、システムがオンライン環境で対応するバージョンをインストールする仮想環境を自動的に作成します。MesaStackのアップグレード後もバックエンドバージョンは固定されます。{link}',
'希望する{backend}{version}バージョンを使用するには、システムがオンライン環境で対応するバージョンをインストールする仮想環境を自動的に作成します。GPUStackのアップグレード後もバックエンドバージョンは固定されます。{link}',
'models.form.gpuselector': 'GPUセレクター',
'models.form.backend.llamabox':
'GGUF形式のモデル用(Linux、macOS、Windowsをサポート)。',
@@ -277,7 +277,7 @@ export default {
'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`,
'models.catalog.nogpus.tips':
'No compatible GPUs are available in the selected cluster for this model.',
'models.form.modelfile.notfound': `The model file path you specified does not exist on the MesaStack server. It's recommended to place the model file at the same path on both the MesaStack server and MesaStack workers. This helps MesaStack make better decisions.`,
'models.form.modelfile.notfound': `The model file path you specified does not exist on the GPUStack server. It's recommended to place the model file at the same path on both the GPUStack server and GPUStack workers. This helps GPUStack make better decisions.`,
'models.form.readyWorkers': 'workers ready',
'models.form.maxContextLength': 'Maximum Context Length',
'models.form.backend.helperText':
@@ -290,13 +290,7 @@ export default {
'models.instance.previousRun': 'Previous Run',
'models.instance.startHistory': 'Run History',
'models.instance.startHistory.tips':
'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'
'Shows logs from the run before the last error-triggered restart.'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -381,7 +375,7 @@ export default {
// 62. 'models.form.backend_parameters.vllm.tips': 'For more details about {backend} parameters, see <a href={link} target="_blank">here</a>.',
// 63. 'models.button.accessSettings.tips': 'Changes to access settings take effect after one minute.',
// 64. 'models.table.userSelection.tips': 'Admin users can access all models by default.',
// 65. 'models.form.partialoffload.tips': `When CPU offloading is enabled, MesaStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`,
// 65. 'models.form.partialoffload.tips': `When CPU offloading is enabled, GPUStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`,
// 66. 'models.form.backend.warning': 'The selected backend does not support GGUF models. Please add a backend with GGUF support in the Inference Backend.',
// 67. 'models.form.backend.warning.gguf': 'Please ensure that the selected custom backend supports GGUF models.',,
// 68. 'models.form.backendVersion.deprecated': 'Deprecated',
@@ -390,7 +384,7 @@ export default {
// 71.'models.accessSettings.allowedUsers.tips': 'Only designated users can access the model.',
// 72. 'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`,
// 73. 'models.catalog.nogpus.tips': 'No compatible GPUs are available in the selected cluster for this model.',
// 74. 'models.form.modelfile.notfound': `The model file path you specified does not exist on the MesaStack server. It's recommended to place the model file at the same path on both the MesaStack server and MesaStack workers. This helps MesaStack make better decisions.`,
// 74. 'models.form.modelfile.notfound': `The model file path you specified does not exist on the GPUStack server. It's recommended to place the model file at the same path on both the GPUStack server and GPUStack workers. This helps GPUStack make better decisions.`,
// 75. 'models.form.readyWorkers': 'workers ready',
// 76. 'models.form.maxContextLength': 'Maximum Context Length',
// 77. 'models.form.backend.helperText': 'Not enabled yet. Will be enabled after deployment. ',
@@ -399,11 +393,5 @@ export default {
// 78. 'models.form.enableModelRoute.tips': 'Enable Model Route',
// 79. 'models.table.modelView': 'Model View',
// 80. 'models.table.instanceView': 'Instance View',
// 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'
// 81. 'models.table.category': 'Category'
// ========== End of To-Do List ==========
+1 -12
View File
@@ -36,12 +36,9 @@ export default {
'noresult.catalog.nofound': 'No matching models found.',
'noresult.resources.cluster':
'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':
'No workers available. Add a worker to get started.',
'noresult.resources.gotocluster': 'Create Your First Cluster',
'noresult.resources.addk8scluster': 'Add a Kubernetes Cluster',
'noresult.resources.gotoworker': 'Add Worker',
'noresult.benchmark.title': 'No Benchmarks',
'noresult.benchmark.subTitle': 'No benchmarks have been added yet.',
@@ -69,13 +66,5 @@ export default {
'noresult.gpuservice.storage.title': 'ストレージなし',
'noresult.gpuservice.storage.subTitle':
'ストレージはまだ追加されていません。',
'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 公開鍵が見つかりません。'
'noresult.gpuservice.storage.nofound': '一致するストレージが見つかりません。'
};
-15
View File
@@ -1,15 +0,0 @@
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 MesaStack 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'
};
+5 -6
View File
@@ -52,7 +52,7 @@ export default {
'MacOSまたはWindowsはサポートされていません。',
'resources.worker.current.version': '現在のバージョンは {version} です。',
'resources.worker.driver.install':
'<a href="https://docs.gpustack.ai/latest/installation/installation-requirements/" target="_blank">必要なドライバとライブラリ</a> をMesaStackのインストール前にインストールしてください。',
'<a href="https://docs.gpustack.ai/latest/installation/installation-requirements/" target="_blank">必要なドライバとライブラリ</a> をGPUStackのインストール前にインストールしてください。',
'resources.worker.select.command':
'ラベルを選択してコマンドを生成し、コピーを使用してコマンドをコピーします。',
'resources.worker.script.install': 'スクリプトインストール',
@@ -89,7 +89,7 @@ export default {
'Paste the <span class="bold-text">Token</span>.',
'resources.register.worker.step7':
'Click <span class="bold-text">Restart</span> to apply the settings.',
'resources.register.install.title': 'Install MesaStack on {os}',
'resources.register.install.title': 'Install GPUStack on {os}',
'resources.register.download':
'Download and install the <a href={url} target="_blank">installer</a>. Only supported: {versions}.',
'resource.register.maos.support': 'Apple Silicon (M series), macOS 14+',
@@ -99,7 +99,6 @@ export default {
'resources.worker': 'Worker',
'resources.modelfiles.form.exsting': 'Downloaded',
'resources.modelfiles.form.added': 'Added',
'resources.modelfiles.form.isLora': 'Is LoRA',
'resources.worker.maintenance.title': 'System Maintenance',
'resources.worker.maintenance.enable': 'Enter Maintenance Mode',
'resources.worker.maintenance.disable': 'Exit Maintenance Mode',
@@ -112,7 +111,7 @@ export default {
'No available clusters. Please create a cluster before adding a node.',
'resources.metrics.details': 'Monitoring',
'resoureces.worker.upgrade.tips':
'Please upgrade to match the MesaStack Server version.',
'Please upgrade to match the GPUStack Server version.',
'resources.worker.version': 'Worker Version: {version}',
'resources.server.version': 'Server Version: {version}',
'resources.worker.currentVersion': 'Current Version: {version}',
@@ -128,7 +127,7 @@ export default {
// 5. 'resources.register.worker.step5': 'Enter the <span class="bold-text">Server URL</span>: {url}.',
// 6. 'resources.register.worker.step6': 'Paste the <span class="bold-text">Token</span>.',
// 7. 'resources.register.worker.step7': 'Click <span class="bold-text">Restart</span> to apply the settings.',
// 8. 'resources.register.install.title': 'Install MesaStack on {os}',
// 8. 'resources.register.install.title': 'Install GPUStack on {os}',
// 9. 'resources.register.download':'Download and install the <a>installer</a>. Only supported: {versions}.',
// 10. 'resource.register.maos.support': 'Apple Silicon (M series), macOS 14+',
// 11. 'resource.register.windows.support': 'win 10, win 11',
@@ -145,5 +144,5 @@ export default {
// 22. 'resources.worker.maintenance.remark.rules': 'Please enter maintenance remarks',
// 23. 'resources.worker.maintenance.tips': 'When maintenance mode is enabled, the node will stop scheduling new model deployment tasks. Running instances will not be affected.',
// 24. 'resources.worker.noCluster.tips': 'No available clusters. Please create a cluster before adding a node.'
// 25. 'resoureces.worker.upgrade.tips': 'Please upgrade to match the MesaStack Server version.'
// 25. 'resoureces.worker.upgrade.tips': 'Please upgrade to match the GPUStack Server version.'
// ========== End of To-Do List ==========
+1 -72
View File
@@ -17,15 +17,9 @@ export default {
'usage.table.user.apiKeysUsed': 'API Keys Used',
'usage.table.lastActive': 'Last Active',
'usage.filter.granularity': 'Granularity',
'usage.filter.granularity.hour': '時間',
'usage.filter.granularity.day': 'Day',
'usage.filter.granularity.week': 'Week',
'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.apikeys': 'API Keys',
'usage.tabs.users': 'User',
@@ -38,70 +32,5 @@ export default {
'usage.chart.cached': 'Cached',
'usage.chart.uncached': 'Uncached',
'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)',
'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.resourceName': '名前で絞り込み',
'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': 'デタッチ済み'
'usage.table.inputTokensCached': 'Input Tokens Cached'
};
+4 -6
View File
@@ -29,20 +29,18 @@ export default {
'users.password.modify.title': 'パスワードを変更',
'users.password.modify.description':
'アカウントのセキュリティのため、初期パスワードを変更してください。',
'users.password.modify.tips':
'パスワードを定期的に更新すると、アカウントの安全を保てます。',
'users.password.confirm': '新しいパスワードを確認',
'users.password.confirm.empty': '新しいパスワードを確認してください。',
'users.password.confirm.error': '入力された2つのパスワードが一致しません。',
'users.login.title': 'ログイン',
'users.version.islatest': 'MesaStack {version} は最新バージョンです',
'users.version.update': 'MesaStack {version} が利用可能です',
'users.version.islatest': 'GPUStack {version} は最新バージョンです',
'users.version.update': 'GPUStack {version} が利用可能です',
'users.settings.title': 'User Settings',
'users.status.activate': 'Activate Account',
'users.status.deactivate': 'Deactivate Account',
'users.status.inactiveAccount': 'Inactive Account',
'users.login.getInitialPassword':
'Run the following command on your MesaStack Server to retrieve the initial admin password.'
'Run the following command on your GPUStack Server to retrieve the initial admin password.'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -50,5 +48,5 @@ export default {
// 2. 'users.status.activate': 'Activate Account',
// 3. 'users.status.deactivate': 'Deactivate Account',
// 4. 'users.status.inactiveAccount': 'Inactive Account',
// 5. 'users.login.getInitialPassword': 'Run the following command on your MesaStack Server to retrieve the initial admin password.'
// 5. 'users.login.getInitialPassword': 'Run the following command on your GPUStack Server to retrieve the initial admin password.'
// ========== End of To-Do List ==========
+1 -1
View File
@@ -4,7 +4,7 @@ export default {
'vendor.hygon': 'Hygon',
'vendor.moorthreads': 'Moore Threads',
'vendor.iluvatar': 'Iluvatar',
'vendor.metax': 'MetaX',
'vendor.metax': 'Metax',
'vendor.cambricon': 'Cambricon',
'vendor.thead': 'T-Head PPU'
};
+1 -1
View File
@@ -25,7 +25,7 @@ export default {
'ai.provider.ollama': 'Ollama',
'ai.provider.openai': 'OpenAI',
'ai.provider.openrouter': 'OpenRouter',
'ai.provider.qwen': 'Alibaba Cloud Model Studio',
'ai.provider.qwen': 'Qwen',
'ai.provider.spark': 'Spark',
'ai.provider.stepfun': 'StepFun',
'ai.provider.together-ai': 'TogetherAI',
+1 -3
View File
@@ -23,9 +23,7 @@ export default {
'apikeys.accessScope.inference': 'Inference APIs',
'apikeys.access.permissions': 'Access Permissions',
'apikeys.type.auto': 'Auto-generated',
'apikeys.type.custom': 'Custom',
'apikeys.button.ipConfig': 'IP Access Control',
'quotaLimits.button.title': 'Quota Limit'
'apikeys.type.custom': 'Custom'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
-8
View File
@@ -23,14 +23,6 @@ export default {
'backend.form.defaultExecuteCommand': 'Команда выполнения по умолчанию',
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' и '{{'model_name'}}' заполняются реальными значениями во время запуска`,
'backend.form.defaultBackendParameters': 'Параметры бэкенда по умолчанию',
'backend.form.flagFormat': 'Формат флага',
'backend.form.flagFormat.tips':
'Формат соединения опции и её значения. Оставьте пустым, чтобы сохранить каждый параметр в исходном виде, без приведения к единому формату.',
'backend.form.flagFormat.space': 'Разделение пробелом (--key value)',
'backend.form.flagFormat.equal': 'Знак равенства (--key=value)',
'backend.form.commonParameters': 'Общие параметры бэкенда',
'backend.form.commonParameters.tips':
'Отображаются как подсказки в поле параметров бэкенда при развёртывании.',
'backend.form.versionConfig': 'Конфигурация версий',
'backend.form.addParameter': 'Добавить параметр',
'backend.form.noVersion': 'Версии не добавлены',
+2 -2
View File
@@ -37,8 +37,8 @@ export default {
'Stress test for long-context handling. Evaluates KV cache behavior, memory usage, and backend stability.',
'benchmark.form.profile.heavy.tips':
'Decode-heavy generation benchmark. Measures sustained decoding speed and output token throughput.',
'benchmark.table.filter.bygpu': 'Поиск GPU',
'benchmark.table.filter.bymodel': 'Поиск модели',
'benchmark.table.filter.bygpu': 'Filter by GPU',
'benchmark.table.filter.bymodel': 'Filter by Model',
'benchmark.table.filter.bydataset': 'Filter by Dataset',
'benchmark.table.filter.byProfile': 'Filter by Profile',
'benchmark.table.avg': 'Avg',
-15
View File
@@ -1,15 +0,0 @@
export default {
'billing.upsell.title': 'Billing is an Enterprise feature',
'billing.upsell.subtitle':
'Track spend, generate invoices, and enforce budgets across teams. Upgrade to MesaStack Enterprise to manage billing.',
'billing.upsell.featuresTitle': 'What you get in Enterprise',
'billing.upsell.feature.usage':
'See cost breakdowns by organization, user, and model',
'billing.upsell.feature.invoices':
'Generate invoices and export billing reports',
'billing.upsell.feature.budgets':
'Set budgets and spending limits with alerts',
'billing.upsell.feature.chargeback':
'Attribute and charge back usage to teams and projects',
'billing.upsell.cta': 'Learn about Enterprise'
};
+8 -46
View File
@@ -39,11 +39,9 @@ export default {
'clusters.workerpool.batchSize.desc':
'Количество воркеров, создаваемых одновременно в пуле воркеров',
'clusters.create.addworker.tips':
'Пожалуйста, убедитесь, что выполнены <a href={link} target="_blank">предварительные условия</a> перед выполнением следующей команды.',
'Пожалуйста, убедитесь, что выполнены <a href={link} target="_blank">предварительные условия</a> для {label} перед выполнением следующей команды.',
'clusters.create.addCommand.tips':
'На воркере, который необходимо добавить, выполните следующую команду, чтобы присоединить его к кластеру.',
'clusters.create.addCommand.k8s.tips':
'На Kubernetes-кластере, который необходимо зарегистрировать, выполните следующую команду, чтобы создать ресурсы Kubernetes и зарегистрировать этот кластер.',
'cluster.create.checkEnv.tips':
'Используйте следующую команду для проверки готовности окружения',
'clusters.create.register.tips':
@@ -69,14 +67,8 @@ export default {
'clusters.addworker.selectCluster.tips':
'Для <span class="bold-text">не-Docker</span> кластеров, пожалуйста, регистрируйте кластеры или управляйте пулами воркеров на странице Кластеры.',
'clusters.addworker.selectGPU': 'Выбрать производителя GPU',
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
'clusters.addworker.selectGPU.subtitle':
'Вы можете выбрать несколько производителей GPU или не выбирать для кластера только с CPU',
'clusters.addworker.checkEnv': 'Проверить окружение',
'clusters.addworker.checkEnv.cpuOnlyTips':
'Используйте следующую команду, чтобы убедиться, что в кластере Kubernetes есть хотя бы один готовый узел. Вы регистрируете кластер только с CPU.',
'clusters.addworker.specifyArgs': 'Указать аргументы',
'clusters.addworker.dtkVersion': 'Версия DTK',
'clusters.addworker.runCommand': 'Выполнить команду',
'clusters.addworker.specifyWorkerIP': 'Указать IP воркера',
'clusters.addworker.detectWorkerIP': 'Автоматически определить IP воркера',
@@ -113,20 +105,18 @@ export default {
'{count} новый воркер был добавлен в кластер.',
'clusters.addworker.message.success_multiple':
'{count} новых воркеров были добавлены в кластер.',
'clusters.create.serverUrl': 'URL сервера MesaStack',
'clusters.create.serverUrl': 'URL сервера GPUStack',
'clusters.create.workerConfig': 'Конфигурация воркера',
'clusters.edit.k8sOptions.changed.tip':
'Вы изменили параметры Kubernetes. Чтобы изменения вступили в силу, повторно выполните команду регистрации в целевом кластере.',
'clusters.addworker.containerName': 'Имя контейнера воркера',
'clusters.addworker.containerName.tips':
'Укажите имя для контейнера воркера.',
'clusters.addworker.dataVolume': 'Том данных MesaStack',
'clusters.addworker.dataVolume': 'Том данных GPUStack',
'clusters.addworker.dataVolume.tips':
'Укажите путь для хранения данных MesaStack.',
'Укажите путь для хранения данных GPUStack.',
'clusters.table.ip.internal': 'Внутренний',
'clusters.table.ip.external': 'Внешний',
'clusters.form.serverUrl.tips':
'Если рабочий узел не может напрямую получить доступ к MesaStack Server, укажите внешний URL службы MesaStack Server.',
'Если рабочий узел не может напрямую получить доступ к GPUStack Server, укажите внешний URL службы GPUStack Server.',
'clusters.form.setDefault': 'Установить по умолчанию',
'clusters.form.setDefault.tips':
'Использовать по умолчанию для развертывания.',
@@ -145,7 +135,7 @@ export default {
'clusters.addworker.theadNotes-02':
'T-Head PPU uses the Container Device Interface (CDI) for device injection and requires the <span class="bold-text">/var/run/cdi</span> directory to be available for CDI generation.',
'clusters.addworker.nvidiaNotes':
'The built-in inference backends in MesaStack require <span class="bold-text">CUDA 12.8+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">570</span> or newer.',
'The built-in inference backends in GPUStack v2.1 require <span class="bold-text">CUDA 12.6+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">560</span> or newer.',
'clusters.volume.title': 'Volume Mounts',
'clusters.volume.name': 'Volume Name',
'clusters.volume.mountPath': 'Container Path',
@@ -169,35 +159,7 @@ export default {
'clusters.volume.pvc.readOnly': 'Read Only',
'clusters.volume.configMap.name': 'ConfigMap Name',
'clusters.volume.configMap.optional': 'Optional',
'clusters.volume.add': 'Add Volume Mount',
'clusters.systemDefaultContainerRegistry.title': 'Default Container Registry',
'clusters.systemDefaultContainerRegistry.tip':
'Default registry used to resolve MesaStack 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 MesaStack 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.'
'clusters.volume.add': 'Add Volume Mount'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -213,5 +175,5 @@ export default {
// 10. 'clusters.addworker.theadNotes': 'If the <span class="bold-text>/usr/local/PPU_SDK</span> directory does not exist, please create a symbolic link pointing to the T-Head PPU SDK installed path: <span class="bold-text>ln -s /path/to/PPU_SDK /usr/local/PPU_SDK</span>',
// 11. 'clusters.addworker.theadNotes-02': 'T-Head PPU uses the Container Device Interface (CDI) for device injection and requires the <span class="bold-text">/var/run/cdi</span> directory to be available for CDI generation.'
// 12. 'clusters.addworker.metaxNotes': `If the <span class="bold-text">/opt/mxdriver</span> or <span class="bold-text">/opt/maca</span> directory does not exist, create a symbolic link to the MetaX driver and SDK installation path: <span class="desc-fill">ln -s /path/to/mxdriver /opt/mxdriver</span><span class="desc-fill">ln -s /path/to/maca /opt/maca</span>.`,
// 13. 'clusters.addworker.nvidiaNotes': 'The built-in inference backends in MesaStack v2.1 require <span class="bold-text">CUDA 12.6+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">560</span> or newer.'
// 13. 'clusters.addworker.nvidiaNotes': 'The built-in inference backends in GPUStack v2.1 require <span class="bold-text">CUDA 12.6+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">560</span> or newer.'
// ================================================================
+4 -24
View File
@@ -46,28 +46,22 @@ export default {
'common.button.enabled': 'Активно',
'common.button.disabled': 'Отключено',
'common.button.upgrade': 'Обновить',
'common.enterprise.feature': 'Available in MesaStack Enterprise',
'common.input.holder': 'Введите значение',
'common.validate.value': 'Поле {name} обязательно',
'common.button.edit': 'Редактировать',
'common.button.authorize': 'Настройка прав',
'common.button.confirm': 'Подтвердить',
'common.button.viewlog': 'Просмотр логов',
'common.button.viewevent': 'Просмотр событий',
'common.button.recreate': 'Пересоздать',
'common.table.operation': 'Действия',
'common.table.creator': 'Создатель',
'common.table.createTime': 'Создано',
'common.table.updateTime': 'Обновлено',
'common.table.description': 'Описание',
'common.table.displayName': 'Отображаемое имя',
'common.table.name': 'Название',
'common.table.status': 'Статус',
'common.table.name.list': 'Название {type}',
'common.search.name.placeholder': 'Фильтр по названию',
'common.search.id.placeholder': 'Фильтр по ID',
'common.filter.byId': 'Фильтр по ID',
'common.filter.byCreator': 'Фильтр по создателю',
'common.table.type': 'Тип',
'common.table.default': 'Значение по умолчанию',
'common.copy.success': 'Скопировано!',
@@ -167,7 +161,6 @@ export default {
'common.time.hour': 'Час',
'common.time.minute': 'Минута',
'common.issue.report': 'Сообщить о проблеме',
'common.github.star.tooltip': 'Поставьте нам звезду на GitHub',
'common.social.discord': 'Присоединиться к Discord',
'common.table.mark': 'Комментарий',
'common.table.rollback.mark': 'Комментарий к откату',
@@ -197,7 +190,6 @@ export default {
'common.table.user': 'Пользователь',
'common.settings.instructions': 'Инструкции',
'common.settings.language': 'Язык',
'common.settings.language.tips': 'Задайте язык отображения интерфейса.',
'common.delete.confirm': 'Вы уверены, что хотите удалить выбранный {type}?',
'common.delete.single.confirm':
'Вы уверены, что хотите удалить <span style="font-size: 13px;font-weight: 700">{name}</span>?',
@@ -211,7 +203,7 @@ export default {
'common.form.password': 'Пароль',
'common.form.username': 'Имя пользователя',
'common.login.rember': 'Запомнить меня',
'settings.company': 'MesaStack',
'settings.company': 'GPUStack.ai',
'common.button.help': 'Помощь',
'common.button.feedback': 'Обратная связь',
'common.button.docs': 'Документация',
@@ -229,6 +221,7 @@ export default {
'common.text.latest': 'Последняя',
'common.text.new': 'Новая',
'common.text.changelog': 'История изменений',
'common.button.recreate': 'Пересоздать',
'common.button.delrecreate': 'Удалить (Пересоздать)',
'common.options.all': 'Все',
'common.options.none': 'Нет',
@@ -251,11 +244,6 @@ export default {
'common.appearance.tips': 'По умолчанию соответствует системным настройкам.',
'common.button.forgotpassword': 'Забыли пароль?',
'common.appearance.theme': 'Тема',
'common.appearance.description':
'Настройте внешний вид интерфейса на вашем устройстве.',
'common.security': 'Безопасность',
'common.security.description':
'Управляйте паролем для входа в учётную запись.',
'common.page.wentwrong': 'Что-то пошло не так.',
'common.page.refresh.tips':
'Страница может нуждаться в обновлении. Попробуйте обновить её!',
@@ -266,15 +254,11 @@ export default {
'common.login.auth': 'Аутентификация...',
'common.login.auth.failed': 'Ошибка аутентификации',
'common.login.password': 'Войти с паролем',
'common.login.username.holder': 'Введите имя пользователя',
'common.login.password.holder': 'Введите пароль',
'common.login.newpassword.holder': 'Введите новый пароль',
'common.login.confirm.holder': 'Введите пароль ещё раз',
'common.external.login': 'Войти через {type}',
'common.sso.noConfig':
'Единый вход не настроен в этой системе. Пожалуйста, обратитесь к администратору.',
'common.button.edit.item': 'Редактировать {name}',
'common.button.copy.item': 'Дублировать {name}',
'common.button.copy.item': 'Duplicate {name}',
'common.button.terminal': 'Терминал',
'common.button.addItem': 'Добавить элемент',
'common.help.default': 'По умолчанию: {content}',
@@ -296,11 +280,7 @@ export default {
'common.file.format.limit': 'Invalid file format. Allowed: {formats}.',
'common.image.limit.width': 'Image width must be {width}.',
'common.image.limit.height': 'Image height must be {height}.',
'common.remaining': 'Остаток {count}',
'common.max': 'Макс. {count}',
'common.max.count': 'Количество {label}',
'common.validate.group': 'Please complete the {group} configuration',
'common.preferences': 'Preferences'
'common.max': 'Макс. {count}'
};
// ========== To-Do: Translate Keys (Remove After Translation) ==========
+14 -5
View File
@@ -1,22 +1,31 @@
export default {
'dashboard.title': 'Панель управления',
'dashboard.workers': 'Рабочие узлы',
'dashboard.deployments': 'Deployments',
'dashboard.models': 'Модели',
'dashboard.totalgpus': 'Всего GPU',
'dashboard.allocategpus': 'Выделенные GPU',
'dashboard.instances': 'Инстансы',
'dashboard.systemload': 'Нагрузка системы',
'dashboard.memory': 'ОЗУ',
'dashboard.disk': 'Хранилище',
'dashboard.vram': 'VRAM',
'dashboard.cpuutilization': 'Средняя загрузка CPU',
'dashboard.memoryutilization': 'Средняя загрузка ОЗУ',
'dashboard.diskutilization': 'Использование хранилища',
'dashboard.vramutilization': 'Средняя загрузка видеопамяти',
'dashboard.gpuutilization': 'Средняя загрузка GPU',
'dashboard.usage': 'Использование',
'dashboard.usage.title': 'Использование за последние {days} дн.',
'dashboard.usage.others': 'Прочее',
'dashboard.apirequest': 'API-запросы',
'dashboard.tokens': 'Использование токенов',
'dashboard.topusers': 'Топ пользователей',
'dashboard.activeDeployments': 'Active Deployments',
'dashboard.usageByModel': 'Использование по моделям',
'dashboard.activeModels': 'Активные модели',
'dashboard.activeUsers': 'Активные пользователи',
'dashboard.tokenUsageByModel': 'Использование токенов по моделям',
'dashboard.apiRequestsByModel': 'API-запросы по моделям',
'dashboard.topTokenUsageByUser': 'Топ-10 пользователей по токенам',
'dashboard.topTokenUsageByApiKey': 'Топ-10 API-ключей по токенам',
'dashboard.runninginstances': 'Запущенные инстансы',
'dashboard.activeModels.name': 'Название модели',
'dashboard.allocatevram': 'Выделено VRAM / ОЗУ',
'dashboard.usage.selectuser': 'Выбрать пользователей',
'dashboard.usage.selectmodel': 'Выбрать модели',

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