Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a19fea0c6c | ||
|
|
a516e6ce72 | ||
|
|
2e375c6603 | ||
|
|
7d94c77c15 | ||
|
|
457d2f2f72 | ||
|
|
5e7d83e5dd | ||
|
|
b1cffe047c | ||
|
|
6509f2a4ff | ||
|
|
f719c11606 | ||
|
|
b8d7873b77 | ||
|
|
a9248a98a5 | ||
|
|
8f85e9a082 | ||
|
|
1a6d1654b2 | ||
|
|
abe705c034 | ||
|
|
8b34f68824 | ||
|
|
e37828a2cd | ||
|
|
4709661f93 | ||
|
|
1d7543f19c | ||
|
|
1cf146ca32 | ||
|
|
b05c510776 | ||
|
|
e79d5f8962 | ||
|
|
5503fdc21e | ||
|
|
36a1038d12 | ||
|
|
ee42a9d0ed | ||
|
|
28976dea4b | ||
|
|
22e5cc3aa5 | ||
|
|
3052c0b23e | ||
|
|
1c9cabf7e7 | ||
|
|
03842d1741 | ||
|
|
84304eadb7 | ||
|
|
ab3739219e | ||
|
|
7cf4932f3a | ||
|
|
032f1eefef | ||
|
|
e35be94f7c | ||
|
|
e76e2ad85a | ||
|
|
cb19cd2476 | ||
|
|
c532732ce9 | ||
|
|
2bc10dbb2e | ||
|
|
b634a03ef9 | ||
|
|
ac64bb98ad | ||
|
|
aa49539210 | ||
|
|
1f5678f781 | ||
|
|
7adc089e94 | ||
|
|
6e1e9c07ee | ||
|
|
1d55382f3b | ||
|
|
e68bb0bc4e | ||
|
|
c8e14dade5 | ||
|
|
e4c1a3f5c2 | ||
|
|
cdd6ef9cb3 | ||
|
|
737d43fd16 | ||
|
|
9f5163531c | ||
|
|
df5cf0f4e5 | ||
|
|
e160b64697 | ||
|
|
7d36c6ac4a | ||
|
|
0820edf55f | ||
|
|
12e324893f | ||
|
|
4017c4dd33 | ||
|
|
17b65d80e1 | ||
|
|
47cb3321ab | ||
|
|
547b9f617b | ||
|
|
9e7bd78693 | ||
|
|
c53d60b628 | ||
|
|
9566e0cdb2 | ||
|
|
ed3e779d22 | ||
|
|
3e2133753d | ||
|
|
736a853192 | ||
|
|
08ef2ffa9a | ||
|
|
6cf512cccd | ||
|
|
6f56e614f3 | ||
|
|
8c06862705 | ||
|
|
48159171c2 | ||
|
|
d5d0749b72 | ||
|
|
dc4dd81e11 | ||
|
|
c736e2186d | ||
|
|
7a76bda484 | ||
|
|
778ea36a9d | ||
|
|
04e1e1e566 | ||
|
|
01f66085ab | ||
|
|
ddf723525a | ||
|
|
7b18a92388 | ||
|
|
b985dcee63 | ||
|
|
77e92432ed | ||
|
|
a7b33a928b | ||
|
|
1218c6d096 | ||
|
|
6337431ed4 | ||
|
|
2ba5118bc2 | ||
|
|
12649c7ccf | ||
|
|
5d96932870 | ||
|
|
e6cfa6cf77 | ||
|
|
55d16c5681 | ||
|
|
ab75685955 | ||
|
|
bd0bf0bf63 | ||
|
|
fd050b33d9 | ||
|
|
698090b17a | ||
|
|
6f96260993 | ||
|
|
98a6b19298 | ||
|
|
ded270ff25 | ||
|
|
c8860207e8 | ||
|
|
8d045ffd8b | ||
|
|
fd7437e74a | ||
|
|
807ba866d3 | ||
|
|
5e2808d7c4 | ||
|
|
a26a3ff8f9 | ||
|
|
a2e9f69b96 | ||
|
|
ac800a303a | ||
|
|
dd542a9f29 | ||
|
|
26ce456bde | ||
|
|
7ce1faaf6c | ||
|
|
878bdaa5a7 | ||
|
|
68a7e3f66a | ||
|
|
feccc81c5f | ||
|
|
620045d7a3 | ||
|
|
6dc28d61b0 | ||
|
|
754cc29793 | ||
|
|
ed525509ea | ||
|
|
2b6f26e6c8 | ||
|
|
09821aab85 | ||
|
|
636190b8e6 | ||
|
|
e420c22a45 | ||
|
|
993f6edcc4 |
@@ -0,0 +1,143 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,103 @@
|
||||
---
|
||||
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`.
|
||||
+1
-1
@@ -13,6 +13,6 @@
|
||||
.swc
|
||||
.DS_Store
|
||||
.idea
|
||||
.claude
|
||||
.claude/settings.local.json
|
||||
/dist.zip
|
||||
.cache
|
||||
@@ -1,265 +0,0 @@
|
||||
# React State and Request Patterns
|
||||
|
||||
These guidelines define preferred patterns for request handling, state updates, and side-effect management in React applications.
|
||||
|
||||
The primary goal is to keep data flow explicit, predictable, maintainable, and performant while avoiding unnecessary rerenders and effect-driven logic.
|
||||
|
||||
---
|
||||
|
||||
## 1. Avoid Effect-Driven Requests
|
||||
|
||||
Do not use request functions themselves as dependencies in `useEffect`.
|
||||
|
||||
Avoid patterns like:
|
||||
|
||||
```ts
|
||||
useEffect(() => {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
```
|
||||
|
||||
Requests should be triggered explicitly by user actions or lifecycle entry points.
|
||||
|
||||
---
|
||||
|
||||
## 2. Form Requests Should Be Action-Driven
|
||||
|
||||
For form-related requests (such as loading `Select` options):
|
||||
|
||||
- Fetch data when the form is opened for the first time.
|
||||
- If later requests depend on user interactions, trigger them directly inside the interaction handler.
|
||||
- Do not rely on `useEffect` dependency changes to trigger requests.
|
||||
|
||||
Recommended:
|
||||
|
||||
```ts
|
||||
const handleOnChange = (value) => {
|
||||
fetchData(value);
|
||||
};
|
||||
```
|
||||
|
||||
Avoid:
|
||||
|
||||
```ts
|
||||
useEffect(() => {
|
||||
fetchData(value);
|
||||
}, [value]);
|
||||
```
|
||||
|
||||
The action itself should control the request.
|
||||
|
||||
---
|
||||
|
||||
## 3. Update Related States Together
|
||||
|
||||
If a single action updates multiple related states:
|
||||
|
||||
- Do not synchronize them through `useEffect`
|
||||
- Do not derive them indirectly through `useMemo`
|
||||
|
||||
Instead, update all related states directly inside the action handler.
|
||||
|
||||
Recommended:
|
||||
|
||||
```ts
|
||||
const handleOnChange = (value) => {
|
||||
setState1(...);
|
||||
setState2(...);
|
||||
buildState(...);
|
||||
};
|
||||
```
|
||||
|
||||
Avoid implicit state synchronization chains.
|
||||
|
||||
---
|
||||
|
||||
## 4. Group Strongly Related State
|
||||
|
||||
If multiple states are always updated together:
|
||||
|
||||
- Do not split them into multiple `useState` calls.
|
||||
- Prefer a single state object.
|
||||
|
||||
Recommended:
|
||||
|
||||
```ts
|
||||
const [state, setState] = useState({
|
||||
state1: ...,
|
||||
state2: ...,
|
||||
state3: ...,
|
||||
});
|
||||
```
|
||||
|
||||
This reduces unnecessary rerenders and keeps state transitions predictable.
|
||||
|
||||
---
|
||||
|
||||
## 5. Prefer Explicit State Flow
|
||||
|
||||
Avoid chaining business logic through multiple `useEffect` hooks.
|
||||
|
||||
Keep:
|
||||
|
||||
- request execution
|
||||
- state updates
|
||||
- derived calculations
|
||||
|
||||
close to the triggering action whenever possible.
|
||||
|
||||
Prefer:
|
||||
|
||||
```ts
|
||||
const handleAction = () => {
|
||||
fetchData();
|
||||
setTableData(...);
|
||||
setSelectedRow(...);
|
||||
};
|
||||
```
|
||||
|
||||
Over:
|
||||
|
||||
```ts
|
||||
useEffect(() => {
|
||||
buildTable();
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
updateSelection();
|
||||
}, [tableData]);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Avoid Premature Memoization
|
||||
|
||||
Do not use `useMemo` or `useCallback` unless there is a confirmed rendering or computation bottleneck.
|
||||
|
||||
Overusing memoization:
|
||||
|
||||
- increases complexity
|
||||
- makes state flow harder to understand
|
||||
- may introduce stale dependency issues
|
||||
|
||||
Prefer simple and explicit logic first.
|
||||
|
||||
Optimize only when necessary.
|
||||
|
||||
---
|
||||
|
||||
## 7. Keep Request Logic Predictable
|
||||
|
||||
A user interaction should clearly show:
|
||||
|
||||
- what request is triggered
|
||||
- which states are updated
|
||||
- how the UI changes
|
||||
|
||||
Avoid indirect update chains caused by dependency-driven effects.
|
||||
|
||||
The code should make the request and update flow easy to trace.
|
||||
|
||||
---
|
||||
|
||||
## 8. Prefer Action-Driven Architecture
|
||||
|
||||
Prefer:
|
||||
|
||||
- action-driven updates
|
||||
- explicit handlers
|
||||
- localized state transitions
|
||||
|
||||
Over:
|
||||
|
||||
- effect-driven synchronization
|
||||
- cross-hook implicit updates
|
||||
- reactive chains between states
|
||||
|
||||
The triggering action should remain the primary source of truth for UI updates.
|
||||
|
||||
---
|
||||
|
||||
# Form
|
||||
|
||||
Form-specific patterns that build on the rules above. The theme: keep cascading selections (pick A → derive B → write form) on a single, predictable path.
|
||||
|
||||
## 1. No Fallback for Derived Selection
|
||||
|
||||
When "pick A then auto-pick B", match by rule and return `undefined` if no match — let the corresponding form field stay empty.
|
||||
|
||||
Do not silently fall back to `list[0]` or another default. A fallback hides data issues and tricks the user into thinking they have a valid selection.
|
||||
|
||||
```ts
|
||||
const findB = (key, list) =>
|
||||
key ? list.find((x) => x.key === key) : undefined;
|
||||
```
|
||||
|
||||
For form fields, prefer clearing with `undefined` over `''`. With Ant Design, `undefined` restores the placeholder; `''` is treated as a real value.
|
||||
|
||||
## 2. Async Race Protection
|
||||
|
||||
For fetches triggered by a lifecycle entry (e.g., modal open), tag each invocation with a session ref. Discard stale results if the session has rotated (the modal was closed and re-opened) by the time the response arrives.
|
||||
|
||||
```ts
|
||||
const sessionRef = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
sessionRef.current += 1;
|
||||
return;
|
||||
}
|
||||
const session = ++sessionRef.current;
|
||||
Promise.all([fetchA(), fetchB()]).then(([as, bs]) => {
|
||||
if (sessionRef.current !== session) return;
|
||||
applySelection(as.items[0], findB(as.items[0].key, bs.items));
|
||||
});
|
||||
}, [open]);
|
||||
```
|
||||
|
||||
## 3. Reference Template
|
||||
|
||||
A typical form with two cascading selectors backed by a single shared state:
|
||||
|
||||
```ts
|
||||
type Selection = { a?: string; b?: number };
|
||||
|
||||
const [selection, setSelection] = useState<Selection>({});
|
||||
const sessionRef = useRef(0);
|
||||
|
||||
const findB = (key, list) =>
|
||||
key ? list.find((x) => x.key === key) : undefined;
|
||||
|
||||
// Single atomic write: state + form together.
|
||||
const applySelection = (a, b) => {
|
||||
setSelection({ a: a.name, b: b?.id });
|
||||
form.current?.setFieldsValue({
|
||||
field: b?.field,
|
||||
spec: { ...currentSpec, ...b?.spec }
|
||||
});
|
||||
};
|
||||
|
||||
// Trigger 1: modal opened
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
sessionRef.current++;
|
||||
setSelection({});
|
||||
return;
|
||||
}
|
||||
const session = ++sessionRef.current;
|
||||
Promise.all([fetchA(), fetchB()]).then(([as, bs]) => {
|
||||
if (sessionRef.current !== session) return;
|
||||
const first = as.items[0];
|
||||
applySelection(first, findB(first.key, bs.items));
|
||||
});
|
||||
}, [open]);
|
||||
|
||||
// Trigger 2: user picks A
|
||||
const handleAChange = (a) => {
|
||||
applySelection(a, findB(a.key, listB));
|
||||
};
|
||||
|
||||
// Trigger 3: user picks B
|
||||
const handleBChange = (b) => {
|
||||
setSelection((prev) => ({ ...prev, b: b.id }));
|
||||
form.current?.setFieldsValue({ ...b.fields });
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
# 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
|
||||
@@ -0,0 +1,129 @@
|
||||
# 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`.
|
||||
@@ -1,46 +0,0 @@
|
||||
## 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.
|
||||
@@ -31,5 +31,6 @@ export default function createProxyTable(target?: string) {
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
return proxyTable;
|
||||
}
|
||||
|
||||
@@ -338,6 +338,20 @@ const baseRoutes = [
|
||||
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'
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+1
-1
@@ -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.22",
|
||||
"@gpustack/core-ui": "^1.0.32",
|
||||
"@huggingface/gguf": "^0.1.7",
|
||||
"@huggingface/hub": "^0.15.1",
|
||||
"@huggingface/tasks": "^0.11.6",
|
||||
|
||||
@@ -5,6 +5,8 @@ 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(
|
||||
|
||||
Generated
+117
-117
@@ -24,8 +24,8 @@ importers:
|
||||
specifier: ^7.1.1
|
||||
version: 7.1.2
|
||||
'@gpustack/core-ui':
|
||||
specifier: ^1.0.22
|
||||
version: 1.0.22(czdvzceysqw7iv6pct2ucnb23e)
|
||||
specifier: ^1.0.32
|
||||
version: 1.0.32(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.29.0)(@types/node@25.6.2)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(lightningcss@1.22.1)(prettier@3.8.3)(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
|
||||
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))
|
||||
'@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.29.0)(@babel/template@7.28.6)(@types/react@18.3.28)(react@18.3.1)
|
||||
version: 2.20.0(@babel/core@7.23.6)(@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.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(chokidar@3.6.0)(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(encoding@0.1.13)(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)))
|
||||
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)))
|
||||
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.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)
|
||||
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)
|
||||
babel-plugin-named-asset-import:
|
||||
specifier: ^0.3.8
|
||||
version: 0.3.8(@babel/core@7.29.0)
|
||||
version: 0.3.8(@babel/core@7.23.6)
|
||||
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.22':
|
||||
resolution: {integrity: sha512-uFLGalziwuojjMIeXMlCZn3tLRGA6PCa0JxLxa/+kKP+xAsy7Fcxgh4PerbTyk+P+hKnYv8cD+DPpkObDGqJgg==, tarball: https://registry.npmjs.org/@gpustack/core-ui/-/core-ui-1.0.22.tgz}
|
||||
'@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}
|
||||
peerDependencies:
|
||||
'@ant-design/icons': '>=6.0.0'
|
||||
'@ant-design/icons': ^6.1.0
|
||||
'@ant-design/pro-components': 3.1.0-0
|
||||
'@monaco-editor/react': ^4.6.0
|
||||
ahooks: '>=3.0.0'
|
||||
antd: '>=6.0.0'
|
||||
antd-style: '>=3.0.0'
|
||||
axios: '>=1.8.0'
|
||||
echarts: '>=5.0.0'
|
||||
ahooks: ^3.8.5
|
||||
antd: ^6.3.3
|
||||
antd-style: ^3.6.2
|
||||
axios: ^1.8.2
|
||||
echarts: ^5.5.1
|
||||
file-saver: ^2.0.5
|
||||
monaco-editor: ^0.30.1
|
||||
monaco-yaml: ^4.0.0
|
||||
overlayscrollbars-react: ^0.5.6
|
||||
react: '>=18.0.0'
|
||||
react-dom: '>=18.0.0'
|
||||
styled-components: '>=6.0.0'
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
styled-components: ^6.1.15
|
||||
|
||||
'@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}
|
||||
@@ -9257,14 +9257,14 @@ snapshots:
|
||||
'@radix-ui/popper': 0.0.10
|
||||
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)':
|
||||
'@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)':
|
||||
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.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@umijs/plugins': 4.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/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 +10134,90 @@ snapshots:
|
||||
dependencies:
|
||||
'@babel/types': 7.29.0
|
||||
|
||||
'@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)':
|
||||
'@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
|
||||
'@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.29.0)':
|
||||
'@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0)
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/helper-module-transforms': 7.28.6(@babel/core@7.23.6)
|
||||
'@babel/helper-plugin-utils': 7.28.6
|
||||
'@babel/helper-simple-access': 7.27.1
|
||||
transitivePeerDependencies:
|
||||
@@ -10808,7 +10808,7 @@ snapshots:
|
||||
|
||||
'@formatjs/intl-utils@2.3.0': {}
|
||||
|
||||
'@gpustack/core-ui@1.0.22(czdvzceysqw7iv6pct2ucnb23e)':
|
||||
'@gpustack/core-ui@1.0.32(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)
|
||||
@@ -12281,11 +12281,11 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@umijs/bundler-utoopack@4.6.51(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))':
|
||||
'@umijs/bundler-utoopack@4.6.51(styled-jsx@5.1.7(@babel/core@7.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))':
|
||||
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.29.0)(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.23.6)(react@18.3.1))
|
||||
compression: 1.8.1
|
||||
connect-history-api-fallback: 2.0.0
|
||||
cors: 2.8.6
|
||||
@@ -12553,14 +12553,14 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@umijs/max@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(lightningcss@1.22.1)(prettier@3.8.3)(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(terser@5.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.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))':
|
||||
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.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@4.24.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@umijs/plugins': 4.6.51(@babel/core@7.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)
|
||||
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.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
|
||||
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))
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
- '@rspack/core'
|
||||
@@ -12642,7 +12642,7 @@ snapshots:
|
||||
dependencies:
|
||||
tsx: 3.12.2
|
||||
|
||||
'@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/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)':
|
||||
dependencies:
|
||||
'@ahooksjs/use-request': 2.8.15(react@18.3.1)
|
||||
'@ant-design/antd-theme-variable': 1.0.0
|
||||
@@ -12657,7 +12657,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.29.0)(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.23.6)(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 +12687,7 @@ snapshots:
|
||||
- react-native
|
||||
- supports-color
|
||||
|
||||
'@umijs/plugins@4.6.51(@babel/core@7.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@4.24.16(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@umijs/plugins@4.6.51(@babel/core@7.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)':
|
||||
dependencies:
|
||||
'@ahooksjs/use-request': 2.8.15(react@18.3.1)
|
||||
'@ant-design/antd-theme-variable': 1.0.0
|
||||
@@ -12702,7 +12702,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.29.0)(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.23.6)(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 +12732,7 @@ snapshots:
|
||||
- react-native
|
||||
- supports-color
|
||||
|
||||
'@umijs/plugins@4.6.51(@babel/core@7.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@umijs/plugins@4.6.51(@babel/core@7.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)':
|
||||
dependencies:
|
||||
'@ahooksjs/use-request': 2.8.15(react@18.3.1)
|
||||
'@ant-design/antd-theme-variable': 1.0.0
|
||||
@@ -12747,7 +12747,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.29.0)(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.23.6)(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 +12777,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.29.0)(react@18.3.1))(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))':
|
||||
'@umijs/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))':
|
||||
dependencies:
|
||||
'@iconify/utils': 2.1.1
|
||||
'@stagewise/toolbar': 0.6.2
|
||||
@@ -12787,7 +12787,7 @@ 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.29.0)(react@18.3.1))(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
|
||||
'@umijs/bundler-utoopack': 4.6.51(styled-jsx@5.1.7(@babel/core@7.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-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
|
||||
@@ -12870,13 +12870,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.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)))':
|
||||
'@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)))':
|
||||
dependencies:
|
||||
chokidar: 3.6.0
|
||||
express: 4.22.1
|
||||
lodash: 4.18.1
|
||||
prettier: 2.8.8
|
||||
umi: 4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.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.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))
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -12892,13 +12892,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@umijs/test@4.6.51(@babel/core@7.29.0)':
|
||||
'@umijs/test@4.6.51(@babel/core@7.23.6)':
|
||||
dependencies:
|
||||
'@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.29.0)
|
||||
'@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.23.6)
|
||||
'@jest/types': 27.5.1
|
||||
'@umijs/bundler-utils': 4.6.51
|
||||
'@umijs/utils': 4.6.51
|
||||
babel-jest: 29.7.0(@babel/core@7.29.0)
|
||||
babel-jest: 29.7.0(@babel/core@7.23.6)
|
||||
esbuild: 0.21.4
|
||||
identity-obj-proxy: 3.0.0
|
||||
isomorphic-unfetch: 4.0.2
|
||||
@@ -13018,7 +13018,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.29.0)(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.23.6)(react@18.3.1))':
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.22.5
|
||||
'@hono/node-server': 1.19.14(hono@4.12.18)
|
||||
@@ -13039,7 +13039,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.29.0)(react@18.3.1)
|
||||
styled-jsx: 5.1.7(@babel/core@7.23.6)(react@18.3.1)
|
||||
ws: 8.20.0
|
||||
optionalDependencies:
|
||||
'@utoo/pack-darwin-arm64': 1.4.3
|
||||
@@ -13555,13 +13555,13 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- debug
|
||||
|
||||
babel-jest@29.7.0(@babel/core@7.29.0):
|
||||
babel-jest@29.7.0(@babel/core@7.23.6):
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@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.29.0)
|
||||
babel-preset-jest: 29.6.3(@babel/core@7.23.6)
|
||||
chalk: 4.1.2
|
||||
graceful-fs: 4.2.11
|
||||
slash: 3.0.0
|
||||
@@ -13601,9 +13601,9 @@ snapshots:
|
||||
cosmiconfig: 7.1.0
|
||||
resolve: 1.22.12
|
||||
|
||||
babel-plugin-named-asset-import@0.3.8(@babel/core@7.29.0):
|
||||
babel-plugin-named-asset-import@0.3.8(@babel/core@7.23.6):
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
|
||||
babel-plugin-react-compiler@0.0.0-experimental-c23de8d-20240515:
|
||||
dependencies:
|
||||
@@ -13615,11 +13615,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.29.0)(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.23.6)(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.29.0)
|
||||
'@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.23.6)
|
||||
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 +13627,30 @@ snapshots:
|
||||
- '@babel/core'
|
||||
- supports-color
|
||||
|
||||
babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0):
|
||||
babel-preset-current-node-syntax@1.2.0(@babel/core@7.23.6):
|
||||
dependencies:
|
||||
'@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/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-preset-jest@29.6.3(@babel/core@7.29.0):
|
||||
babel-preset-jest@29.6.3(@babel/core@7.23.6):
|
||||
dependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
babel-plugin-jest-hoist: 29.6.3
|
||||
babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0)
|
||||
babel-preset-current-node-syntax: 1.2.0(@babel/core@7.23.6)
|
||||
|
||||
babel-runtime-jsx-plus@0.1.5: {}
|
||||
|
||||
@@ -16410,9 +16410,9 @@ snapshots:
|
||||
|
||||
jiti@2.7.0: {}
|
||||
|
||||
jotai@2.20.0(@babel/core@7.29.0)(@babel/template@7.28.6)(@types/react@18.3.28)(react@18.3.1):
|
||||
jotai@2.20.0(@babel/core@7.23.6)(@babel/template@7.28.6)(@types/react@18.3.28)(react@18.3.1):
|
||||
optionalDependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
'@babel/template': 7.28.6
|
||||
'@types/react': 18.3.28
|
||||
react: 18.3.1
|
||||
@@ -19805,12 +19805,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.29.0)(react@18.3.1):
|
||||
styled-jsx@5.1.7(@babel/core@7.23.6)(react@18.3.1):
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@babel/core': 7.29.0
|
||||
'@babel/core': 7.23.6
|
||||
|
||||
stylelint-config-recommended@7.0.0(stylelint@14.8.2):
|
||||
dependencies:
|
||||
@@ -20164,12 +20164,12 @@ snapshots:
|
||||
|
||||
ua-parser-js@0.7.41: {}
|
||||
|
||||
umi-presets-pro@2.0.3(@babel/core@7.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(chokidar@3.6.0)(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(encoding@0.1.13)(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)(umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.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.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))):
|
||||
dependencies:
|
||||
'@alita/plugins': 3.5.5(@babel/core@7.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@alita/plugins': 3.5.5(@babel/core@7.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/max-plugin-openapi': 2.0.3(chokidar@3.6.0)(encoding@0.1.13)(typescript@5.9.3)
|
||||
'@umijs/plugins': 4.6.51(@babel/core@7.29.0)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(dva@2.5.0-beta.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(rc-field-form@2.7.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@umijs/request-record': 1.1.4(umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14)))
|
||||
'@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)))
|
||||
swagger-ui-dist: 4.19.1
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
@@ -20193,17 +20193,17 @@ snapshots:
|
||||
isomorphic-fetch: 2.2.1
|
||||
qs: 6.15.1
|
||||
|
||||
umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@8.35.0)(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.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.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)):
|
||||
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.29.0)(react@18.3.1))(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
|
||||
'@umijs/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/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.29.0)
|
||||
'@umijs/test': 4.6.51(@babel/core@7.23.6)
|
||||
'@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 +20247,17 @@ snapshots:
|
||||
- webpack-hot-middleware
|
||||
- webpack-plugin-serve
|
||||
|
||||
umi@4.6.51(@babel/core@7.29.0)(@types/node@25.6.2)(@types/react@18.3.28)(eslint@9.39.4(jiti@2.7.0))(lightningcss@1.22.1)(prettier@3.8.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@3.30.0)(sass@1.54.0)(styled-jsx@5.1.7(@babel/core@7.29.0)(react@18.3.1))(stylelint@14.8.2)(terser@5.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.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)):
|
||||
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.29.0)(react@18.3.1))(terser@5.48.0)(type-fest@0.20.2)(typescript@5.9.3)(webpack@5.106.2(lightningcss@1.22.1)(postcss@8.5.14))
|
||||
'@umijs/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/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.29.0)
|
||||
'@umijs/test': 4.6.51(@babel/core@7.23.6)
|
||||
'@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)
|
||||
|
||||
+17
-85
@@ -3,9 +3,6 @@ import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } 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 { queryClusterList } from '@/pages/cluster-management/apis';
|
||||
import { ProviderValueMap } from '@/pages/cluster-management/config';
|
||||
import { queryResourceEvents } from '@/pages/usage/apis/resource';
|
||||
import { getGPUStackPlugin } from '@/plugins';
|
||||
import { enterprisePluginReady } from '@/plugins/enterprise-ready';
|
||||
import { GPUStackPluginManager } from '@/plugins/manager';
|
||||
@@ -17,6 +14,10 @@ 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,
|
||||
@@ -41,80 +42,6 @@ const checkDefaultPage = async (userInfo: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Probes the caller's cluster list once so access predicates can gate
|
||||
// GPU Service (Kubernetes-only). Cheap (one list request) and never
|
||||
// blocks login — any failure just falls back to `undefined`, which
|
||||
// the predicate treats as "unknown / don't restrict beyond role".
|
||||
// The result is also mirrored into sessionStorage so access extensions
|
||||
// that run without the initialState argument can read it (e.g. to
|
||||
// override the admin shortcut in scopes where the menu shouldn't
|
||||
// show even for admins).
|
||||
const HAS_K8S_CLUSTER_KEY = 'hasKubernetesCluster';
|
||||
const probeHasKubernetesCluster = async (): Promise<boolean | undefined> => {
|
||||
try {
|
||||
const res = await queryClusterList(
|
||||
{ page: -1 },
|
||||
{
|
||||
skipErrorHandler: true
|
||||
}
|
||||
);
|
||||
const value = (res?.items ?? []).some(
|
||||
(c) => c?.provider === ProviderValueMap.Kubernetes
|
||||
);
|
||||
try {
|
||||
window.sessionStorage.setItem(HAS_K8S_CLUSTER_KEY, JSON.stringify(value));
|
||||
} catch {
|
||||
// sessionStorage may be unavailable (Safari private mode); the
|
||||
// access predicate already handles a missing value as "unknown".
|
||||
}
|
||||
return value;
|
||||
} catch (error) {
|
||||
console.error('probeHasKubernetesCluster error', error);
|
||||
try {
|
||||
window.sessionStorage.removeItem(HAS_K8S_CLUSTER_KEY);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// Probes whether the caller has ANY resource-usage events (GPU/CPU instance or
|
||||
// storage lifecycle). Used alongside the cluster probe so a user who has run
|
||||
// GPU instances still sees GPU Service / the full Usage page even if they
|
||||
// currently have no Kubernetes cluster. Mirrored into sessionStorage for the
|
||||
// access extensions; any failure → undefined ("unknown — don't restrict").
|
||||
const HAS_RESOURCE_EVENTS_KEY = 'hasResourceEvents';
|
||||
const probeHasResourceEvents = async (): Promise<boolean | undefined> => {
|
||||
try {
|
||||
// No date range = "ever"; scope is clamped to the caller server-side.
|
||||
const res = await queryResourceEvents(
|
||||
{ perPage: 1 },
|
||||
{
|
||||
skipErrorHandler: true
|
||||
}
|
||||
);
|
||||
const value = (res?.pagination?.total ?? 0) > 0;
|
||||
try {
|
||||
window.sessionStorage.setItem(
|
||||
HAS_RESOURCE_EVENTS_KEY,
|
||||
JSON.stringify(value)
|
||||
);
|
||||
} catch {
|
||||
// sessionStorage may be unavailable; predicate treats missing as unknown.
|
||||
}
|
||||
return value;
|
||||
} catch (error) {
|
||||
console.error('probeHasResourceEvents error', error);
|
||||
try {
|
||||
window.sessionStorage.removeItem(HAS_RESOURCE_EVENTS_KEY);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
// runtime configuration
|
||||
export async function getInitialState(): Promise<{
|
||||
fetchUserInfo: () => Promise<Global.UserInfo>;
|
||||
@@ -235,19 +162,24 @@ export async function getInitialState(): Promise<{
|
||||
getAppVersionInfo();
|
||||
|
||||
if (![DEFAULT_ENTER_PAGE.login].includes(location.pathname)) {
|
||||
const [userInfo, hasKubernetesCluster, hasResourceEvents] =
|
||||
await Promise.all([
|
||||
fetchUserInfo(),
|
||||
probeHasKubernetesCluster(),
|
||||
probeHasResourceEvents()
|
||||
]);
|
||||
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();
|
||||
}
|
||||
checkDefaultPage(userInfo);
|
||||
return {
|
||||
fetchUserInfo,
|
||||
currentUser: userInfo,
|
||||
pluginData,
|
||||
hasKubernetesCluster,
|
||||
hasResourceEvents
|
||||
...accessFlags
|
||||
};
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 656 B |
@@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -63,6 +63,7 @@ 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
|
||||
|
||||
@@ -55,3 +55,10 @@ export const userSettingsHelperAtom = atom(
|
||||
}
|
||||
);
|
||||
export const hideModalTemporarilyAtom = atom<boolean>(false);
|
||||
|
||||
export const collapsedMenuGroupsAtom = atomWithStorage<string[]>(
|
||||
'collapsedMenuGroups',
|
||||
[],
|
||||
undefined,
|
||||
{ getOnInit: true }
|
||||
);
|
||||
|
||||
+1
-4
@@ -32,10 +32,7 @@ export const UpdateCheckAtom = atom<{
|
||||
latest_version: ''
|
||||
});
|
||||
|
||||
export const initialPasswordAtom = atomWithStorage<string>(
|
||||
'initialPassword',
|
||||
''
|
||||
);
|
||||
export const initialPasswordAtom = atom<string>('');
|
||||
|
||||
// Namespace the server creates for an Org's resources on each Kubernetes
|
||||
// cluster. The format must match the backend's ``get_namespace_name``
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4613488 */
|
||||
src: url('iconfont.woff2?t=1780035199453') format('woff2'),
|
||||
url('iconfont.woff?t=1780035199453') format('woff'),
|
||||
url('iconfont.ttf?t=1780035199453') format('truetype');
|
||||
src: url('iconfont.woff2?t=1780907696624') format('woff2'),
|
||||
url('iconfont.woff?t=1780907696624') format('woff'),
|
||||
url('iconfont.ttf?t=1780907696624') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
@@ -13,6 +13,18 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-billing-outlined:before {
|
||||
content: "\e70d";
|
||||
}
|
||||
|
||||
.icon-billing-filled:before {
|
||||
content: "\e70e";
|
||||
}
|
||||
|
||||
.icon-fingerprint:before {
|
||||
content: "\e70c";
|
||||
}
|
||||
|
||||
.icon-database-outlined:before {
|
||||
content: "\e70a";
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,27 @@
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "47798379",
|
||||
"name": "request_quote_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz",
|
||||
"font_class": "billing-outlined",
|
||||
"unicode": "e70d",
|
||||
"unicode_decimal": 59149
|
||||
},
|
||||
{
|
||||
"icon_id": "47798378",
|
||||
"name": "request_quote_24dp_1F1F1F_FILL1_wght300_GRAD0_opsz",
|
||||
"font_class": "billing-filled",
|
||||
"unicode": "e70e",
|
||||
"unicode_decimal": 59150
|
||||
},
|
||||
{
|
||||
"icon_id": "47761129",
|
||||
"name": "fingerprint",
|
||||
"font_class": "fingerprint",
|
||||
"unicode": "e70c",
|
||||
"unicode_decimal": 59148
|
||||
},
|
||||
{
|
||||
"icon_id": "47654402",
|
||||
"name": "database_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz24 (1",
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+10
-3
@@ -276,7 +276,6 @@ body {
|
||||
border-radius: var(--table-td-radius);
|
||||
|
||||
> td {
|
||||
background-color: unset !important;
|
||||
border-bottom: 1px solid var(--ant-color-split);
|
||||
}
|
||||
}
|
||||
@@ -639,10 +638,15 @@ body {
|
||||
inset: 53px auto auto 0 !important;
|
||||
border-radius: 0 0 var(--border-radius-base) var(--border-radius-base);
|
||||
border-top: none;
|
||||
max-height: 240px;
|
||||
min-height: 100px;
|
||||
.ant-cascader-menus {
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
&.gpu-selector {
|
||||
.ant-cascader-menu {
|
||||
max-height: 400px;
|
||||
height: unset;
|
||||
}
|
||||
.ant-cascader-menu:last-child {
|
||||
flex: auto;
|
||||
|
||||
@@ -847,3 +851,6 @@ body {
|
||||
padding-right: 8px;
|
||||
padding-block: 8px;
|
||||
}
|
||||
.ant-select-multiple .ant-select-content {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
+113
-35
@@ -1,54 +1,132 @@
|
||||
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
|
||||
import { converter, modeHsl, modeRgb, useMode } from 'culori/fn';
|
||||
import { clampChroma, formatHex, modeOklch, modeRgb, useMode } from 'culori/fn';
|
||||
import useUserSettings from './use-user-settings';
|
||||
|
||||
const toHsl = converter('hsl');
|
||||
|
||||
const DEFAULT_BRAND_HUE = 211; // brand color
|
||||
const COOL_HUE_START = 180;
|
||||
const COOL_HUE_END = 280;
|
||||
// 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 COOL_HUE_RANGE = COOL_HUE_END - COOL_HUE_START;
|
||||
const GOLDEN_RATIO_CONJUGATE = 0.618033988749895;
|
||||
|
||||
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]
|
||||
// );
|
||||
/**
|
||||
* 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(modeHsl);
|
||||
useMode(modeOklch);
|
||||
|
||||
const brandHue = DEFAULT_BRAND_HUE;
|
||||
const { isDarkTheme } = useUserSettings();
|
||||
|
||||
return useMemoizedFn((count: number): string[] => {
|
||||
if (count <= 0) return [];
|
||||
|
||||
const colors: string[] = [];
|
||||
const l = isDarkTheme ? 0.62 : 0.64;
|
||||
const c = isDarkTheme ? 0.16 : 0.2;
|
||||
|
||||
const out: string[] = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
let hue: number;
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
hue = brandHue - 5;
|
||||
} else {
|
||||
const offset = (i * GOLDEN_RATIO_CONJUGATE) % 1;
|
||||
hue = COOL_HUE_START + offset * COOL_HUE_RANGE;
|
||||
export default function useCoolColors() {
|
||||
useMode(modeRgb);
|
||||
useMode(modeOklch);
|
||||
|
||||
if (Math.abs(hue - brandHue) < 5) {
|
||||
hue = (hue + 10) % COOL_HUE_END;
|
||||
}
|
||||
}
|
||||
const { isDarkTheme } = useUserSettings();
|
||||
|
||||
const s = i === 0 ? 100 : 75 + (i % 3) * 5;
|
||||
const l = i === 0 ? 50 : 55 + (i % 2) * 5;
|
||||
return useMemoizedFn((count: number): string[] => {
|
||||
if (count <= 0) return [];
|
||||
|
||||
colors.push(`hsl(${Math.round(hue)}, ${s}%, ${l}%)`);
|
||||
// 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;
|
||||
|
||||
// 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'
|
||||
)
|
||||
)
|
||||
];
|
||||
|
||||
const rest = count - 1;
|
||||
if (rest <= 0) return colors;
|
||||
|
||||
// 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);
|
||||
|
||||
// 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];
|
||||
|
||||
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'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return colors;
|
||||
|
||||
@@ -18,6 +18,7 @@ export function useQueryDataList<
|
||||
Response = Array<ListItem>
|
||||
>(option: {
|
||||
key: string;
|
||||
manual?: boolean;
|
||||
responseType?: 'array' | 'object';
|
||||
fetchList: (
|
||||
params: Params,
|
||||
@@ -38,6 +39,7 @@ export function useQueryDataList<
|
||||
fetchList,
|
||||
getLabel,
|
||||
getValue,
|
||||
manual = true,
|
||||
responseType = 'array',
|
||||
errorMsg
|
||||
} = option;
|
||||
@@ -70,7 +72,7 @@ export function useQueryDataList<
|
||||
return responseType === 'array' ? res.items || [] : res;
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
manual: manual,
|
||||
debounceWait: option.debounceWait || 300,
|
||||
onSuccess: () => {},
|
||||
onError: (error) => {
|
||||
@@ -105,16 +107,18 @@ 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 } = option;
|
||||
const { key, fetchDetail, getData, errorMsg, delay, manual = true } = option;
|
||||
const axiosTokenRef = useRef<CancelTokenSource | null>(null);
|
||||
const [detailData, setDetailData] = useState<Detail>({} as Detail);
|
||||
|
||||
@@ -142,7 +146,7 @@ export function useQueryData<Detail, Params = any>(option: {
|
||||
return res;
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
manual: manual,
|
||||
onSuccess: () => {},
|
||||
onError: (error) => {
|
||||
message.error(
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
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 };
|
||||
}
|
||||
@@ -89,6 +89,10 @@ export function useUpdateChunkedList(options: {
|
||||
...cacheDataListRef.current
|
||||
].slice(0, limit);
|
||||
|
||||
options.setDataList?.([...cacheDataListRef.current], {
|
||||
createdIds: newDataList.map((item) => item.id)
|
||||
});
|
||||
|
||||
options.onCreate?.(latestCreateList);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,7 @@ export default function useWatchList<T = Record<string, any>>(API: string) {
|
||||
listRequestTokenRef.current?.cancel?.();
|
||||
listRequestTokenRef.current = createAxiosToken();
|
||||
const params = {
|
||||
page: 1,
|
||||
perPage: 100
|
||||
page: -1
|
||||
};
|
||||
const res: any = await queryAllDataList(params, {
|
||||
token: listRequestTokenRef.current.token
|
||||
@@ -86,6 +85,7 @@ export default function useWatchList<T = Record<string, any>>(API: string) {
|
||||
|
||||
return {
|
||||
watchDataList,
|
||||
setWatchDataList,
|
||||
deleteItemFromCache: handleDeleteItemFromCache
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { useAtom } from 'jotai';
|
||||
import { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { DEFAULT_ENTER_PAGE } from '../config/settings';
|
||||
import GithubStar from './github-star';
|
||||
|
||||
const NewLabel = styled.span`
|
||||
position: relative;
|
||||
@@ -260,6 +261,7 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
|
||||
<Wrapper>
|
||||
{contextHolder}
|
||||
<PluginExtraField name="OrgSwitcher" isDarkTheme={isDarkTheme} />
|
||||
{process.env.ENABLE_ENTERPRISE !== 'true' && <GithubStar />}
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import externalLinks from '@/constants/external-links';
|
||||
import { GithubFilled } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Tooltip } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const REPO = 'gpustack/gpustack';
|
||||
const CACHE_KEY = 'gpustack:github-stars';
|
||||
const CACHE_TTL = 24 * 60 * 60 * 1000;
|
||||
const FETCH_TIMEOUT = 4000;
|
||||
|
||||
const StarLink = styled.a`
|
||||
display: inline-flex;
|
||||
align-items: stretch;
|
||||
height: 24px;
|
||||
border-radius: var(--ant-border-radius);
|
||||
border: 1px solid var(--ant-color-border-secondary);
|
||||
background-color: var(--ant-color-bg-container);
|
||||
color: var(--ant-color-text-secondary);
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
transition:
|
||||
border-color 0.2s,
|
||||
color 0.2s;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--ant-color-border);
|
||||
color: var(--ant-color-text);
|
||||
}
|
||||
|
||||
.seg {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.seg + .seg {
|
||||
border-left: 1px solid var(--ant-color-border-secondary);
|
||||
background-color: var(--ant-color-fill-quaternary);
|
||||
}
|
||||
|
||||
.anticon {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.count {
|
||||
font-weight: 500;
|
||||
font-variant-numeric: tabular-nums;
|
||||
min-width: 1.5em;
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
|
||||
const formatCount = (n: number): string => {
|
||||
if (n >= 1000) {
|
||||
const k = n / 1000;
|
||||
return k >= 10 ? `${Math.round(k)}k` : `${k.toFixed(1)}k`;
|
||||
}
|
||||
return String(n);
|
||||
};
|
||||
|
||||
type CacheEntry = { value: number; time: number };
|
||||
|
||||
const readCache = (): CacheEntry | null => {
|
||||
try {
|
||||
const raw = localStorage.getItem(CACHE_KEY);
|
||||
if (!raw) return null;
|
||||
const parsed = JSON.parse(raw);
|
||||
if (typeof parsed?.value !== 'number' || typeof parsed?.time !== 'number') {
|
||||
return null;
|
||||
}
|
||||
return parsed;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const writeCache = (value: number) => {
|
||||
try {
|
||||
localStorage.setItem(
|
||||
CACHE_KEY,
|
||||
JSON.stringify({ value, time: Date.now() })
|
||||
);
|
||||
} catch {
|
||||
// ignore quota errors
|
||||
}
|
||||
};
|
||||
|
||||
const GithubStar = () => {
|
||||
const intl = useIntl();
|
||||
const [count, setCount] = useState<number | null>(
|
||||
() => readCache()?.value ?? null
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const cached = readCache();
|
||||
const fresh = cached && Date.now() - cached.time < CACHE_TTL;
|
||||
if (fresh) return;
|
||||
|
||||
const controller = new AbortController();
|
||||
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
|
||||
|
||||
fetch(`https://api.github.com/repos/${REPO}`, { signal: controller.signal })
|
||||
.then((r) => (r.ok ? r.json() : null))
|
||||
.then((data) => {
|
||||
if (!data || typeof data.stargazers_count !== 'number') return;
|
||||
setCount(data.stargazers_count);
|
||||
writeCache(data.stargazers_count);
|
||||
})
|
||||
.catch(() => {
|
||||
// offline, blocked, rate-limited — stay hidden if no cache
|
||||
})
|
||||
.finally(() => clearTimeout(timer));
|
||||
|
||||
return () => {
|
||||
clearTimeout(timer);
|
||||
controller.abort();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Tooltip title={intl.formatMessage({ id: 'common.github.star.tooltip' })}>
|
||||
<StarLink href={externalLinks.github} target="_blank" rel="noreferrer">
|
||||
<span className="seg">
|
||||
<GithubFilled />
|
||||
</span>
|
||||
<span className="seg">
|
||||
<span className="count">
|
||||
{count != null ? formatCount(count) : 'Star'}
|
||||
</span>
|
||||
</span>
|
||||
</StarLink>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default GithubStar;
|
||||
+47
-2
@@ -12,6 +12,7 @@ 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,
|
||||
@@ -41,7 +42,7 @@ import {
|
||||
import { Button, ConfigProvider, Modal, theme } from 'antd';
|
||||
import { useAtom } from 'jotai';
|
||||
import 'overlayscrollbars/overlayscrollbars.css';
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import { PageContainerInner } from '../pages/_components/page-box';
|
||||
import Exception from './Exception';
|
||||
import './Layout.css';
|
||||
@@ -155,6 +156,50 @@ 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: '',
|
||||
@@ -327,7 +372,7 @@ export default (props: any) => {
|
||||
config={{
|
||||
apiBaseUrl: GPUSTACK_API_BASE_URL,
|
||||
theme: userSettings.theme,
|
||||
iconUrl: '//at.alicdn.com/t/c/font_4613488_rwchdketjg8.js',
|
||||
iconUrl: '',
|
||||
isDarkTheme: userSettings.isDarkTheme,
|
||||
defaultColorPrimary: COLOR_PRIMARY
|
||||
}}
|
||||
|
||||
+21
-11
@@ -1,9 +1,11 @@
|
||||
import { collapsedMenuGroupsAtom } from '@/atoms/settings';
|
||||
import { CaretDownOutlined } from '@ant-design/icons';
|
||||
import { IconFont, OverlayScroller } from '@gpustack/core-ui';
|
||||
import { Link, useLocation } from '@umijs/max';
|
||||
import { Tooltip } from 'antd';
|
||||
import { createStyles, type FullToken } from 'antd-style';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { useAtom } from 'jotai';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
interface MenuItem {
|
||||
icon?: string;
|
||||
@@ -144,8 +146,19 @@ const SiderMenu: React.FC<SiderMenuProps> = (props) => {
|
||||
const { menuData, collapsed } = props;
|
||||
const { styles, cx } = useStyles();
|
||||
const location = useLocation();
|
||||
const [collapseKeys, setCollapseKeys] = useState<Set<string>>(new Set());
|
||||
console.log('SiderMenu', location.pathname);
|
||||
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 dividerStyles = useMemo(() => {
|
||||
if (collapsed) {
|
||||
@@ -164,14 +177,11 @@ const SiderMenu: React.FC<SiderMenuProps> = (props) => {
|
||||
const handleToggleGroup = (e: any, menuGroup: any) => {
|
||||
e.stopPropagation();
|
||||
|
||||
console.log('handleToggleGroup', menuGroup.key);
|
||||
|
||||
if (collapseKeys.has(menuGroup.key)) {
|
||||
collapseKeys.delete(menuGroup.key);
|
||||
} else {
|
||||
collapseKeys.add(menuGroup.key);
|
||||
}
|
||||
setCollapseKeys(new Set(collapseKeys));
|
||||
setCollapsedGroups(
|
||||
collapsedGroups.includes(menuGroup.key)
|
||||
? collapsedGroups.filter((key) => key !== menuGroup.key)
|
||||
: [...collapsedGroups, menuGroup.key]
|
||||
);
|
||||
};
|
||||
|
||||
const menuItemRender = (menuItem: MenuItem, key: string) => {
|
||||
|
||||
@@ -25,7 +25,7 @@ export default {
|
||||
'ai.provider.ollama': 'Ollama',
|
||||
'ai.provider.openai': 'OpenAI',
|
||||
'ai.provider.openrouter': 'OpenRouter',
|
||||
'ai.provider.qwen': 'Qwen',
|
||||
'ai.provider.qwen': 'Alibaba Cloud Model Studio',
|
||||
'ai.provider.spark': 'Spark',
|
||||
'ai.provider.stepfun': 'StepFun',
|
||||
'ai.provider.together-ai': 'TogetherAI',
|
||||
|
||||
@@ -23,11 +23,12 @@ 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.parameterFormat': 'Parameter Format',
|
||||
'backend.form.parameterFormat.default': 'Backend Default',
|
||||
'backend.form.parameterFormat.space': 'Space (--key value)',
|
||||
'backend.form.parameterFormat.equal': 'Equal (--key=value)',
|
||||
'backend.form.commonParameters': 'Common Parameters',
|
||||
'backend.form.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',
|
||||
|
||||
@@ -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': 'Filter by GPU',
|
||||
'benchmark.table.filter.bymodel': 'Filter by Model',
|
||||
'benchmark.table.filter.bygpu': 'Search by GPU',
|
||||
'benchmark.table.filter.bymodel': 'Search 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',
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export default {
|
||||
'billing.upsell.title': 'Billing is an Enterprise feature',
|
||||
'billing.upsell.subtitle':
|
||||
'Track spend, generate invoices, and enforce budgets across teams. Upgrade to GPUStack 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'
|
||||
};
|
||||
@@ -70,8 +70,13 @@ export default {
|
||||
'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',
|
||||
@@ -112,8 +117,6 @@ export default {
|
||||
'clusters.create.workerConfig': 'Worker Configuration',
|
||||
'clusters.edit.k8sOptions.changed.tip':
|
||||
'You have changed the Kubernetes options. Re-run the registration command on the target cluster for the changes to take effect.',
|
||||
'clusters.edit.workerConfig.tip':
|
||||
'Changes to the worker configuration take effect only after restarting the affected workers.',
|
||||
'clusters.addworker.containerName': 'Worker Container Name',
|
||||
'clusters.addworker.containerName.tips':
|
||||
'Specify a name for the worker container.',
|
||||
@@ -141,7 +144,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 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.',
|
||||
'The built-in inference backends in GPUStack require <span class="bold-text">CUDA 12.8+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">570</span> or newer.',
|
||||
'clusters.volume.title': 'Volume Mounts',
|
||||
'clusters.volume.name': 'Volume Name',
|
||||
'clusters.volume.mountPath': 'Container Path',
|
||||
|
||||
@@ -169,6 +169,7 @@ 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',
|
||||
@@ -197,6 +198,8 @@ 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':
|
||||
@@ -250,6 +253,11 @@ 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!',
|
||||
@@ -260,6 +268,10 @@ 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.',
|
||||
|
||||
@@ -81,6 +81,8 @@ export default {
|
||||
'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',
|
||||
@@ -120,6 +122,8 @@ export default {
|
||||
'gpuservice.instance.sliced': 'Sliced',
|
||||
'gpuservice.instance.memory': 'VRAM',
|
||||
'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',
|
||||
@@ -172,5 +176,9 @@ export default {
|
||||
'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.form.storage.select': 'Select Storage',
|
||||
'gpuservice.creator': 'Creator',
|
||||
'gpuservice.owner.global': 'Global',
|
||||
'gpuservice.template.group.yours': 'Your Templates',
|
||||
'gpuservice.template.group.global': 'Global Templates'
|
||||
};
|
||||
|
||||
@@ -38,6 +38,7 @@ export default {
|
||||
'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',
|
||||
|
||||
@@ -90,6 +90,7 @@ export default {
|
||||
// --- 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',
|
||||
|
||||
@@ -28,6 +28,8 @@ 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.',
|
||||
|
||||
@@ -25,7 +25,7 @@ export default {
|
||||
'ai.provider.ollama': 'Ollama',
|
||||
'ai.provider.openai': 'OpenAI',
|
||||
'ai.provider.openrouter': 'OpenRouter',
|
||||
'ai.provider.qwen': 'Qwen',
|
||||
'ai.provider.qwen': 'Alibaba Cloud Model Studio',
|
||||
'ai.provider.spark': 'Spark',
|
||||
'ai.provider.stepfun': 'StepFun',
|
||||
'ai.provider.together-ai': 'TogetherAI',
|
||||
|
||||
@@ -23,11 +23,12 @@ 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.parameterFormat': 'パラメータ形式',
|
||||
'backend.form.parameterFormat.default': 'バックエンドのデフォルト',
|
||||
'backend.form.parameterFormat.space': 'スペース区切り (--key value)',
|
||||
'backend.form.parameterFormat.equal': 'イコール連結 (--key=value)',
|
||||
'backend.form.commonParameters': 'よく使うパラメータ',
|
||||
'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',
|
||||
|
||||
@@ -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': 'Filter by GPU',
|
||||
'benchmark.table.filter.bymodel': 'Filter by Model',
|
||||
'benchmark.table.filter.bygpu': 'GPU 検索',
|
||||
'benchmark.table.filter.bymodel': 'モデル検索',
|
||||
'benchmark.table.filter.bydataset': 'Filter by Dataset',
|
||||
'benchmark.table.filter.byProfile': 'Filter by Profile',
|
||||
'benchmark.table.avg': 'Avg',
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export default {
|
||||
'billing.upsell.title': 'Billing is an Enterprise feature',
|
||||
'billing.upsell.subtitle':
|
||||
'Track spend, generate invoices, and enforce budgets across teams. Upgrade to GPUStack 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'
|
||||
};
|
||||
@@ -70,8 +70,13 @@ export default {
|
||||
'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 を自動検出',
|
||||
@@ -112,8 +117,6 @@ export default {
|
||||
'clusters.create.workerConfig': 'Worker Configuration',
|
||||
'clusters.edit.k8sOptions.changed.tip':
|
||||
'Kubernetes オプションを変更しました。変更を有効にするには、対象クラスターで登録コマンドを再実行してください。',
|
||||
'clusters.edit.workerConfig.tip':
|
||||
'ワーカー設定の変更は、対象のワーカーを再起動した後に有効になります。',
|
||||
'clusters.addworker.containerName': 'Worker Container Name',
|
||||
'clusters.addworker.containerName.tips':
|
||||
'Specify a name for the worker container.',
|
||||
@@ -141,7 +144,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 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.',
|
||||
'The built-in inference backends in GPUStack require <span class="bold-text">CUDA 12.8+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">570</span> or newer.',
|
||||
'clusters.volume.title': 'Volume Mounts',
|
||||
'clusters.volume.name': 'Volume Name',
|
||||
'clusters.volume.mountPath': 'Container Path',
|
||||
|
||||
@@ -170,6 +170,7 @@ 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': 'ロールバックコメント',
|
||||
@@ -198,6 +199,7 @@ 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> を削除してもよろしいですか?',
|
||||
@@ -250,6 +252,11 @@ 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!',
|
||||
@@ -260,6 +267,10 @@ 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.',
|
||||
|
||||
@@ -80,6 +80,8 @@ export default {
|
||||
'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 アクセスを有効化',
|
||||
@@ -119,6 +121,8 @@ export default {
|
||||
'gpuservice.instance.sliced': '分割',
|
||||
'gpuservice.instance.memory': 'VRAM',
|
||||
'gpuservice.instance.ram': 'RAM',
|
||||
'gpuservice.instance.os': 'OS',
|
||||
'gpuservice.instance.arch': 'アーキテクチャ',
|
||||
'gpuservice.instance.disk': 'ディスク',
|
||||
'gpuservice.table.count': '数量',
|
||||
'gpuservice.instance.disk.system': 'システムディスク',
|
||||
@@ -172,5 +176,9 @@ export default {
|
||||
'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.form.storage.select': 'ストレージを選択',
|
||||
'gpuservice.creator': '作成者',
|
||||
'gpuservice.owner.global': 'グローバル',
|
||||
'gpuservice.template.group.yours': '自分のテンプレート',
|
||||
'gpuservice.template.group.global': 'グローバルテンプレート'
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ export default {
|
||||
'menu.usage.usage': '使用状況',
|
||||
'menu.billingAndUsage': '使用状況と請求',
|
||||
'menu.billingAndUsage.usage': '使用状況',
|
||||
'menu.billingAndUsage.billing': '請求',
|
||||
'menu.404': '404',
|
||||
'menu.settings': 'Settings',
|
||||
'menu.resources.workers': 'Workers',
|
||||
|
||||
@@ -90,6 +90,7 @@ export default {
|
||||
// --- Resource events ---
|
||||
'usage.events.resourceType': 'リソースタイプ',
|
||||
'usage.events.eventType': 'イベントタイプ',
|
||||
'usage.events.resourceName': '名前で絞り込み',
|
||||
'usage.events.col.time': '時刻',
|
||||
'usage.events.col.resource': 'リソース',
|
||||
'usage.events.col.event': 'イベント',
|
||||
|
||||
@@ -29,6 +29,8 @@ 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つのパスワードが一致しません。',
|
||||
|
||||
@@ -25,7 +25,7 @@ export default {
|
||||
'ai.provider.ollama': 'Ollama',
|
||||
'ai.provider.openai': 'OpenAI',
|
||||
'ai.provider.openrouter': 'OpenRouter',
|
||||
'ai.provider.qwen': 'Qwen',
|
||||
'ai.provider.qwen': 'Alibaba Cloud Model Studio',
|
||||
'ai.provider.spark': 'Spark',
|
||||
'ai.provider.stepfun': 'StepFun',
|
||||
'ai.provider.together-ai': 'TogetherAI',
|
||||
|
||||
@@ -23,11 +23,12 @@ export default {
|
||||
'backend.form.defaultExecuteCommand': 'Команда выполнения по умолчанию',
|
||||
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' и '{{'model_name'}}' заполняются реальными значениями во время запуска`,
|
||||
'backend.form.defaultBackendParameters': 'Параметры бэкенда по умолчанию',
|
||||
'backend.form.parameterFormat': 'Формат параметров',
|
||||
'backend.form.parameterFormat.default': 'По умолчанию бэкенда',
|
||||
'backend.form.parameterFormat.space': 'Пробел (--key value)',
|
||||
'backend.form.parameterFormat.equal': 'Знак равенства (--key=value)',
|
||||
'backend.form.commonParameters': 'Часто используемые параметры',
|
||||
'backend.form.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': 'Конфигурация версий',
|
||||
|
||||
@@ -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': 'Filter by GPU',
|
||||
'benchmark.table.filter.bymodel': 'Filter by Model',
|
||||
'benchmark.table.filter.bygpu': 'Поиск GPU',
|
||||
'benchmark.table.filter.bymodel': 'Поиск модели',
|
||||
'benchmark.table.filter.bydataset': 'Filter by Dataset',
|
||||
'benchmark.table.filter.byProfile': 'Filter by Profile',
|
||||
'benchmark.table.avg': 'Avg',
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export default {
|
||||
'billing.upsell.title': 'Billing is an Enterprise feature',
|
||||
'billing.upsell.subtitle':
|
||||
'Track spend, generate invoices, and enforce budgets across teams. Upgrade to GPUStack 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'
|
||||
};
|
||||
@@ -70,8 +70,13 @@ export default {
|
||||
'Для <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 воркера',
|
||||
@@ -112,8 +117,6 @@ export default {
|
||||
'clusters.create.workerConfig': 'Конфигурация воркера',
|
||||
'clusters.edit.k8sOptions.changed.tip':
|
||||
'Вы изменили параметры Kubernetes. Чтобы изменения вступили в силу, повторно выполните команду регистрации в целевом кластере.',
|
||||
'clusters.edit.workerConfig.tip':
|
||||
'Изменения конфигурации воркера вступают в силу только после перезапуска соответствующих воркеров.',
|
||||
'clusters.addworker.containerName': 'Имя контейнера воркера',
|
||||
'clusters.addworker.containerName.tips':
|
||||
'Укажите имя для контейнера воркера.',
|
||||
@@ -142,7 +145,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 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.',
|
||||
'The built-in inference backends in GPUStack require <span class="bold-text">CUDA 12.8+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">570</span> or newer.',
|
||||
'clusters.volume.title': 'Volume Mounts',
|
||||
'clusters.volume.name': 'Volume Name',
|
||||
'clusters.volume.mountPath': 'Container Path',
|
||||
|
||||
@@ -167,6 +167,7 @@ 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': 'Комментарий к откату',
|
||||
@@ -196,6 +197,7 @@ 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>?',
|
||||
@@ -249,6 +251,11 @@ 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':
|
||||
'Страница может нуждаться в обновлении. Попробуйте обновить её!',
|
||||
@@ -259,6 +266,10 @@ 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':
|
||||
'Единый вход не настроен в этой системе. Пожалуйста, обратитесь к администратору.',
|
||||
|
||||
@@ -84,6 +84,8 @@ export default {
|
||||
'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-доступ',
|
||||
@@ -118,6 +120,8 @@ export default {
|
||||
'gpuservice.instance.sliced': 'Разделено',
|
||||
'gpuservice.instance.memory': 'VRAM',
|
||||
'gpuservice.instance.ram': 'RAM',
|
||||
'gpuservice.instance.os': 'ОС',
|
||||
'gpuservice.instance.arch': 'Архитектура',
|
||||
'gpuservice.instance.disk': 'Диск',
|
||||
'gpuservice.table.count': 'Количество',
|
||||
'gpuservice.instance.disk.system': 'Системный диск',
|
||||
@@ -169,5 +173,9 @@ export default {
|
||||
'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.form.storage.select': 'Выберите хранилище',
|
||||
'gpuservice.creator': 'Создатель',
|
||||
'gpuservice.owner.global': 'Глобальный',
|
||||
'gpuservice.template.group.yours': 'Ваши шаблоны',
|
||||
'gpuservice.template.group.global': 'Глобальные шаблоны'
|
||||
};
|
||||
|
||||
@@ -29,6 +29,7 @@ export default {
|
||||
'menu.usage.usage': 'Использование',
|
||||
'menu.billingAndUsage': 'Использование и биллинг',
|
||||
'menu.billingAndUsage.usage': 'Использование',
|
||||
'menu.billingAndUsage.billing': 'Биллинг',
|
||||
'menu.404': 'Ошибка 404',
|
||||
'menu.resources.workers': 'Воркеры',
|
||||
'menu.resources.gpus': 'GPUs',
|
||||
|
||||
@@ -90,6 +90,7 @@ export default {
|
||||
// --- Resource events ---
|
||||
'usage.events.resourceType': 'Тип ресурса',
|
||||
'usage.events.eventType': 'Тип события',
|
||||
'usage.events.resourceName': 'Фильтр по имени',
|
||||
'usage.events.col.time': 'Время',
|
||||
'usage.events.col.resource': 'Ресурс',
|
||||
'usage.events.col.event': 'Событие',
|
||||
|
||||
@@ -29,6 +29,8 @@ 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': 'Пароли не совпадают',
|
||||
|
||||
@@ -25,7 +25,7 @@ export default {
|
||||
'ai.provider.ollama': 'Ollama',
|
||||
'ai.provider.openai': 'OpenAI',
|
||||
'ai.provider.openrouter': 'OpenRouter',
|
||||
'ai.provider.qwen': 'Qwen',
|
||||
'ai.provider.qwen': 'Alibaba Cloud Model Studio',
|
||||
'ai.provider.spark': 'Spark',
|
||||
'ai.provider.stepfun': 'StepFun',
|
||||
'ai.provider.together-ai': 'TogetherAI',
|
||||
|
||||
@@ -23,11 +23,12 @@ export default {
|
||||
'backend.form.defaultExecuteCommand': 'Varsayılan Çalıştırma Komutu',
|
||||
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' ve '{{'model_name'}}' dağıtım sırasında gerçek değerlerle değiştirilecek yer tutuculardır.`,
|
||||
'backend.form.defaultBackendParameters': 'Varsayılan Altyapı Parametreleri',
|
||||
'backend.form.parameterFormat': 'Parametre Biçimi',
|
||||
'backend.form.parameterFormat.default': 'Varsayılan',
|
||||
'backend.form.parameterFormat.space': 'Boşluk (--key value)',
|
||||
'backend.form.parameterFormat.equal': 'Eşittir (--key=value)',
|
||||
'backend.form.commonParameters': 'Yaygın Parametreler',
|
||||
'backend.form.flagFormat': 'Bayrak Biçimi',
|
||||
'backend.form.flagFormat.tips':
|
||||
'Bir seçenek ile değerinin birleştirilme biçimi. Boş bırakılırsa her parametre girildiği biçimde korunur, biçim birleştirilmez.',
|
||||
'backend.form.flagFormat.space': 'Boşlukla Ayır (--key value)',
|
||||
'backend.form.flagFormat.equal': 'Eşittir İşareti (--key=value)',
|
||||
'backend.form.commonParameters': 'Ortak Altyapı Parametreleri',
|
||||
'backend.form.commonParameters.tips':
|
||||
'Dağıtım sırasında altyapı parametreleri girişinde öneri olarak gösterilir.',
|
||||
'backend.form.versionConfig': 'Sürüm Yapılandırması',
|
||||
|
||||
@@ -37,8 +37,8 @@ export default {
|
||||
'Uzun bağlam işleme için stres testi. KV önbellek davranışını, bellek kullanımını ve altyapı kararlılığını değerlendirir.',
|
||||
'benchmark.form.profile.heavy.tips':
|
||||
'Çözümleme ağırlıklı üretim kıyaslaması. Sürekli çözümleme hızını ve çıkış token verimini ölçer.',
|
||||
'benchmark.table.filter.bygpu': "GPU'ya göre filtrele",
|
||||
'benchmark.table.filter.bymodel': 'Modele göre filtrele',
|
||||
'benchmark.table.filter.bygpu': 'GPU ara',
|
||||
'benchmark.table.filter.bymodel': 'Model ara',
|
||||
'benchmark.table.filter.bydataset': 'Veri Kümesine göre filtrele',
|
||||
'benchmark.table.filter.byProfile': 'Profile göre filtrele',
|
||||
'benchmark.table.avg': 'Ort.',
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
export default {
|
||||
'billing.upsell.title': 'Billing is an Enterprise feature',
|
||||
'billing.upsell.subtitle':
|
||||
'Track spend, generate invoices, and enforce budgets across teams. Upgrade to GPUStack 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'
|
||||
};
|
||||
@@ -70,8 +70,13 @@ export default {
|
||||
'<span class="bold-text">Docker dışı</span> kümeler için lütfen Kümeler sayfasından küme kaydı oluşturun veya işçi havuzlarını yönetin.',
|
||||
'clusters.addworker.selectGPU': 'GPU Üreticisi Seç',
|
||||
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
|
||||
'clusters.addworker.selectGPU.subtitle':
|
||||
'Birden fazla GPU Üreticisi seçebilir veya yalnızca CPU kümeleri için hiçbirini seçmeyebilirsiniz',
|
||||
'clusters.addworker.checkEnv': 'Ortamı Kontrol Et',
|
||||
'clusters.addworker.checkEnv.cpuOnlyTips':
|
||||
'Kubernetes kümesinde en az bir hazır düğüm olduğunu doğrulamak için aşağıdaki komutu kullanın. Yalnızca CPU kümelerini kaydediyorsunuz.',
|
||||
'clusters.addworker.specifyArgs': 'Argümanları Belirle',
|
||||
'clusters.addworker.dtkVersion': 'DTK Sürümü',
|
||||
'clusters.addworker.runCommand': 'Komutu Çalıştır',
|
||||
'clusters.addworker.specifyWorkerIP': "İşçi Düğüm IP'si",
|
||||
'clusters.addworker.detectWorkerIP': "İşçi Düğüm IP'sini Otomatik Algıla",
|
||||
@@ -112,8 +117,6 @@ export default {
|
||||
'clusters.create.workerConfig': 'İşçi Düğüm Yapılandırması',
|
||||
'clusters.edit.k8sOptions.changed.tip':
|
||||
'Kubernetes seçeneklerini değiştirdiniz. Değişikliklerin etkili olması için kayıt komutunu hedef kümede yeniden çalıştırın.',
|
||||
'clusters.edit.workerConfig.tip':
|
||||
'İşçi düğüm yapılandırmasındaki değişiklikler yalnızca ilgili işçi düğümleri yeniden başlatıldıktan sonra etkili olur.',
|
||||
'clusters.addworker.containerName': 'İşçi Düğüm Konteyner Adı',
|
||||
'clusters.addworker.containerName.tips':
|
||||
'İşçi düğüm konteyneri için bir ad belirtin.',
|
||||
@@ -142,7 +145,7 @@ export default {
|
||||
'clusters.addworker.theadNotes-02':
|
||||
'T-Head PPU, cihaz enjeksiyonu için Container Device Interface (CDI) kullanır ve CDI oluşturma için <span class="bold-text">/var/run/cdi</span> dizininin kullanılabilir olmasını gerektirir.',
|
||||
'clusters.addworker.nvidiaNotes':
|
||||
'GPUStack v2.1\'deki yerleşik çıkarım altyapıları <span class="bold-text">CUDA 12.6+</span> gerektirir. Lütfen NVIDIA sürücü sürümünüzün <span class="bold-text">560</span> veya daha yeni olduğundan emin olun.',
|
||||
'GPUStack\'teki yerleşik çıkarım altyapıları <span class="bold-text">CUDA 12.8+</span> gerektirir. Lütfen NVIDIA sürücü sürümünüzün <span class="bold-text">570</span> veya daha yeni olduğundan emin olun.',
|
||||
'clusters.volume.title': 'Volume Mounts',
|
||||
'clusters.volume.name': 'Volume Name',
|
||||
'clusters.volume.mountPath': 'Container Path',
|
||||
|
||||
@@ -167,6 +167,7 @@ export default {
|
||||
'common.time.hour': 'saat',
|
||||
'common.time.minute': 'dakika',
|
||||
'common.issue.report': 'Sorun bildir',
|
||||
'common.github.star.tooltip': "GitHub'da bize yıldız verin",
|
||||
'common.social.discord': "Discord'umuza Katılın",
|
||||
'common.table.mark': 'Yorum',
|
||||
'common.table.rollback.mark': 'Geri Alma Yorumu',
|
||||
@@ -198,6 +199,7 @@ export default {
|
||||
'common.table.user': 'Kullanıcı',
|
||||
'common.settings.instructions': 'Talimatlar',
|
||||
'common.settings.language': 'Dil',
|
||||
'common.settings.language.tips': 'Arayüzün görüntüleme dilini ayarlayın.',
|
||||
'common.delete.confirm':
|
||||
'Seçili {type} öğesini silmek istediğinizden emin misiniz?',
|
||||
'common.delete.single.confirm':
|
||||
@@ -253,6 +255,11 @@ export default {
|
||||
'common.appearance.tips': 'Varsayılan olarak sistem tercihini takip eder.',
|
||||
'common.button.forgotpassword': 'Şifrenizi mi unuttunuz?',
|
||||
'common.appearance.theme': 'Tema',
|
||||
'common.appearance.description':
|
||||
'Arayüzün cihazınızdaki görünümünü özelleştirin.',
|
||||
'common.security': 'Güvenlik',
|
||||
'common.security.description':
|
||||
'Hesabınıza giriş yapmak için kullanılan parolayı yönetin.',
|
||||
'common.page.wentwrong': 'Bir şeyler ters gitti.',
|
||||
'common.page.refresh.tips':
|
||||
'Sayfanın güncellenmesi gerekebilir. Yenilemeyi deneyin!',
|
||||
@@ -263,6 +270,10 @@ export default {
|
||||
'common.login.auth': 'Kimlik doğrulanıyor...',
|
||||
'common.login.auth.failed': 'Kimlik doğrulama başarısız',
|
||||
'common.login.password': 'Şifre ile giriş yap',
|
||||
'common.login.username.holder': 'Lütfen kullanıcı adını girin',
|
||||
'common.login.password.holder': 'Lütfen şifreyi girin',
|
||||
'common.login.newpassword.holder': 'Lütfen yeni şifreyi girin',
|
||||
'common.login.confirm.holder': 'Lütfen şifreyi tekrar girin',
|
||||
'common.external.login': '{type} ile giriş yap',
|
||||
'common.sso.noConfig':
|
||||
'Bu sistemde çoklu oturum açma etkinleştirilmemiş. Lütfen yöneticinize başvurun.',
|
||||
|
||||
@@ -80,6 +80,8 @@ export default {
|
||||
'gpuservice.publicKey': 'SSH Açık Anahtarı',
|
||||
'gpuservice.publicKey.add': 'SSH Açık Anahtarı Ekle',
|
||||
'gpuservice.publicKey.edit': 'SSH Açık Anahtarını Düzenle',
|
||||
'gpuservice.publicKey.delete.tips':
|
||||
'Bir SSH Açık Anahtarını silmek, mevcut bağlı Örneklerin erişimini iptal etmez. Erişimi kaldırmak için ilgili Örnekleri ayrı ayrı düzenleyin.',
|
||||
'gpuservice.publicKey.filter.name': 'Ada göre ara',
|
||||
'gpuservice.publicKey.label': 'SSH Açık Anahtarı',
|
||||
'gpuservice.instance.ssh.enable': 'SSH Erişimini Etkinleştir',
|
||||
@@ -114,6 +116,8 @@ export default {
|
||||
'gpuservice.instance.sliced': 'Bölünmüş',
|
||||
'gpuservice.instance.memory': 'VRAM',
|
||||
'gpuservice.instance.ram': 'RAM',
|
||||
'gpuservice.instance.os': 'OS',
|
||||
'gpuservice.instance.arch': 'Mimari',
|
||||
'gpuservice.instance.disk': 'Disk',
|
||||
'gpuservice.table.count': 'Sayı',
|
||||
'gpuservice.instance.disk.system': 'Sistem Diski',
|
||||
@@ -167,5 +171,9 @@ export default {
|
||||
'Data is cleared when the instance stops.',
|
||||
'gpuservice.storage.persistentVolume.tips':
|
||||
'Data persists across instance restarts. Persistent volumes remain intact after instance termination and can be shared by multiple instances.',
|
||||
'gpuservice.form.storage.select': 'Depolamayı Seç'
|
||||
'gpuservice.form.storage.select': 'Depolamayı Seç',
|
||||
'gpuservice.creator': 'Oluşturan',
|
||||
'gpuservice.owner.global': 'Genel',
|
||||
'gpuservice.template.group.yours': 'Şablonlarınız',
|
||||
'gpuservice.template.group.global': 'Genel Şablonlar'
|
||||
};
|
||||
|
||||
@@ -36,6 +36,7 @@ export default {
|
||||
'menu.usage.usage': 'Kullanım',
|
||||
'menu.billingAndUsage': 'Kullanım ve Faturalandırma',
|
||||
'menu.billingAndUsage.usage': 'Kullanım',
|
||||
'menu.billingAndUsage.billing': 'Faturalandırma',
|
||||
'menu.404': '404',
|
||||
'menu.resources.clusters': 'Kümeler',
|
||||
'menu.resources.credentials': 'Bulut Kimlik Bilgileri',
|
||||
|
||||
@@ -90,6 +90,7 @@ export default {
|
||||
// --- Resource events ---
|
||||
'usage.events.resourceType': 'Kaynak türü',
|
||||
'usage.events.eventType': 'Olay türü',
|
||||
'usage.events.resourceName': 'Ada göre filtrele',
|
||||
'usage.events.col.time': 'Zaman',
|
||||
'usage.events.col.resource': 'Kaynak',
|
||||
'usage.events.col.event': 'Olay',
|
||||
|
||||
@@ -29,6 +29,8 @@ export default {
|
||||
'users.password.modify.title': 'Şifreyi Değiştir',
|
||||
'users.password.modify.description':
|
||||
'Hesabınızın güvenliği için lütfen başlangıç şifrenizi değiştirin.',
|
||||
'users.password.modify.tips':
|
||||
'Parolanızı düzenli olarak güncellemek hesabınızın güvenliğini korumaya yardımcı olur.',
|
||||
'users.password.confirm': 'Yeni Şifreyi Onayla',
|
||||
'users.password.confirm.empty': 'Lütfen yeni şifreyi tekrar girin.',
|
||||
'users.password.confirm.error': 'Girilen iki şifre eşleşmiyor.',
|
||||
|
||||
@@ -25,7 +25,7 @@ export default {
|
||||
'ai.provider.ollama': 'Ollama',
|
||||
'ai.provider.openai': 'OpenAI',
|
||||
'ai.provider.openrouter': 'OpenRouter',
|
||||
'ai.provider.qwen': '通义千问',
|
||||
'ai.provider.qwen': '阿里云百炼',
|
||||
'ai.provider.spark': '讯飞星火',
|
||||
'ai.provider.stepfun': '阶跃星辰',
|
||||
'ai.provider.together-ai': 'Together AI',
|
||||
|
||||
@@ -23,11 +23,12 @@ export default {
|
||||
'backend.form.defaultExecuteCommand': '默认执行命令',
|
||||
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}'、'{{'port'}}'、'{{'worker_ip'}}' 和 '{{'model_name'}}' 都是占位符,在部署过程中会被替换为实际的值。`,
|
||||
'backend.form.defaultBackendParameters': '默认后端参数',
|
||||
'backend.form.parameterFormat': '参数输出格式',
|
||||
'backend.form.parameterFormat.default': '后端默认',
|
||||
'backend.form.parameterFormat.space': '空格分隔 (--key value)',
|
||||
'backend.form.parameterFormat.equal': '等号连接 (--key=value)',
|
||||
'backend.form.commonParameters': '常用参数',
|
||||
'backend.form.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': '添加参数',
|
||||
|
||||
@@ -37,8 +37,8 @@ export default {
|
||||
'长上下文压力测试。评估 KV Cache 行为、内存占用及后端稳定性。',
|
||||
'benchmark.form.profile.heavy.tips':
|
||||
'以解码为主的生成测试。衡量持续解码速度与输出 Token 吞吐能力。',
|
||||
'benchmark.table.filter.bygpu': '按 GPU 过滤',
|
||||
'benchmark.table.filter.bymodel': '按模型过滤',
|
||||
'benchmark.table.filter.bygpu': 'GPU 查询',
|
||||
'benchmark.table.filter.bymodel': '模型查询',
|
||||
'benchmark.table.filter.bydataset': '按数据集过滤',
|
||||
'benchmark.table.filter.byProfile': '按模式过滤',
|
||||
'benchmark.table.avg': '均值',
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
export default {
|
||||
'billing.upsell.title': '计费是企业版功能',
|
||||
'billing.upsell.subtitle':
|
||||
'在团队间跟踪花费、生成账单并执行预算。升级到 GPUStack 企业版即可管理计费。',
|
||||
'billing.upsell.featuresTitle': '企业版包含的能力',
|
||||
'billing.upsell.feature.usage': '按组织、用户与模型查看成本明细',
|
||||
'billing.upsell.feature.invoices': '生成账单并导出计费报表',
|
||||
'billing.upsell.feature.budgets': '设置预算与花费上限并触发告警',
|
||||
'billing.upsell.feature.chargeback': '将用量归属并分摊到团队与项目',
|
||||
'billing.upsell.cta': '了解企业版'
|
||||
};
|
||||
@@ -68,8 +68,13 @@ export default {
|
||||
'<span class="bold-text">非 Docker</span> 集群请前往集群页面注册集群或管理节点池。',
|
||||
'clusters.addworker.selectGPU': '选择 GPU 厂商',
|
||||
'clusters.addworker.selectGPU.multiTag': '可多选',
|
||||
'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',
|
||||
@@ -110,8 +115,6 @@ export default {
|
||||
'clusters.create.workerConfig': '节点配置',
|
||||
'clusters.edit.k8sOptions.changed.tip':
|
||||
'您已修改 Kubernetes 选项,需要在目标集群上重新运行注册命令才会生效。',
|
||||
'clusters.edit.workerConfig.tip':
|
||||
'修改节点配置后,需要重启对应节点才会生效。',
|
||||
'clusters.addworker.containerName': '节点容器名称',
|
||||
'clusters.addworker.containerName.tips': '为节点容器指定一个名称。',
|
||||
'clusters.addworker.dataVolume': 'GPUStack 数据卷',
|
||||
@@ -135,7 +138,7 @@ export default {
|
||||
'clusters.addworker.theadNotes-02':
|
||||
'平头哥(T-Head)PPU 使用容器设备接口(CDI)进行设备注入,因此需要确保 <span class="bold-text">/var/run/cdi</span> 目录可用以生成 CDI。',
|
||||
'clusters.addworker.nvidiaNotes':
|
||||
'GPUStack v2.1 内置推理后端依赖 <span class="bold-text">CUDA 12.6</span> 及以上版本,请确保 NVIDIA 驱动版本为 <span class="bold-text">560</span> 或以上。',
|
||||
'GPUStack 内置推理后端依赖 <span class="bold-text">CUDA 12.8</span> 及以上版本,请确保 NVIDIA 驱动版本为 <span class="bold-text">570</span> 或以上。',
|
||||
'clusters.volume.title': '卷挂载',
|
||||
'clusters.volume.name': '卷名称',
|
||||
'clusters.volume.mountPath': '容器内路径',
|
||||
|
||||
@@ -161,6 +161,7 @@ export default {
|
||||
'common.time.hour': '小时',
|
||||
'common.time.minute': '分钟',
|
||||
'common.issue.report': '问题反馈',
|
||||
'common.github.star.tooltip': '在 GitHub 上 Star 我们',
|
||||
'common.social.discord': '加入我们的Discord',
|
||||
'common.table.mark': '备注',
|
||||
'common.table.rollback.mark': '回滚备注',
|
||||
@@ -189,6 +190,7 @@ export default {
|
||||
'common.table.user': '用户',
|
||||
'common.settings.instructions': '操作指引',
|
||||
'common.settings.language': '语言',
|
||||
'common.settings.language.tips': '设置界面的显示语言。',
|
||||
'common.delete.confirm': '确定删除选中的{type}吗?',
|
||||
'common.delete.single.confirm':
|
||||
'确定删除以下内容?\n <span style="font-size: 13px;font-weight: 700">{name}</span>',
|
||||
@@ -245,6 +247,9 @@ 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': '页面似乎需要更新,刷新一下试试吧!',
|
||||
'common.tips.escape.disable': '请点击「取消」按钮或右上角 X 关闭窗口',
|
||||
@@ -253,6 +258,10 @@ 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}',
|
||||
@@ -260,7 +269,7 @@ export default {
|
||||
'common.button.terminal': '终端',
|
||||
'common.button.addItem': '添加项',
|
||||
'common.help.default': '默认: {content}',
|
||||
'common.filter.status': '按状态筛选',
|
||||
'common.filter.status': '按状态过滤',
|
||||
'common.form.rule.selectInput': '请选择或输入{name}',
|
||||
'common.tag.experimental': '实验性',
|
||||
'common.title.example': '示例',
|
||||
|
||||
@@ -75,6 +75,8 @@ export default {
|
||||
'gpuservice.publicKey': 'SSH 公钥',
|
||||
'gpuservice.publicKey.add': '添加 SSH 公钥',
|
||||
'gpuservice.publicKey.edit': '编辑 SSH 公钥',
|
||||
'gpuservice.publicKey.delete.tips':
|
||||
'删除 SSH Public Key 不会撤销已挂载 GPU 实例的访问权限。如需移除访问权限,请分别编辑对应的 GPU 实例。',
|
||||
'gpuservice.publicKey.filter.name': '按名称搜索',
|
||||
'gpuservice.publicKey.label': 'SSH 公钥',
|
||||
'gpuservice.instance.ssh.enable': '启用 SSH 访问',
|
||||
@@ -109,6 +111,8 @@ export default {
|
||||
'gpuservice.instance.sliced': '切分',
|
||||
'gpuservice.instance.memory': '显存',
|
||||
'gpuservice.instance.ram': '内存',
|
||||
'gpuservice.instance.os': '系统',
|
||||
'gpuservice.instance.arch': '架构',
|
||||
'gpuservice.instance.disk': '磁盘',
|
||||
'gpuservice.table.count': '数量',
|
||||
'gpuservice.instance.disk.system': '系统盘',
|
||||
@@ -156,5 +160,9 @@ export default {
|
||||
'gpuservice.storage.tempCapacity.required': '请输入临时存储容量',
|
||||
'gpuservice.form.rule.name':
|
||||
'由小写字母、数字和 "-" 组成,以字母或数字开头和结尾,不能包含连续的 "-",最多 63 个字符。',
|
||||
'gpuservice.form.storage.select': '选择存储'
|
||||
'gpuservice.form.storage.select': '选择存储',
|
||||
'gpuservice.creator': '创建者',
|
||||
'gpuservice.owner.global': '全局',
|
||||
'gpuservice.template.group.yours': '我的模板',
|
||||
'gpuservice.template.group.global': '全局模板'
|
||||
};
|
||||
|
||||
@@ -31,6 +31,7 @@ export default {
|
||||
'menu.usage.usage': '使用量',
|
||||
'menu.billingAndUsage': '用量与计费',
|
||||
'menu.billingAndUsage.usage': '用量统计',
|
||||
'menu.billingAndUsage.billing': '计费',
|
||||
'menu.404': '404',
|
||||
'menu.resources.workers': '节点',
|
||||
'menu.resources.gpus': 'GPU',
|
||||
|
||||
@@ -99,7 +99,7 @@ export default {
|
||||
'models.form.search.gguftips':
|
||||
'当 macOS 或 Windows 作节点时勾选 GGUF(搜索语音模型时取消勾选)',
|
||||
'models.form.button.addlabel': '添加标签',
|
||||
'models.filter.category': '按类别筛选',
|
||||
'models.filter.category': '按类别过滤',
|
||||
'models.list.more.logs': '查看更多',
|
||||
'models.catalog.release.date': '发布日期',
|
||||
'models.localpath.gguf.tips.title': 'GGUF 格式模型',
|
||||
|
||||
@@ -90,6 +90,7 @@ export default {
|
||||
// --- Resource events ---
|
||||
'usage.events.resourceType': '资源类型',
|
||||
'usage.events.eventType': '事件类型',
|
||||
'usage.events.resourceName': '按名称筛选',
|
||||
'usage.events.col.time': '时间',
|
||||
'usage.events.col.resource': '资源',
|
||||
'usage.events.col.event': '事件',
|
||||
|
||||
@@ -27,6 +27,7 @@ export default {
|
||||
'users.password.length': '长度在6至64个字符之间',
|
||||
'users.password.modify.title': '修改密码',
|
||||
'users.password.modify.description': '为了确保您的账户安全,请修改初始密码',
|
||||
'users.password.modify.tips': '定期更新密码有助于保护账户安全。',
|
||||
'users.password.confirm': '确认新密码',
|
||||
'users.password.confirm.empty': '请确认新密码',
|
||||
'users.password.confirm.error': '两次输入的密码不一致',
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import useCoolColors from '@/hooks/use-cool-colors';
|
||||
import { Chart } from '@gpustack/core-ui';
|
||||
import { formatLargeNumber } from '@gpustack/core-ui/utils';
|
||||
import { Empty, theme } from 'antd';
|
||||
import { Empty, Spin, theme } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useEffect, useMemo, useRef } from 'react';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
|
||||
export interface BarSeriesItem {
|
||||
name: string;
|
||||
@@ -17,10 +23,18 @@ export interface BarChartProps {
|
||||
xAxisData: string[];
|
||||
height: number | string;
|
||||
width?: number | string;
|
||||
loading?: boolean;
|
||||
legendData?: { name: string; icon?: string }[];
|
||||
labelFormatter?: (val: any, index?: number) => string;
|
||||
tooltipValueFormatter?: (val: any) => string;
|
||||
title?: string;
|
||||
grid?: {
|
||||
left?: number | string;
|
||||
right?: number | string;
|
||||
top?: number | string;
|
||||
bottom?: number | string;
|
||||
containLabel?: boolean;
|
||||
};
|
||||
legendIsolate?: boolean;
|
||||
}
|
||||
|
||||
@@ -31,15 +45,38 @@ const BarChart: React.FC<BarChartProps> = (props) => {
|
||||
height,
|
||||
width,
|
||||
legendData,
|
||||
loading,
|
||||
labelFormatter,
|
||||
tooltipValueFormatter,
|
||||
title,
|
||||
grid,
|
||||
legendIsolate
|
||||
} = props;
|
||||
const { token } = theme.useToken();
|
||||
const chartRef = useRef<{ chart: any } | null>(null);
|
||||
const generateCoolColors = useCoolColors();
|
||||
|
||||
// ECharts reads the DOM width at init time; with a "100%" width it can pick
|
||||
// up a stale/tiny value while the flex child's layout is still resolving,
|
||||
// rendering every bar squeezed at the left edge until its internal (throttled)
|
||||
// ResizeObserver corrects it ~100ms later — a visible blue-sliver flash. We
|
||||
// measure the container ourselves via a callback ref (which fires during
|
||||
// commit, before paint) and feed ECharts an explicit pixel width, so the very
|
||||
// first render is already correct. No gating, so the chart mounts with no
|
||||
// extra delay; the ResizeObserver keeps the width in sync afterwards.
|
||||
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
||||
const [measuredWidth, setMeasuredWidth] = useState(0);
|
||||
const measureRef = useCallback((node: HTMLDivElement | null) => {
|
||||
resizeObserverRef.current?.disconnect();
|
||||
if (!node) return;
|
||||
setMeasuredWidth(node.clientWidth);
|
||||
resizeObserverRef.current = new ResizeObserver(() => {
|
||||
setMeasuredWidth(node.clientWidth);
|
||||
});
|
||||
resizeObserverRef.current.observe(node);
|
||||
}, []);
|
||||
useEffect(() => () => resizeObserverRef.current?.disconnect(), []);
|
||||
|
||||
const dynamicColors = useMemo(
|
||||
() => generateCoolColors(seriesData.length),
|
||||
[seriesData.length, generateCoolColors]
|
||||
@@ -126,8 +163,9 @@ const BarChart: React.FC<BarChartProps> = (props) => {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: title ? 30 : 10,
|
||||
bottom: 0,
|
||||
containLabel: true
|
||||
bottom: 8,
|
||||
containLabel: true,
|
||||
...grid
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
@@ -268,18 +306,43 @@ const BarChart: React.FC<BarChartProps> = (props) => {
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
{loading ? (
|
||||
<Spin size="middle" />
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Chart
|
||||
ref={chartRef as any}
|
||||
options={options as any}
|
||||
height={height}
|
||||
width={width || '100%'}
|
||||
/>
|
||||
<div
|
||||
ref={measureRef}
|
||||
style={{ width: width || '100%', height, position: 'relative' }}
|
||||
>
|
||||
<Chart
|
||||
ref={chartRef as any}
|
||||
options={options as any}
|
||||
height={height}
|
||||
width={measuredWidth || width || '100%'}
|
||||
/>
|
||||
{loading && (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
inset: 20,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
background: 'var(--ant-color-bg-container)',
|
||||
opacity: 0.6,
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
>
|
||||
<Spin size="middle" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import { IconFont } from '@gpustack/core-ui';
|
||||
import { Collapse, CollapseProps } from 'antd';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const CollapseInner = styled(Collapse)`
|
||||
.ant-collapse-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px !important;
|
||||
padding-inline: 5px !important;
|
||||
padding-block: 8px !important;
|
||||
border-radius: var(--border-radius-base) !important;
|
||||
font-size: 14px !important;
|
||||
font-weight: 600 !important;
|
||||
&:hover {
|
||||
background-color: var(--ant-color-fill-tertiary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-collapse-body {
|
||||
padding-inline: 0 !important;
|
||||
padding-block: 0 !important;
|
||||
}
|
||||
.ant-collapse-header-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
}
|
||||
`;
|
||||
|
||||
const CollapsePanel: React.FC<{
|
||||
items: CollapseProps['items'];
|
||||
activeKey: string | string[];
|
||||
accordion?: boolean;
|
||||
defaultActiveKey?: string | string[];
|
||||
onChange?: (key: string | string[]) => void;
|
||||
styles?: Record<string, React.CSSProperties>;
|
||||
}> = ({ items, activeKey, accordion, defaultActiveKey, onChange, styles }) => {
|
||||
return (
|
||||
<CollapseInner
|
||||
expandIconPlacement="start"
|
||||
bordered={false}
|
||||
ghost
|
||||
accordion={accordion}
|
||||
activeKey={activeKey}
|
||||
defaultActiveKey={defaultActiveKey}
|
||||
onChange={onChange}
|
||||
destroyOnHidden={false}
|
||||
styles={{
|
||||
...styles,
|
||||
header: {
|
||||
backgroundColor: 'var(--ant-collapse-header-bg)'
|
||||
}
|
||||
}}
|
||||
expandIcon={({ isActive }) => (
|
||||
<IconFont
|
||||
type="icon-down"
|
||||
rotate={isActive ? 0 : -90}
|
||||
style={{ fontSize: '14px' }}
|
||||
></IconFont>
|
||||
)}
|
||||
items={items}
|
||||
></CollapseInner>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollapsePanel;
|
||||
@@ -1,250 +0,0 @@
|
||||
import {
|
||||
readColumnSettings,
|
||||
writeColumnSettings
|
||||
} from '@/utils/localstore/index';
|
||||
import { SettingOutlined } from '@ant-design/icons';
|
||||
import { OverlayScroller } from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Checkbox, Col, Popover, Row, Tooltip } from 'antd';
|
||||
import React, { useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Container = styled.div`
|
||||
padding: 8px 12px;
|
||||
padding-right: 4px;
|
||||
.title {
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.btn-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-top: 12px;
|
||||
}
|
||||
.buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
`;
|
||||
|
||||
const Title = styled.div`
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
margin-top: 4px;
|
||||
`;
|
||||
|
||||
const LabelWrapper = styled.span`
|
||||
color: var(--ant-color-text-secondary);
|
||||
> span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
.sub-title {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ColumnSettings: React.FC<{
|
||||
width?: number;
|
||||
fixedColumns?: string[];
|
||||
tableName: string;
|
||||
contentHeight: number;
|
||||
columns: {
|
||||
title: React.ReactNode;
|
||||
dataIndex?: string;
|
||||
children?: { title: React.ReactNode; dataIndex?: string }[];
|
||||
}[];
|
||||
selectedColumns?: string[];
|
||||
defaultSelectedColumns?: string[];
|
||||
grouped?: boolean;
|
||||
onReset?: () => void;
|
||||
onChange?: (selectedColumns: string[]) => void;
|
||||
}> = (props) => {
|
||||
const intl = useIntl();
|
||||
const {
|
||||
tableName,
|
||||
contentHeight,
|
||||
width = 420,
|
||||
columns,
|
||||
selectedColumns,
|
||||
defaultSelectedColumns,
|
||||
grouped,
|
||||
onReset,
|
||||
onChange,
|
||||
fixedColumns
|
||||
} = props;
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [innerSelectedColumns, setInnerSelectedColumns] = React.useState<
|
||||
string[]
|
||||
>(defaultSelectedColumns || []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (open) {
|
||||
setInnerSelectedColumns(selectedColumns ?? defaultSelectedColumns ?? []);
|
||||
}
|
||||
}, [open, selectedColumns, defaultSelectedColumns]);
|
||||
|
||||
const handleToggle = () => {
|
||||
setOpen(!open);
|
||||
};
|
||||
|
||||
const handleDraftChange = (columns: string[]) => {
|
||||
setInnerSelectedColumns(columns);
|
||||
};
|
||||
|
||||
const handleSelectAll = () => {
|
||||
if (grouped) {
|
||||
const allCols: string[] = [];
|
||||
columns.forEach((group) => {
|
||||
group.children?.forEach((col) => {
|
||||
if (col.dataIndex) {
|
||||
allCols.push(col.dataIndex);
|
||||
}
|
||||
});
|
||||
});
|
||||
handleDraftChange(allCols);
|
||||
} else {
|
||||
const allCols = columns
|
||||
.map((col) => col.dataIndex)
|
||||
.filter((dataIndex): dataIndex is string => Boolean(dataIndex));
|
||||
handleDraftChange(allCols);
|
||||
}
|
||||
};
|
||||
|
||||
const handleConfirm = () => {
|
||||
setOpen(false);
|
||||
writeColumnSettings(tableName, innerSelectedColumns);
|
||||
onChange?.(innerSelectedColumns);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
const resetCols = defaultSelectedColumns ?? [];
|
||||
writeColumnSettings(tableName, resetCols);
|
||||
onChange?.(resetCols);
|
||||
setInnerSelectedColumns(resetCols);
|
||||
onReset?.();
|
||||
};
|
||||
|
||||
const handleOpenChange = (isOpen: boolean) => {
|
||||
setOpen(isOpen);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const initColumns = async () => {
|
||||
const stored = await readColumnSettings(tableName);
|
||||
if (stored && stored.length > 0) {
|
||||
setInnerSelectedColumns(stored);
|
||||
onChange?.(stored);
|
||||
} else {
|
||||
setInnerSelectedColumns(defaultSelectedColumns || []);
|
||||
}
|
||||
};
|
||||
initColumns();
|
||||
}, []);
|
||||
|
||||
const contentRender = () => {
|
||||
return (
|
||||
<Container>
|
||||
{!grouped && (
|
||||
<div className="title">
|
||||
{intl.formatMessage({ id: 'benchmark.table.columnSettings' })}
|
||||
</div>
|
||||
)}
|
||||
<OverlayScroller
|
||||
maxHeight={contentHeight}
|
||||
styles={{
|
||||
wrapper: {
|
||||
paddingInlineStart: 0
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Checkbox.Group
|
||||
value={innerSelectedColumns}
|
||||
onChange={handleDraftChange}
|
||||
>
|
||||
<>
|
||||
{grouped ? (
|
||||
columns.map((row, index) => (
|
||||
<div key={index}>
|
||||
<Title>{row.title}</Title>
|
||||
<Row>
|
||||
{row.children?.map((col) => (
|
||||
<Col key={col.dataIndex} span={12}>
|
||||
<Checkbox
|
||||
disabled={fixedColumns?.includes(
|
||||
col.dataIndex || ''
|
||||
)}
|
||||
value={col.dataIndex}
|
||||
style={{ marginBottom: 8 }}
|
||||
>
|
||||
<LabelWrapper>{col.title}</LabelWrapper>
|
||||
</Checkbox>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<Row>
|
||||
{columns.map((col) => (
|
||||
<Col key={col.dataIndex} span={12}>
|
||||
<Checkbox
|
||||
disabled={fixedColumns?.includes(col.dataIndex || '')}
|
||||
value={col.dataIndex}
|
||||
style={{ marginBottom: 8 }}
|
||||
>
|
||||
<LabelWrapper>{col.title}</LabelWrapper>
|
||||
</Checkbox>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
)}
|
||||
</>
|
||||
</Checkbox.Group>
|
||||
</OverlayScroller>
|
||||
|
||||
<div className="btn-wrapper">
|
||||
<Button size="middle" onClick={handleReset}>
|
||||
{intl.formatMessage({ id: 'common.button.resetdefault' })}
|
||||
</Button>
|
||||
<div className="buttons">
|
||||
<Button size="middle" type="primary" onClick={handleSelectAll}>
|
||||
{intl.formatMessage({ id: 'common.checkbox.all' })}
|
||||
</Button>
|
||||
<Button size="middle" type="primary" onClick={handleConfirm}>
|
||||
{intl.formatMessage({ id: 'common.button.save' })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover
|
||||
open={open}
|
||||
onOpenChange={handleOpenChange}
|
||||
trigger={'click'}
|
||||
arrow={false}
|
||||
placement="bottomRight"
|
||||
content={contentRender()}
|
||||
styles={{
|
||||
root: {
|
||||
width: width
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Tooltip
|
||||
title={intl.formatMessage({ id: 'benchmark.table.columnSettings' })}
|
||||
>
|
||||
<Button onClick={handleToggle} icon={<SettingOutlined />}></Button>
|
||||
</Tooltip>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColumnSettings;
|
||||
@@ -1,99 +0,0 @@
|
||||
import { useOverlayScroller } from '@gpustack/core-ui';
|
||||
import React, { useCallback } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { WrapperContext } from './use-wrapper-context';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const ContentWrapper = styled.div`
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
const Footer = styled.div`
|
||||
padding-block: 0;
|
||||
background-color: var(--ant-color-bg-elevated);
|
||||
`;
|
||||
|
||||
interface ColumnWrapperProps {
|
||||
children: React.ReactNode;
|
||||
footer?: React.ReactNode;
|
||||
maxHeight?: string | number;
|
||||
paddingBottom?: number;
|
||||
styles?: {
|
||||
wrapper?: React.CSSProperties;
|
||||
container?: React.CSSProperties;
|
||||
};
|
||||
}
|
||||
|
||||
const ColumnWrapper: React.FC<ColumnWrapperProps> = ({
|
||||
children,
|
||||
footer,
|
||||
maxHeight,
|
||||
styles = {}
|
||||
}) => {
|
||||
const scroller = React.useRef<any>(null);
|
||||
const footerRef = React.useRef<HTMLDivElement>(null);
|
||||
const contentRef = React.useRef<HTMLDivElement>(null);
|
||||
const {
|
||||
initialize,
|
||||
instance,
|
||||
scrollEventElement,
|
||||
scrollToBottom,
|
||||
scrollToTarget,
|
||||
getScrollElementScrollableHeight
|
||||
} = useOverlayScroller({
|
||||
options: {
|
||||
scrollbars: {
|
||||
autoHide: 'move'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
if (scroller.current) {
|
||||
initialize(scroller.current);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setContentPaddingBottom = useCallback((padding: number) => {
|
||||
contentRef.current!.style.paddingBottom = `${padding}px`;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<WrapperContext.Provider
|
||||
value={{
|
||||
scroller: scroller,
|
||||
osInstance: instance,
|
||||
scrollEventElement,
|
||||
getScrollElementScrollableHeight,
|
||||
scrollToBottom,
|
||||
scrollToTarget,
|
||||
setSScrollContentPaddingBottom: setContentPaddingBottom
|
||||
}}
|
||||
>
|
||||
<Wrapper style={{ height: maxHeight || '100%', ...styles.wrapper }}>
|
||||
<ContentWrapper
|
||||
ref={scroller}
|
||||
style={{
|
||||
padding: '16px 24px',
|
||||
...styles.container
|
||||
}}
|
||||
>
|
||||
<div ref={contentRef}>{children}</div>
|
||||
</ContentWrapper>
|
||||
{footer && <Footer ref={footerRef}>{footer}</Footer>}
|
||||
</Wrapper>
|
||||
</WrapperContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColumnWrapper;
|
||||
@@ -1,28 +0,0 @@
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
interface WrapperContextProps {
|
||||
osInstance?: any;
|
||||
scroller?: any;
|
||||
scrollEventElement?: any;
|
||||
scrollToBottom?: () => void;
|
||||
scrollToTop?: () => void;
|
||||
getScrollElementScrollableHeight?: () => {
|
||||
scrollHeight: number;
|
||||
scrollTop: number;
|
||||
};
|
||||
scrollToTarget?: (target: any, offset?: number) => void;
|
||||
getScrollElement?: () => HTMLElement | null;
|
||||
setSScrollContentPaddingBottom?: (padding: number) => void;
|
||||
}
|
||||
|
||||
export const WrapperContext = createContext<WrapperContextProps>(
|
||||
{} as WrapperContextProps
|
||||
);
|
||||
|
||||
export const useWrapperContext = () => {
|
||||
const context = useContext(WrapperContext);
|
||||
if (!context) {
|
||||
throw new Error('useWrapperContext must be used within a WrapperProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
@@ -1,155 +0,0 @@
|
||||
import { CloseOutlined } from '@ant-design/icons';
|
||||
import { OverlayScroller } from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Form } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import React, { forwardRef, useImperativeHandle } from 'react';
|
||||
import filterFormCss from '../styles/filter-form.less';
|
||||
|
||||
const FilterForm: React.FC<
|
||||
React.PropsWithChildren & {
|
||||
ref?: any;
|
||||
width?: number;
|
||||
contentHeight?: number | string;
|
||||
initialValues?: any;
|
||||
hasFilters?: boolean;
|
||||
open?: boolean;
|
||||
onClear?: () => void;
|
||||
onClose?: () => void;
|
||||
onValuesChange?: (ChangeValues: any, allValues: any) => void;
|
||||
styles?: {
|
||||
container?: React.CSSProperties;
|
||||
wrapper?: React.CSSProperties;
|
||||
};
|
||||
}
|
||||
> = forwardRef(
|
||||
(
|
||||
{
|
||||
children,
|
||||
open,
|
||||
width = 300,
|
||||
contentHeight = 400,
|
||||
initialValues = {},
|
||||
styles,
|
||||
onClose,
|
||||
onClear,
|
||||
onValuesChange
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const intl = useIntl();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const handleOnReset = () => {
|
||||
form.resetFields(Object.keys(initialValues));
|
||||
form.resetFields();
|
||||
onValuesChange?.({}, form.getFieldsValue());
|
||||
};
|
||||
|
||||
const handleOnClose = () => {
|
||||
onClose?.();
|
||||
};
|
||||
|
||||
const handleOnValuesChange = (changedValues: any, allValues: any) => {
|
||||
onValuesChange?.(changedValues, allValues);
|
||||
};
|
||||
|
||||
const handleOnClear = () => {
|
||||
handleOnReset();
|
||||
};
|
||||
|
||||
const filtersCount = Object.values(form.getFieldsValue()).filter(
|
||||
(value) => value !== undefined && value !== null && value !== ''
|
||||
).length;
|
||||
|
||||
const renderFooter = () => {
|
||||
return (
|
||||
<div className={filterFormCss['btn-wrapper']}>
|
||||
<Button size="middle" onClick={handleOnReset}>
|
||||
{intl.formatMessage({ id: 'common.button.reset' })}
|
||||
</Button>
|
||||
<div className={filterFormCss.buttons}>
|
||||
<Button size="middle" type="primary" onClick={handleOnClose}>
|
||||
{intl.formatMessage({ id: 'common.button.close' })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
form,
|
||||
reset: handleOnReset,
|
||||
getValues: () => form.getFieldsValue(),
|
||||
setValues: (values: any) => form.setFieldsValue(values)
|
||||
}));
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(filterFormCss.wrapper, {
|
||||
[filterFormCss.show]: open
|
||||
})}
|
||||
style={{
|
||||
width: open ? width : 0,
|
||||
height: contentHeight,
|
||||
...styles?.wrapper
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ width: width, ...styles?.container }}
|
||||
className={filterFormCss.container}
|
||||
>
|
||||
<div className={filterFormCss.title}>
|
||||
<span
|
||||
style={{
|
||||
fontWeight: 500,
|
||||
color: 'var(--ant-color-text-tertiary)'
|
||||
}}
|
||||
>
|
||||
{intl.formatMessage({ id: 'common.filter.label' })}
|
||||
</span>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<CloseOutlined />}
|
||||
onClick={handleOnClose}
|
||||
type="text"
|
||||
style={{
|
||||
color: 'var(--ant-color-text-secondary)'
|
||||
}}
|
||||
></Button>
|
||||
</div>
|
||||
<OverlayScroller
|
||||
maxHeight={contentHeight}
|
||||
styles={{
|
||||
wrapper: {
|
||||
paddingInline: 8
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Form
|
||||
onValuesChange={handleOnValuesChange}
|
||||
initialValues={initialValues}
|
||||
form={form}
|
||||
layout="vertical"
|
||||
styles={{
|
||||
label: {
|
||||
lineHeight: 1,
|
||||
height: 'auto',
|
||||
marginBottom: 8,
|
||||
fontWeight: 500
|
||||
},
|
||||
content: {
|
||||
minHeight: 0
|
||||
}
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Form>
|
||||
</OverlayScroller>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export default FilterForm;
|
||||
@@ -1,90 +0,0 @@
|
||||
import { ColumnWrapper, GSDrawer, ModalFooter } from '@gpustack/core-ui';
|
||||
import type { DrawerProps } from 'antd';
|
||||
import { Tag } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
const ModalFooterStyle = {
|
||||
padding: '16px 24px 8px',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end'
|
||||
};
|
||||
|
||||
type AddModalProps = {
|
||||
title: React.ReactNode;
|
||||
open: boolean;
|
||||
onCancel?: () => void;
|
||||
children?: React.ReactNode;
|
||||
onSubmit?: () => void;
|
||||
width?: number | string;
|
||||
footer?: React.ReactNode;
|
||||
subTitle?: React.ReactNode;
|
||||
push?: DrawerProps['push'];
|
||||
};
|
||||
const FormDrawer: React.FC<AddModalProps> = ({
|
||||
title,
|
||||
open,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
children,
|
||||
width = 600,
|
||||
subTitle,
|
||||
footer,
|
||||
push
|
||||
}) => {
|
||||
return (
|
||||
<GSDrawer
|
||||
title={
|
||||
<>
|
||||
{title}
|
||||
{subTitle && (
|
||||
<Tag
|
||||
variant="outlined"
|
||||
style={{
|
||||
fontSize: 12,
|
||||
fontWeight: 400,
|
||||
marginLeft: 8,
|
||||
borderRadius: 4,
|
||||
borderColor: 'var(--ant-color-border-secondary)',
|
||||
color: 'var(--ant-color-text-secondary)'
|
||||
}}
|
||||
>
|
||||
{subTitle}
|
||||
</Tag>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
open={open}
|
||||
onClose={onCancel}
|
||||
destroyOnHidden={true}
|
||||
closeIcon={false}
|
||||
mask={{
|
||||
closable: false
|
||||
}}
|
||||
keyboard={false}
|
||||
push={push}
|
||||
styles={{
|
||||
wrapper: { width }
|
||||
}}
|
||||
footer={false}
|
||||
>
|
||||
<ColumnWrapper
|
||||
styles={{
|
||||
container: { paddingBlock: 0 }
|
||||
}}
|
||||
footer={
|
||||
footer ?? (
|
||||
<ModalFooter
|
||||
onOk={onSubmit}
|
||||
onCancel={onCancel}
|
||||
style={ModalFooterStyle}
|
||||
></ModalFooter>
|
||||
)
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</ColumnWrapper>
|
||||
</GSDrawer>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormDrawer;
|
||||
@@ -1,81 +0,0 @@
|
||||
.mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 1004;
|
||||
height: 100%;
|
||||
background-color: var(--ant-color-bg-mask);
|
||||
opacity: 0;
|
||||
transition: opacity var(--ant-motion-duration-slow)
|
||||
var(--ant-motion-ease-in-out);
|
||||
}
|
||||
|
||||
.maskOpen {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
z-index: 1005;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: var(--ant-color-bg-elevated);
|
||||
border-radius: var(--ant-border-radius-lg) 0 0 var(--ant-border-radius-lg);
|
||||
box-shadow:
|
||||
-6px 0 16px 0 rgb(0 0 0 / 8%),
|
||||
-3px 0 6px -4px rgb(0 0 0 / 12%),
|
||||
-9px 0 28px 8px rgb(0 0 0 / 5%);
|
||||
transform: translateX(100%);
|
||||
transition: transform var(--ant-motion-duration-slow)
|
||||
var(--ant-motion-ease-in-out);
|
||||
}
|
||||
|
||||
.overlayOpen {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
min-height: 56px;
|
||||
padding: var(--ant-padding) var(--ant-padding-lg);
|
||||
border-bottom: 1px solid var(--ant-color-split);
|
||||
color: var(--ant-color-text);
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.subTitle {
|
||||
margin-left: 8px;
|
||||
border-color: var(--ant-color-border-secondary);
|
||||
border-radius: 4px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding-block: 16px;
|
||||
flex: 1;
|
||||
height: calc(100vh - 57px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 16px 24px 8px;
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
import { ColumnWrapper, IconFont } from '@gpustack/core-ui';
|
||||
import { Button, Tag } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import styles from './form-overlay-view.module.less';
|
||||
|
||||
type FormOverlayViewProps = {
|
||||
title: React.ReactNode;
|
||||
open: boolean;
|
||||
onCancel?: () => void;
|
||||
children?: React.ReactNode;
|
||||
onSubmit?: () => void;
|
||||
footer?: React.ReactNode;
|
||||
subTitle?: React.ReactNode;
|
||||
width?: number | string;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
maskClosable?: boolean;
|
||||
getContainer?: () => HTMLElement | null | undefined;
|
||||
};
|
||||
|
||||
const FormOverlayView: React.FC<FormOverlayViewProps> = ({
|
||||
title,
|
||||
open,
|
||||
onCancel,
|
||||
onSubmit,
|
||||
children,
|
||||
subTitle,
|
||||
footer,
|
||||
width = 600,
|
||||
className,
|
||||
style,
|
||||
maskClosable = false,
|
||||
getContainer
|
||||
}) => {
|
||||
const [container, setContainer] = React.useState<HTMLElement | null>(null);
|
||||
const [mounted, setMounted] = React.useState(false);
|
||||
const [active, setActive] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (open) {
|
||||
setContainer(getContainer?.() ?? null);
|
||||
setMounted(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setActive(false);
|
||||
}, [getContainer, open]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!mounted || !container) {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = requestAnimationFrame(() => setActive(true));
|
||||
return () => cancelAnimationFrame(id);
|
||||
}, [mounted, container]);
|
||||
|
||||
const handleTransitionEnd = (e: React.TransitionEvent<HTMLDivElement>) => {
|
||||
if (e.target !== e.currentTarget || e.propertyName !== 'transform') {
|
||||
return;
|
||||
}
|
||||
if (!open && !active) {
|
||||
setMounted(false);
|
||||
setContainer(null);
|
||||
}
|
||||
};
|
||||
|
||||
if (!mounted || !container) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<>
|
||||
<div
|
||||
className={classNames(styles.mask, { [styles.maskOpen]: active })}
|
||||
onClick={maskClosable ? onCancel : undefined}
|
||||
/>
|
||||
<div
|
||||
className={classNames(
|
||||
styles.overlay,
|
||||
{ [styles.overlayOpen]: active },
|
||||
className
|
||||
)}
|
||||
style={{ width, ...style }}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
onTransitionEnd={handleTransitionEnd}
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
style={{ fontWeight: 600, fontSize: 16 }}
|
||||
icon={<IconFont type="icon-down2" rotate={90} />}
|
||||
onClick={onCancel}
|
||||
/>
|
||||
<div className={styles.title}>
|
||||
{title}
|
||||
{subTitle && (
|
||||
<Tag variant="outlined" className={styles.subTitle}>
|
||||
{subTitle}
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ColumnWrapper
|
||||
styles={{
|
||||
container: { paddingBlock: 16 }
|
||||
}}
|
||||
footer={footer}
|
||||
>
|
||||
{children}
|
||||
</ColumnWrapper>
|
||||
</div>
|
||||
</>,
|
||||
container
|
||||
);
|
||||
};
|
||||
|
||||
export default FormOverlayView;
|
||||
File diff suppressed because one or more lines are too long
@@ -1,26 +0,0 @@
|
||||
import { update } from 'jdenticon';
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
interface IdenticonProps {
|
||||
value: string;
|
||||
size: number;
|
||||
}
|
||||
|
||||
const Identicon: React.FC<IdenticonProps> = ({
|
||||
value = 'test',
|
||||
size = 28
|
||||
}: IdenticonProps) => {
|
||||
const icon = useRef<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
update(icon.current, value);
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<svg data-jdenticon-value={value} height={size} ref={icon} width={size} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Identicon;
|
||||
@@ -1,68 +0,0 @@
|
||||
import {
|
||||
digitReg,
|
||||
lowercaseReg,
|
||||
specialCharacterReg,
|
||||
uppercaseReg
|
||||
} from '@/config';
|
||||
import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Space } from 'antd';
|
||||
|
||||
const PasswordValidate: React.FC<{ value: string }> = ({ value = '' }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const renderIcon = ({ valid, text }: { valid: boolean; text: string }) => {
|
||||
return (
|
||||
<>
|
||||
{valid ? (
|
||||
<CheckCircleFilled style={{ color: 'green' }} />
|
||||
) : (
|
||||
<CloseCircleFilled style={{ color: 'red' }} />
|
||||
)}
|
||||
<span
|
||||
className="m-l-5"
|
||||
style={{ color: 'var(--ant-color-text-description)' }}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
</>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<Space direction="vertical" style={{ paddingTop: '10px' }}>
|
||||
<span>
|
||||
{renderIcon({
|
||||
valid: uppercaseReg.test(value),
|
||||
text: intl.formatMessage({ id: 'users.password.uppcase' })
|
||||
})}
|
||||
</span>
|
||||
<span>
|
||||
{renderIcon({
|
||||
valid: lowercaseReg.test(value),
|
||||
text: intl.formatMessage({ id: 'users.password.lowercase' })
|
||||
})}
|
||||
</span>
|
||||
|
||||
<span>
|
||||
{renderIcon({
|
||||
valid: digitReg.test(value),
|
||||
text: intl.formatMessage({ id: 'users.password.number' })
|
||||
})}
|
||||
</span>
|
||||
<span>
|
||||
{renderIcon({
|
||||
valid: value.length >= 6 && value.length <= 12,
|
||||
text: intl.formatMessage({ id: 'users.password.length' })
|
||||
})}
|
||||
</span>
|
||||
<span>
|
||||
{renderIcon({
|
||||
valid: specialCharacterReg.test(value),
|
||||
text: intl.formatMessage({ id: 'users.password.special' })
|
||||
})}
|
||||
</span>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
export default PasswordValidate;
|
||||
@@ -2,7 +2,13 @@ import useCoolColors from '@/hooks/use-cool-colors';
|
||||
import { Chart } from '@gpustack/core-ui';
|
||||
import { formatLargeNumber } from '@gpustack/core-ui/utils';
|
||||
import { Empty, Spin, theme } from 'antd';
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react';
|
||||
|
||||
export interface PieChartItem {
|
||||
name: string;
|
||||
@@ -37,6 +43,26 @@ const PieChart: React.FC<PieChartProps> = ({
|
||||
const chartRef = useRef<{ chart: any } | null>(null);
|
||||
const generateCoolColors = useCoolColors();
|
||||
|
||||
// ECharts reads the DOM width at init time; with a "100%" width it can pick
|
||||
// up a stale/tiny value while layout is still resolving, briefly rendering
|
||||
// the donut undersized before its internal (throttled) ResizeObserver
|
||||
// corrects it — a visible flash. We measure the container via a callback ref
|
||||
// (fires during commit, before paint) and feed ECharts an explicit pixel
|
||||
// width so the first render is already correct. The ResizeObserver keeps it
|
||||
// in sync afterwards.
|
||||
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
||||
const [measuredWidth, setMeasuredWidth] = useState(0);
|
||||
const measureRef = useCallback((node: HTMLDivElement | null) => {
|
||||
resizeObserverRef.current?.disconnect();
|
||||
if (!node) return;
|
||||
setMeasuredWidth(node.clientWidth);
|
||||
resizeObserverRef.current = new ResizeObserver(() => {
|
||||
setMeasuredWidth(node.clientWidth);
|
||||
});
|
||||
resizeObserverRef.current.observe(node);
|
||||
}, []);
|
||||
useEffect(() => () => resizeObserverRef.current?.disconnect(), []);
|
||||
|
||||
const colors = useMemo(() => {
|
||||
const generatedColors = generateCoolColors(data.length + colorOffset);
|
||||
return generatedColors.slice(colorOffset);
|
||||
@@ -142,12 +168,12 @@ const PieChart: React.FC<PieChartProps> = ({
|
||||
const displayTotal = total ?? data.reduce((sum, item) => sum + item.value, 0);
|
||||
|
||||
return (
|
||||
<div style={{ width, height, position: 'relative' }}>
|
||||
<div ref={measureRef} style={{ width, height, position: 'relative' }}>
|
||||
<Chart
|
||||
ref={chartRef as any}
|
||||
options={options as any}
|
||||
height={height}
|
||||
width={width}
|
||||
width={measuredWidth || width}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
import { AutoTooltip } from '@gpustack/core-ui';
|
||||
import React from 'react';
|
||||
import pillButtonCss from './styles.less';
|
||||
type Option = {
|
||||
label: string;
|
||||
value: string | number | null;
|
||||
icon?: React.ReactNode;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
value?: string | number;
|
||||
onChange?: (value: string | number | undefined | null) => void;
|
||||
options: Option[];
|
||||
disabled?: boolean;
|
||||
variant?: 'filled' | 'outlined' | 'solid';
|
||||
};
|
||||
|
||||
const PillButtonGroup: React.FC<Props> = ({
|
||||
value,
|
||||
onChange,
|
||||
options,
|
||||
disabled,
|
||||
variant = 'outlined'
|
||||
}) => {
|
||||
return (
|
||||
<div className={pillButtonCss['wrapper']}>
|
||||
{options.map((item) => {
|
||||
const active = value === item.value;
|
||||
|
||||
return (
|
||||
<AutoTooltip
|
||||
ghost
|
||||
title={item.label}
|
||||
key={item.value}
|
||||
maxWidth={'100%'}
|
||||
>
|
||||
<span
|
||||
className={`${pillButtonCss['pill-item']} ${active ? pillButtonCss['active'] : ''}`}
|
||||
key={item.value}
|
||||
color="default"
|
||||
onClick={() => {
|
||||
if (item.value !== value) {
|
||||
onChange?.(item.value);
|
||||
}
|
||||
if (item.value === value) {
|
||||
onChange?.(undefined);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{item.icon}
|
||||
{item.label}
|
||||
</span>
|
||||
</AutoTooltip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PillButtonGroup;
|
||||
@@ -1,31 +0,0 @@
|
||||
.pill-item {
|
||||
height: 28px;
|
||||
line-height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
justify-content: center;
|
||||
border-radius: var(--ant-border-radius);
|
||||
background-color: var(--ant-color-fill-tertiary);
|
||||
color: var(--ant-color-text-secondary);
|
||||
padding: 0 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&.active {
|
||||
background-color: var(--ant-color-fill);
|
||||
color: var(--ant-color-text);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&:not(.active):hover {
|
||||
background-color: var(--ant-color-fill-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user