Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2767f883fc | ||
|
|
e98651da2b | ||
|
|
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 | ||
|
|
bda8264788 | ||
|
|
ab8e792276 | ||
|
|
0e4be63b6b | ||
|
|
d3ebea9090 | ||
|
|
15a9e23243 | ||
|
|
eab7d9c77f | ||
|
|
4a86b71018 | ||
|
|
6350f7f11a | ||
|
|
ce6388253c | ||
|
|
d8f6064d5e | ||
|
|
9f5d95f565 | ||
|
|
0cabbeb81c | ||
|
|
76618aa3a1 | ||
|
|
b19db0f67d | ||
|
|
76f228bfe7 | ||
|
|
be40692773 | ||
|
|
42ac377c17 | ||
|
|
46f859cc3c | ||
|
|
f2bff0d1fb | ||
|
|
0bd98b9662 | ||
|
|
1ac2c02197 | ||
|
|
907187d53a | ||
|
|
faf6246fe6 | ||
|
|
0c16e2be7a | ||
|
|
3212882895 | ||
|
|
62f34ffcaf | ||
|
|
50601b0aff | ||
|
|
4c5d42cb13 | ||
|
|
85268b8917 | ||
|
|
973d2c4529 | ||
|
|
ab766b8c54 | ||
|
|
4945242c87 | ||
|
|
c4a72eb375 | ||
|
|
b12d11728d | ||
|
|
8f0e3a7576 | ||
|
|
4810ff2dbe | ||
|
|
a221b22be9 | ||
|
|
24b458530d | ||
|
|
caea43590a | ||
|
|
c3d7a0b85e | ||
|
|
5c6f28027d | ||
|
|
dc04529f5b | ||
|
|
2bb04a419d | ||
|
|
8e31b71668 | ||
|
|
f055453889 | ||
|
|
820a6cecdd | ||
|
|
c3a779e4c8 | ||
|
|
00008abf03 | ||
|
|
d9c6f2f780 | ||
|
|
9a4d77e869 | ||
|
|
2407416e33 | ||
|
|
234e42ccfa | ||
|
|
4426b8dc30 | ||
|
|
9541f49f39 | ||
|
|
9f7a99f370 | ||
|
|
12f0bb2f98 | ||
|
|
ac45a72e0e | ||
|
|
9705818b15 | ||
|
|
39374eafda | ||
|
|
110bc3205e | ||
|
|
57fec89f2d | ||
|
|
72e794c281 | ||
|
|
fadbcc3e44 | ||
|
|
9e30e964e6 | ||
|
|
fbb707d014 | ||
|
|
d2d82ee5ee | ||
|
|
9061fa08ba | ||
|
|
f29236518e | ||
|
|
6537de5224 | ||
|
|
0413d470a8 | ||
|
|
c01f4d3ce8 | ||
|
|
dd9d359bb0 | ||
|
|
b444c35643 | ||
|
|
abd0d52fad | ||
|
|
c8771dd3b9 | ||
|
|
6d16f56183 | ||
|
|
95ffe0350e | ||
|
|
e314f25a77 | ||
|
|
65ea65e9ae | ||
|
|
4ce975562e | ||
|
|
07e625f2bc | ||
|
|
d95fd8dc90 | ||
|
|
55028cf3a3 |
@@ -31,5 +31,6 @@ export default function createProxyTable(target?: string) {
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
|
|
||||||
return proxyTable;
|
return proxyTable;
|
||||||
}
|
}
|
||||||
|
|||||||
+73
-42
@@ -140,15 +140,6 @@ const baseRoutes = [
|
|||||||
access: 'canSeeOrgAdmin',
|
access: 'canSeeOrgAdmin',
|
||||||
component: './model-routes/index'
|
component: './model-routes/index'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'usage',
|
|
||||||
path: '/models/usage',
|
|
||||||
key: 'usage',
|
|
||||||
icon: 'icon-usage-outlined',
|
|
||||||
selectedIcon: 'icon-usage-filled',
|
|
||||||
defaultIcon: 'icon-usage-outlined',
|
|
||||||
component: './usage/index'
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'providers',
|
name: 'providers',
|
||||||
path: '/models/providers',
|
path: '/models/providers',
|
||||||
@@ -197,6 +188,7 @@ const baseRoutes = [
|
|||||||
icon: 'icon-files',
|
icon: 'icon-files',
|
||||||
selectedIcon: 'icon-files-filled',
|
selectedIcon: 'icon-files-filled',
|
||||||
defaultIcon: 'icon-files',
|
defaultIcon: 'icon-files',
|
||||||
|
access: 'canSeeOrgAdmin',
|
||||||
component: './resources/components/model-files'
|
component: './resources/components/model-files'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -274,6 +266,16 @@ const baseRoutes = [
|
|||||||
path: '/resources',
|
path: '/resources',
|
||||||
redirect: '/resources/workers'
|
redirect: '/resources/workers'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'clusters',
|
||||||
|
path: '/resources/clusters/list',
|
||||||
|
key: 'clusters',
|
||||||
|
icon: 'icon-cluster2-outline',
|
||||||
|
selectedIcon: 'icon-cluster2-filled',
|
||||||
|
defaultIcon: 'icon-cluster2-outline',
|
||||||
|
component: './cluster-management/clusters',
|
||||||
|
subMenu: ['/resources/clusters/detail', '/resources/clusters/create']
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'workers',
|
name: 'workers',
|
||||||
path: '/resources/workers',
|
path: '/resources/workers',
|
||||||
@@ -291,50 +293,65 @@ const baseRoutes = [
|
|||||||
selectedIcon: 'icon-gpu-filled',
|
selectedIcon: 'icon-gpu-filled',
|
||||||
defaultIcon: 'icon-gpu1',
|
defaultIcon: 'icon-gpu1',
|
||||||
component: './resources/components/gpus'
|
component: './resources/components/gpus'
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'clusterManagement',
|
name: 'credentials',
|
||||||
path: '/cluster-management',
|
path: '/resources/credentials',
|
||||||
key: 'clusterManagement',
|
key: 'credentials',
|
||||||
access: 'canSeeOrgAdmin',
|
icon: 'icon-credential-outline',
|
||||||
routes: [
|
selectedIcon: 'icon-credential-filled',
|
||||||
{
|
defaultIcon: 'icon-credential-outline',
|
||||||
path: '/cluster-management',
|
component: './cluster-management/credentials'
|
||||||
redirect: '/cluster-management/clusters/list'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'clusters',
|
|
||||||
path: '/cluster-management/clusters/list',
|
|
||||||
key: 'clusters',
|
|
||||||
icon: 'icon-cluster2-outline',
|
|
||||||
selectedIcon: 'icon-cluster2-filled',
|
|
||||||
defaultIcon: 'icon-cluster2-outline',
|
|
||||||
component: './cluster-management/clusters',
|
|
||||||
subMenu: [
|
|
||||||
'/cluster-management/clusters/detail',
|
|
||||||
'/cluster-management/clusters/create'
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'clusterDetail',
|
name: 'clusterDetail',
|
||||||
path: '/cluster-management/clusters/detail',
|
path: '/resources/clusters/detail',
|
||||||
key: 'clusterDetail',
|
key: 'clusterDetail',
|
||||||
icon: 'icon-cluster2-outline',
|
icon: 'icon-cluster2-outline',
|
||||||
selectedIcon: 'icon-cluster2-filled',
|
selectedIcon: 'icon-cluster2-filled',
|
||||||
defaultIcon: 'icon-cluster2-outline',
|
defaultIcon: 'icon-cluster2-outline',
|
||||||
hideInMenu: true,
|
hideInMenu: true,
|
||||||
component: './cluster-management/cluster-detail'
|
component: './cluster-management/cluster-detail'
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'credentials',
|
// Cross-resource consumption (tokens + GPU/CPU instances + storage).
|
||||||
path: '/cluster-management/credentials',
|
// A folder so it matches the other top-level groups; more usage views can
|
||||||
key: 'credentials',
|
// graduate in here later.
|
||||||
icon: 'icon-credential-outline',
|
name: 'billingAndUsage',
|
||||||
selectedIcon: 'icon-credential-filled',
|
path: '/usage',
|
||||||
defaultIcon: 'icon-credential-outline',
|
key: 'usageGroup',
|
||||||
component: './cluster-management/credentials'
|
icon: 'icon-usage-outlined',
|
||||||
|
selectedIcon: 'icon-usage-filled',
|
||||||
|
defaultIcon: 'icon-usage-outlined',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/usage',
|
||||||
|
redirect: '/usage/overview'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'usage',
|
||||||
|
path: '/usage/overview',
|
||||||
|
key: 'usage',
|
||||||
|
icon: 'icon-usage-outlined',
|
||||||
|
selectedIcon: 'icon-usage-filled',
|
||||||
|
defaultIcon: 'icon-usage-outlined',
|
||||||
|
component: './usage/index'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'billing',
|
||||||
|
path: '/usage/billing',
|
||||||
|
key: 'billing',
|
||||||
|
icon: 'icon-billing-outlined',
|
||||||
|
selectedIcon: 'icon-billing-filled',
|
||||||
|
defaultIcon: 'icon-billing-outlined',
|
||||||
|
hideInMenu: process.env.ENABLE_ENTERPRISE === 'true',
|
||||||
|
// OSS exposes the menu as a teaser for the enterprise billing
|
||||||
|
// module. The page itself just renders an upsell notice — the real
|
||||||
|
// billing UI lives in the enterprise plugin and shadows this route
|
||||||
|
// via `routes.extensions.ts`.
|
||||||
|
component: './billing'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -347,6 +364,20 @@ const baseRoutes = [
|
|||||||
path: '/access-control',
|
path: '/access-control',
|
||||||
redirect: '/access-control/users'
|
redirect: '/access-control/users'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'organizations',
|
||||||
|
path: '/access-control/organizations',
|
||||||
|
key: 'organizations',
|
||||||
|
icon: 'icon-org-outlined',
|
||||||
|
selectedIcon: 'icon-org-filled',
|
||||||
|
defaultIcon: 'icon-org-outlined',
|
||||||
|
// OSS exposes the menu to platform admins as a teaser for the
|
||||||
|
// enterprise multi-tenancy module. The page itself just renders
|
||||||
|
// an upsell notice — the real CRUD UI lives in the enterprise
|
||||||
|
// plugin and shadows this route via `routes.extensions.ts`.
|
||||||
|
access: 'canSeeAdmin',
|
||||||
|
component: './organizations'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'users',
|
name: 'users',
|
||||||
path: '/access-control/users',
|
path: '/access-control/users',
|
||||||
@@ -380,8 +411,8 @@ const baseRoutes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'profile',
|
name: 'profile',
|
||||||
path: '/profile',
|
path: '/preferences',
|
||||||
key: 'profile',
|
key: 'preferences',
|
||||||
hideInMenu: true,
|
hideInMenu: true,
|
||||||
component: './profile',
|
component: './profile',
|
||||||
icon: 'User'
|
icon: 'User'
|
||||||
|
|||||||
+22
-8
@@ -1,13 +1,27 @@
|
|||||||
const child_process = require('child_process');
|
import { execSync } from 'child_process';
|
||||||
|
|
||||||
export const getBranchInfo = () => {
|
export const getBranchInfo = () => {
|
||||||
const latestCommit = child_process
|
// git may be absent (source archive, bare container) or this tree may
|
||||||
.execSync('git rev-parse HEAD')
|
// not be a git checkout. Swallow the failure and fall back to the env
|
||||||
|
// overrides below — losing build info shouldn't fail the build.
|
||||||
|
let latestCommit = '';
|
||||||
|
let versionTag = '';
|
||||||
|
try {
|
||||||
|
latestCommit = execSync('git rev-parse HEAD').toString().trim();
|
||||||
|
versionTag = execSync(`git tag --contains ${latestCommit}`)
|
||||||
.toString()
|
.toString()
|
||||||
.trim();
|
.trim();
|
||||||
const versionTag = child_process
|
} catch {
|
||||||
.execSync(`git tag --contains ${latestCommit}`)
|
// Not a git checkout / git unavailable; rely on env overrides.
|
||||||
.toString()
|
}
|
||||||
.trim();
|
// Respect explicit GPUSTACK_UI_* overrides so a wrapping build that
|
||||||
return { version: versionTag || '', commitId: latestCommit.slice(0, 7) };
|
// checks this source tree out as a sub-package can stamp its own
|
||||||
|
// release tag and commit id onto the UI (otherwise the panel reports
|
||||||
|
// the host tree's git HEAD, which the wrapper doesn't control).
|
||||||
|
const overrideVersion = process.env.GPUSTACK_UI_VERSION?.trim();
|
||||||
|
const overrideCommitId = process.env.GPUSTACK_UI_COMMIT_ID?.trim();
|
||||||
|
return {
|
||||||
|
version: overrideVersion || versionTag || '',
|
||||||
|
commitId: overrideCommitId || latestCommit.slice(0, 7)
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@
|
|||||||
"@ant-design/pro-components": "3.1.0-0",
|
"@ant-design/pro-components": "3.1.0-0",
|
||||||
"@antv/g6": "^5.0.51",
|
"@antv/g6": "^5.0.51",
|
||||||
"@braintree/sanitize-url": "^7.1.1",
|
"@braintree/sanitize-url": "^7.1.1",
|
||||||
"@gpustack/core-ui": "^1.0.19",
|
"@gpustack/core-ui": "^1.0.27",
|
||||||
"@huggingface/gguf": "^0.1.7",
|
"@huggingface/gguf": "^0.1.7",
|
||||||
"@huggingface/hub": "^0.15.1",
|
"@huggingface/hub": "^0.15.1",
|
||||||
"@huggingface/tasks": "^0.11.6",
|
"@huggingface/tasks": "^0.11.6",
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ export default (api: IApi) => {
|
|||||||
const info = JSON.parse(process.env.VERSION || '{}');
|
const info = JSON.parse(process.env.VERSION || '{}');
|
||||||
const env = process.env.NODE_ENV;
|
const env = process.env.NODE_ENV;
|
||||||
|
|
||||||
|
$('html').attr('lang', 'en');
|
||||||
|
|
||||||
$('html').attr('data-env', env);
|
$('html').attr('data-env', env);
|
||||||
|
|
||||||
$('html').attr(
|
$('html').attr(
|
||||||
|
|||||||
Generated
+5
-5
@@ -24,8 +24,8 @@ importers:
|
|||||||
specifier: ^7.1.1
|
specifier: ^7.1.1
|
||||||
version: 7.1.2
|
version: 7.1.2
|
||||||
'@gpustack/core-ui':
|
'@gpustack/core-ui':
|
||||||
specifier: ^1.0.19
|
specifier: ^1.0.27
|
||||||
version: 1.0.19(czdvzceysqw7iv6pct2ucnb23e)
|
version: 1.0.27(czdvzceysqw7iv6pct2ucnb23e)
|
||||||
'@huggingface/gguf':
|
'@huggingface/gguf':
|
||||||
specifier: ^0.1.7
|
specifier: ^0.1.7
|
||||||
version: 0.1.18
|
version: 0.1.18
|
||||||
@@ -1484,8 +1484,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ==, tarball: https://registry.npmjs.org/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz}
|
resolution: {integrity: sha512-KWk80UPIzPmUg+P0rKh6TqspRw0G6eux1PuJr+zz47ftMaZ9QDwbGzHZbtzWkl5hgayM/qrKRutllRC7D/vVXQ==, tarball: https://registry.npmjs.org/@formatjs/intl-utils/-/intl-utils-2.3.0.tgz}
|
||||||
deprecated: the package is rather renamed to @formatjs/ecma-abstract with some changes in functionality (primarily selectUnit is removed and we don't plan to make any further changes to this package
|
deprecated: the package is rather renamed to @formatjs/ecma-abstract with some changes in functionality (primarily selectUnit is removed and we don't plan to make any further changes to this package
|
||||||
|
|
||||||
'@gpustack/core-ui@1.0.19':
|
'@gpustack/core-ui@1.0.27':
|
||||||
resolution: {integrity: sha512-EI2inYoTDdWYdxXXm0BM2DQ7chysnbIXICivBMeUxxhc8IkEcwaQfNqo/KUtA/bkqjDieRxFC1JlsDIIhbcL3A==, tarball: https://registry.npmjs.org/@gpustack/core-ui/-/core-ui-1.0.19.tgz}
|
resolution: {integrity: sha512-m3ue0EHFKULla0mpnpxZwn9LVaGKS+HnuzQYSBECQa4vaP8MEEQsR7oFBtG8bhWUWYA9VzP6k7fjozDkP6TymA==, tarball: https://registry.npmjs.org/@gpustack/core-ui/-/core-ui-1.0.27.tgz}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@ant-design/icons': '>=6.0.0'
|
'@ant-design/icons': '>=6.0.0'
|
||||||
'@ant-design/pro-components': 3.1.0-0
|
'@ant-design/pro-components': 3.1.0-0
|
||||||
@@ -10808,7 +10808,7 @@ snapshots:
|
|||||||
|
|
||||||
'@formatjs/intl-utils@2.3.0': {}
|
'@formatjs/intl-utils@2.3.0': {}
|
||||||
|
|
||||||
'@gpustack/core-ui@1.0.19(czdvzceysqw7iv6pct2ucnb23e)':
|
'@gpustack/core-ui@1.0.27(czdvzceysqw7iv6pct2ucnb23e)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@ant-design/icons': 6.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@ant-design/icons': 6.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@ant-design/pro-components': 3.1.0-0(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@ant-design/pro-components': 3.1.0-0(antd@6.3.7(date-fns@2.30.0)(moment@2.30.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
|||||||
+7
-1
@@ -3,6 +3,7 @@ import { applyAccessExtensions } from './access.extensions';
|
|||||||
export default (initialState: {
|
export default (initialState: {
|
||||||
currentUser?: Global.UserInfo;
|
currentUser?: Global.UserInfo;
|
||||||
hasKubernetesCluster?: boolean;
|
hasKubernetesCluster?: boolean;
|
||||||
|
hasResourceEvents?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const isPlatformAdmin = !!(
|
const isPlatformAdmin = !!(
|
||||||
initialState &&
|
initialState &&
|
||||||
@@ -20,6 +21,10 @@ export default (initialState: {
|
|||||||
// role-based default so a transient network blip can't lock anyone
|
// role-based default so a transient network blip can't lock anyone
|
||||||
// out of the menu.
|
// out of the menu.
|
||||||
const hasKubernetesCluster = initialState?.hasKubernetesCluster;
|
const hasKubernetesCluster = initialState?.hasKubernetesCluster;
|
||||||
|
// Having run GPU/CPU instances or storage (any resource_events) also unlocks
|
||||||
|
// GPU Service / the full Usage page — a user who used it keeps seeing it even
|
||||||
|
// without a current cluster. MaaS-only users (no cluster, no events) don't.
|
||||||
|
const hasResourceEvents = !!initialState?.hasResourceEvents;
|
||||||
|
|
||||||
// Predicate roles, top-down by strictness:
|
// Predicate roles, top-down by strictness:
|
||||||
// * `canSeeAdmin` — strictly platform admin (`users.is_admin`).
|
// * `canSeeAdmin` — strictly platform admin (`users.is_admin`).
|
||||||
@@ -41,7 +46,8 @@ export default (initialState: {
|
|||||||
return applyAccessExtensions({
|
return applyAccessExtensions({
|
||||||
canSeeAdmin: isPlatformAdmin,
|
canSeeAdmin: isPlatformAdmin,
|
||||||
canSeeOrgAdmin: isPlatformAdmin,
|
canSeeOrgAdmin: isPlatformAdmin,
|
||||||
canSeeGpuService: isPlatformAdmin || hasKubernetesCluster !== false,
|
canSeeGpuService:
|
||||||
|
isPlatformAdmin || hasKubernetesCluster !== false || hasResourceEvents,
|
||||||
canManageCurrentOrg: false,
|
canManageCurrentOrg: false,
|
||||||
canSeeUser,
|
canSeeUser,
|
||||||
canDelete: true,
|
canDelete: true,
|
||||||
|
|||||||
+85
-5
@@ -1,10 +1,12 @@
|
|||||||
import { userSettingsHelperAtom } from '@/atoms/settings';
|
import { userSettingsHelperAtom } from '@/atoms/settings';
|
||||||
import { GPUStackVersionAtom, UpdateCheckAtom } from '@/atoms/user';
|
import { GPUStackVersionAtom, UpdateCheckAtom, userAtom } from '@/atoms/user';
|
||||||
import { setAtomStorage } from '@/atoms/utils';
|
import { setAtomStorage } from '@/atoms/utils';
|
||||||
import { DEFAULT_ENTER_PAGE, GPUSTACK_API_BASE_URL } from '@/config/settings';
|
import { DEFAULT_ENTER_PAGE, GPUSTACK_API_BASE_URL } from '@/config/settings';
|
||||||
import { COLOR_PRIMARY } from '@/config/theme/constants';
|
import { COLOR_PRIMARY } from '@/config/theme/constants';
|
||||||
import { queryClusterList } from '@/pages/cluster-management/apis';
|
import { queryClusterList } from '@/pages/cluster-management/apis';
|
||||||
import { ProviderValueMap } from '@/pages/cluster-management/config';
|
import { ProviderValueMap } from '@/pages/cluster-management/config';
|
||||||
|
import { queryResourceEvents } from '@/pages/usage/apis/resource';
|
||||||
|
import { getGPUStackPlugin } from '@/plugins';
|
||||||
import { enterprisePluginReady } from '@/plugins/enterprise-ready';
|
import { enterprisePluginReady } from '@/plugins/enterprise-ready';
|
||||||
import { GPUStackPluginManager } from '@/plugins/manager';
|
import { GPUStackPluginManager } from '@/plugins/manager';
|
||||||
import { requestConfig } from '@/request-config';
|
import { requestConfig } from '@/request-config';
|
||||||
@@ -15,6 +17,7 @@ import {
|
|||||||
} from '@/services/profile/apis';
|
} from '@/services/profile/apis';
|
||||||
import { fetchSystemConfig } from '@/services/system/query-system-config';
|
import { fetchSystemConfig } from '@/services/system/query-system-config';
|
||||||
import { isOnline } from '@/utils';
|
import { isOnline } from '@/utils';
|
||||||
|
import { installTenantFetch } from '@/utils/install-fetch';
|
||||||
import {
|
import {
|
||||||
IS_FIRST_LOGIN,
|
IS_FIRST_LOGIN,
|
||||||
readState,
|
readState,
|
||||||
@@ -24,6 +27,8 @@ import '@gpustack/core-ui/style.css';
|
|||||||
import { RequestConfig, history, request as umiRequest } from '@umijs/max';
|
import { RequestConfig, history, request as umiRequest } from '@umijs/max';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
|
|
||||||
|
installTenantFetch();
|
||||||
|
|
||||||
// only for the first login and access from http://localhost
|
// only for the first login and access from http://localhost
|
||||||
|
|
||||||
const checkDefaultPage = async (userInfo: any) => {
|
const checkDefaultPage = async (userInfo: any) => {
|
||||||
@@ -47,7 +52,12 @@ const checkDefaultPage = async (userInfo: any) => {
|
|||||||
const HAS_K8S_CLUSTER_KEY = 'hasKubernetesCluster';
|
const HAS_K8S_CLUSTER_KEY = 'hasKubernetesCluster';
|
||||||
const probeHasKubernetesCluster = async (): Promise<boolean | undefined> => {
|
const probeHasKubernetesCluster = async (): Promise<boolean | undefined> => {
|
||||||
try {
|
try {
|
||||||
const res = await queryClusterList({ page: -1 });
|
const res = await queryClusterList(
|
||||||
|
{ page: -1 },
|
||||||
|
{
|
||||||
|
skipErrorHandler: true
|
||||||
|
}
|
||||||
|
);
|
||||||
const value = (res?.items ?? []).some(
|
const value = (res?.items ?? []).some(
|
||||||
(c) => c?.provider === ProviderValueMap.Kubernetes
|
(c) => c?.provider === ProviderValueMap.Kubernetes
|
||||||
);
|
);
|
||||||
@@ -69,12 +79,49 @@ const probeHasKubernetesCluster = async (): Promise<boolean | undefined> => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Probes whether the caller has ANY resource-usage events (GPU/CPU instance or
|
||||||
|
// storage lifecycle). Used alongside the cluster probe so a user who has run
|
||||||
|
// GPU instances still sees GPU Service / the full Usage page even if they
|
||||||
|
// currently have no Kubernetes cluster. Mirrored into sessionStorage for the
|
||||||
|
// access extensions; any failure → undefined ("unknown — don't restrict").
|
||||||
|
const HAS_RESOURCE_EVENTS_KEY = 'hasResourceEvents';
|
||||||
|
const probeHasResourceEvents = async (): Promise<boolean | undefined> => {
|
||||||
|
try {
|
||||||
|
// No date range = "ever"; scope is clamped to the caller server-side.
|
||||||
|
const res = await queryResourceEvents(
|
||||||
|
{ perPage: 1 },
|
||||||
|
{
|
||||||
|
skipErrorHandler: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const value = (res?.pagination?.total ?? 0) > 0;
|
||||||
|
try {
|
||||||
|
window.sessionStorage.setItem(
|
||||||
|
HAS_RESOURCE_EVENTS_KEY,
|
||||||
|
JSON.stringify(value)
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// sessionStorage may be unavailable; predicate treats missing as unknown.
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('probeHasResourceEvents error', error);
|
||||||
|
try {
|
||||||
|
window.sessionStorage.removeItem(HAS_RESOURCE_EVENTS_KEY);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// runtime configuration
|
// runtime configuration
|
||||||
export async function getInitialState(): Promise<{
|
export async function getInitialState(): Promise<{
|
||||||
fetchUserInfo: () => Promise<Global.UserInfo>;
|
fetchUserInfo: () => Promise<Global.UserInfo>;
|
||||||
currentUser?: Global.UserInfo;
|
currentUser?: Global.UserInfo;
|
||||||
pluginData?: Record<string, any>;
|
pluginData?: Record<string, any>;
|
||||||
hasKubernetesCluster?: boolean;
|
hasKubernetesCluster?: boolean;
|
||||||
|
hasResourceEvents?: boolean;
|
||||||
}> {
|
}> {
|
||||||
const { location } = history;
|
const { location } = history;
|
||||||
|
|
||||||
@@ -119,6 +166,36 @@ export async function getInitialState(): Promise<{
|
|||||||
getUpdateCheck();
|
getUpdateCheck();
|
||||||
fetchSystemConfig();
|
fetchSystemConfig();
|
||||||
}
|
}
|
||||||
|
// Only commit a substantive user object. A truthy-but-empty
|
||||||
|
// `data` (e.g. server responded 200 with an empty body) would
|
||||||
|
// otherwise look like "logged in" to every `currentUser`
|
||||||
|
// reader and the access seam — break out instead and let the
|
||||||
|
// caller treat the request as failed.
|
||||||
|
if (data && typeof data === 'object' && Object.keys(data).length > 0) {
|
||||||
|
// Commit the identity to atom storage (and so to localStorage)
|
||||||
|
// before returning. The access function — memoized on
|
||||||
|
// `initialState` and run once per commit — reads identity from
|
||||||
|
// localStorage; without this preemptive write the predicate
|
||||||
|
// sees the prior session's identity on its first evaluation
|
||||||
|
// after login, and stays stale until the next identity change
|
||||||
|
// (which usually doesn't come without a manual refresh).
|
||||||
|
try {
|
||||||
|
setAtomStorage(userAtom, data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('userAtom commit error:', err);
|
||||||
|
}
|
||||||
|
// Fire `onUserFetched` so plugins maintaining identity-scoped
|
||||||
|
// caches can seed them under the new identity before any
|
||||||
|
// caller commits this user to `initialState`. Errors here are
|
||||||
|
// swallowed and logged — fetchUserInfo must still return.
|
||||||
|
try {
|
||||||
|
await getGPUStackPlugin()?.login?.onUserFetched?.(data, {
|
||||||
|
request: umiRequest
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('onUserFetched plugin hook error:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
return data;
|
return data;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const data = error?.response?.data;
|
const data = error?.response?.data;
|
||||||
@@ -158,16 +235,19 @@ export async function getInitialState(): Promise<{
|
|||||||
getAppVersionInfo();
|
getAppVersionInfo();
|
||||||
|
|
||||||
if (![DEFAULT_ENTER_PAGE.login].includes(location.pathname)) {
|
if (![DEFAULT_ENTER_PAGE.login].includes(location.pathname)) {
|
||||||
const [userInfo, hasKubernetesCluster] = await Promise.all([
|
const [userInfo, hasKubernetesCluster, hasResourceEvents] =
|
||||||
|
await Promise.all([
|
||||||
fetchUserInfo(),
|
fetchUserInfo(),
|
||||||
probeHasKubernetesCluster()
|
probeHasKubernetesCluster(),
|
||||||
|
probeHasResourceEvents()
|
||||||
]);
|
]);
|
||||||
checkDefaultPage(userInfo);
|
checkDefaultPage(userInfo);
|
||||||
return {
|
return {
|
||||||
fetchUserInfo,
|
fetchUserInfo,
|
||||||
currentUser: userInfo,
|
currentUser: userInfo,
|
||||||
pluginData,
|
pluginData,
|
||||||
hasKubernetesCluster
|
hasKubernetesCluster,
|
||||||
|
hasResourceEvents
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
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 |
@@ -3,6 +3,7 @@
|
|||||||
.ant-layout-sider-children {
|
.ant-layout-sider-children {
|
||||||
border-inline: none;
|
border-inline: none;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
padding-inline-end: 0;
|
||||||
padding-block-end: 8px;
|
padding-block-end: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ export const fromClusterCreationAtom = atom(false);
|
|||||||
export const clusterSessionAtom = atom<{
|
export const clusterSessionAtom = atom<{
|
||||||
firstAddWorker: boolean;
|
firstAddWorker: boolean;
|
||||||
firstAddCluster: boolean;
|
firstAddCluster: boolean;
|
||||||
|
presetClusterType?: 'model' | 'gpu';
|
||||||
// Provider to preselect when the create flow opens — set by the
|
// Provider to preselect when the create flow opens — set by the
|
||||||
// empty-state CTA on feature pages that need a specific provider
|
// empty-state CTA on feature pages that need a specific provider
|
||||||
// (e.g. GPU Service can only schedule on Kubernetes, so its
|
// (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 hideModalTemporarilyAtom = atom<boolean>(false);
|
||||||
|
|
||||||
|
export const collapsedMenuGroupsAtom = atomWithStorage<string[]>(
|
||||||
|
'collapsedMenuGroups',
|
||||||
|
[],
|
||||||
|
undefined,
|
||||||
|
{ getOnInit: true }
|
||||||
|
);
|
||||||
|
|||||||
+39
-23
@@ -32,10 +32,7 @@ export const UpdateCheckAtom = atom<{
|
|||||||
latest_version: ''
|
latest_version: ''
|
||||||
});
|
});
|
||||||
|
|
||||||
export const initialPasswordAtom = atomWithStorage<string>(
|
export const initialPasswordAtom = atom<string>('');
|
||||||
'initialPassword',
|
|
||||||
''
|
|
||||||
);
|
|
||||||
|
|
||||||
// Namespace the server creates for an Org's resources on each Kubernetes
|
// Namespace the server creates for an Org's resources on each Kubernetes
|
||||||
// cluster. The format must match the backend's ``get_namespace_name``
|
// cluster. The format must match the backend's ``get_namespace_name``
|
||||||
@@ -90,20 +87,33 @@ const getStoredCurrentOrgId = (): number | null => {
|
|||||||
// cluster-owner fallback.
|
// cluster-owner fallback.
|
||||||
const ORG_CACHE_KEYS = ['organizationList', 'allOrganizations'] as const;
|
const ORG_CACHE_KEYS = ['organizationList', 'allOrganizations'] as const;
|
||||||
|
|
||||||
const lookupOrgNamespace = (id: number | null): string | null => {
|
export interface CachedOrg {
|
||||||
|
id: number;
|
||||||
|
name?: string;
|
||||||
|
// The platform Org (single global tenant). Its models are NOT
|
||||||
|
// namespaced in ``/v1/models`` — they appear under their bare name.
|
||||||
|
is_platform?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve the cached Org record for an owner/principal id by scanning both
|
||||||
|
// org caches. ``organizationList`` (the caller's member orgs) is checked
|
||||||
|
// alongside the admin-only ``allOrganizations`` so member sessions resolve
|
||||||
|
// too. Id types vary between localStorage payloads (some writers stringify,
|
||||||
|
// others persist as a JSON number), so compare as strings — strict equality
|
||||||
|
// would silently miss those cases.
|
||||||
|
export const getOrgById = (
|
||||||
|
id: number | string | null | undefined
|
||||||
|
): CachedOrg | null => {
|
||||||
if (id == null) return null;
|
if (id == null) return null;
|
||||||
// Normalise both sides to strings — the stored id type varies between
|
|
||||||
// localStorage payloads (some writers stringify, others persist as a
|
|
||||||
// JSON number); strict equality would silently miss those cases.
|
|
||||||
const target = String(id);
|
const target = String(id);
|
||||||
for (const key of ORG_CACHE_KEYS) {
|
for (const key of ORG_CACHE_KEYS) {
|
||||||
try {
|
try {
|
||||||
const raw = localStorage.getItem(key);
|
const raw = localStorage.getItem(key);
|
||||||
if (!raw) continue;
|
if (!raw) continue;
|
||||||
const list = JSON.parse(raw) as Array<{ id: number; name?: string }>;
|
const list = JSON.parse(raw) as CachedOrg[];
|
||||||
if (!Array.isArray(list)) continue;
|
if (!Array.isArray(list)) continue;
|
||||||
const match = list.find((item) => String(item?.id) === target);
|
const match = list.find((item) => String(item?.id) === target);
|
||||||
if (match?.name) return `gpustack-${match.name}`;
|
if (match) return match;
|
||||||
} catch {
|
} catch {
|
||||||
// ignore malformed cache; continue checking other keys
|
// ignore malformed cache; continue checking other keys
|
||||||
}
|
}
|
||||||
@@ -111,17 +121,23 @@ const lookupOrgNamespace = (id: number | null): string | null => {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAllOrganizations = (): Array<{ id: number; name?: string }> => {
|
// Bare Org *name* (e.g. ``org1``) for an owner/principal id, or null.
|
||||||
const allOrganizations = localStorage.getItem('allOrganizations');
|
export const getOrgNameById = (
|
||||||
if (!allOrganizations) return [];
|
id: number | string | null | undefined
|
||||||
try {
|
): string | null => {
|
||||||
const list = JSON.parse(allOrganizations) as Array<{
|
return getOrgById(id)?.name ?? null;
|
||||||
id: number;
|
};
|
||||||
name?: string;
|
|
||||||
}>;
|
const lookupOrgNamespace = (id: number | null): string | null => {
|
||||||
if (!Array.isArray(list)) return [];
|
const name = getOrgNameById(id);
|
||||||
return list;
|
return name ? `gpustack-${name}` : null;
|
||||||
} catch {
|
};
|
||||||
return [];
|
|
||||||
}
|
// The Org the caller is currently acting under, or null in the admin-"All"
|
||||||
|
// context. The org-switcher reloads the page on switch, so the list pages
|
||||||
|
// always show this org's resources — which is how ``/v1/models`` namespaces
|
||||||
|
// their model ids (``{org}/{name}``). Callers reconstructing that id use this
|
||||||
|
// as the fallback owner when a row carries no explicit ``owner_principal_id``.
|
||||||
|
export const getCurrentOrg = (): CachedOrg | null => {
|
||||||
|
return getOrgById(getStoredCurrentOrgId());
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: "iconfont"; /* Project id 4613488 */
|
font-family: "iconfont"; /* Project id 4613488 */
|
||||||
src: url('iconfont.woff2?t=1780035199453') format('woff2'),
|
src: url('iconfont.woff2?t=1780907696624') format('woff2'),
|
||||||
url('iconfont.woff?t=1780035199453') format('woff'),
|
url('iconfont.woff?t=1780907696624') format('woff'),
|
||||||
url('iconfont.ttf?t=1780035199453') format('truetype');
|
url('iconfont.ttf?t=1780907696624') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@@ -13,6 +13,18 @@
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-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 {
|
.icon-database-outlined:before {
|
||||||
content: "\e70a";
|
content: "\e70a";
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -5,6 +5,27 @@
|
|||||||
"css_prefix_text": "icon-",
|
"css_prefix_text": "icon-",
|
||||||
"description": "",
|
"description": "",
|
||||||
"glyphs": [
|
"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",
|
"icon_id": "47654402",
|
||||||
"name": "database_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz24 (1",
|
"name": "database_24dp_1F1F1F_FILL0_wght300_GRAD0_opsz24 (1",
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+15
-36
@@ -171,8 +171,6 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-table .ant-table-container table {
|
.ant-table .ant-table-container table {
|
||||||
// border-spacing: 0 20px;
|
|
||||||
|
|
||||||
.ant-table-thead th.ant-table-column-sort {
|
.ant-table-thead th.ant-table-column-sort {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
|
||||||
@@ -241,8 +239,8 @@ body {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
||||||
.ant-pro-sider-footer {
|
.ant-pro-sider-footer {
|
||||||
padding-block: 2px 0;
|
padding-block: 4px 0;
|
||||||
padding-left: 6px;
|
padding-left: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-pro-sider-actions-list-item {
|
.ant-pro-sider-actions-list-item {
|
||||||
@@ -259,49 +257,29 @@ body {
|
|||||||
border-block-end: none;
|
border-block-end: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-pro-sider-logo {
|
||||||
|
.collapse-btn {
|
||||||
|
color: var(--ant-color-text-tertiary);
|
||||||
|
:hover {
|
||||||
|
color: var(--ant-color-text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ant-pro-sider-logo-collapsed {
|
.ant-pro-sider-logo-collapsed {
|
||||||
padding-left: 12px;
|
padding-left: 6px;
|
||||||
cursor: e-resize;
|
|
||||||
|
|
||||||
.collapse-wrap {
|
|
||||||
display: none;
|
|
||||||
position: absolute;
|
|
||||||
top: 12px;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
height: 48px;
|
|
||||||
width: 64px;
|
|
||||||
top: -16px;
|
|
||||||
left: -16px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
.ant-table .ant-table-tbody {
|
||||||
.collapse-wrap {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-pro-sider .ant-layout-sider-children {
|
|
||||||
// border-right: 1px solid var(--ant-color-split);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-table-content table {
|
|
||||||
.ant-table-tbody {
|
|
||||||
.ant-table-row {
|
.ant-table-row {
|
||||||
border-radius: var(--table-td-radius);
|
border-radius: var(--table-td-radius);
|
||||||
|
|
||||||
> td {
|
> td {
|
||||||
background-color: unset;
|
|
||||||
border-bottom: 1px solid var(--ant-color-split);
|
border-bottom: 1px solid var(--ant-color-split);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// ============== new theme style end =================
|
// ============== new theme style end =================
|
||||||
}
|
}
|
||||||
@@ -347,7 +325,6 @@ body {
|
|||||||
.ant-pro-layout-container {
|
.ant-pro-layout-container {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
// background-color: var(--ant-color-bg-container);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-pro-sider {
|
.ant-pro-sider {
|
||||||
@@ -812,6 +789,8 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ant-pro-sider-logo + div {
|
.ant-pro-sider-logo + div {
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ export function useQueryDataList<
|
|||||||
Response = Array<ListItem>
|
Response = Array<ListItem>
|
||||||
>(option: {
|
>(option: {
|
||||||
key: string;
|
key: string;
|
||||||
|
manual?: boolean;
|
||||||
responseType?: 'array' | 'object';
|
responseType?: 'array' | 'object';
|
||||||
fetchList: (
|
fetchList: (
|
||||||
params: Params,
|
params: Params,
|
||||||
@@ -38,6 +39,7 @@ export function useQueryDataList<
|
|||||||
fetchList,
|
fetchList,
|
||||||
getLabel,
|
getLabel,
|
||||||
getValue,
|
getValue,
|
||||||
|
manual = true,
|
||||||
responseType = 'array',
|
responseType = 'array',
|
||||||
errorMsg
|
errorMsg
|
||||||
} = option;
|
} = option;
|
||||||
@@ -70,7 +72,7 @@ export function useQueryDataList<
|
|||||||
return responseType === 'array' ? res.items || [] : res;
|
return responseType === 'array' ? res.items || [] : res;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
manual: true,
|
manual: manual,
|
||||||
debounceWait: option.debounceWait || 300,
|
debounceWait: option.debounceWait || 300,
|
||||||
onSuccess: () => {},
|
onSuccess: () => {},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
@@ -105,16 +107,18 @@ export function useQueryDataList<
|
|||||||
export function useQueryData<Detail, Params = any>(option: {
|
export function useQueryData<Detail, Params = any>(option: {
|
||||||
key: string;
|
key: string;
|
||||||
delay?: number;
|
delay?: number;
|
||||||
|
manual?: boolean;
|
||||||
fetchDetail: (params: Params, options?: any) => Promise<Detail>;
|
fetchDetail: (params: Params, options?: any) => Promise<Detail>;
|
||||||
getData?: (response: Detail, params?: any) => any;
|
getData?: (response: Detail, params?: any) => any;
|
||||||
errorMsg?: string;
|
errorMsg?: string;
|
||||||
}): {
|
}): {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
detailData: Detail;
|
detailData: Detail;
|
||||||
|
manual?: boolean;
|
||||||
cancelRequest: () => void;
|
cancelRequest: () => void;
|
||||||
fetchData: (params: Params, extra?: any) => Promise<Detail>;
|
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 axiosTokenRef = useRef<CancelTokenSource | null>(null);
|
||||||
const [detailData, setDetailData] = useState<Detail>({} as Detail);
|
const [detailData, setDetailData] = useState<Detail>({} as Detail);
|
||||||
|
|
||||||
@@ -142,7 +146,7 @@ export function useQueryData<Detail, Params = any>(option: {
|
|||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
manual: true,
|
manual: manual,
|
||||||
onSuccess: () => {},
|
onSuccess: () => {},
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
message.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 };
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ export default function useTableFetch<T>(
|
|||||||
key?: (typeof PaginationKey)[keyof typeof PaginationKey];
|
key?: (typeof PaginationKey)[keyof typeof PaginationKey];
|
||||||
fetchAPI: (params: any, options?: any) => Promise<Global.PageResponse<T>>;
|
fetchAPI: (params: any, options?: any) => Promise<Global.PageResponse<T>>;
|
||||||
deleteAPI?: (id: number, params?: any) => Promise<any>;
|
deleteAPI?: (id: number, params?: any) => Promise<any>;
|
||||||
|
afterDelete?: (id?: number | number[]) => void;
|
||||||
contentForDelete?: string;
|
contentForDelete?: string;
|
||||||
defaultData?: any[];
|
defaultData?: any[];
|
||||||
events?: EventsType[];
|
events?: EventsType[];
|
||||||
@@ -48,6 +49,7 @@ export default function useTableFetch<T>(
|
|||||||
const {
|
const {
|
||||||
fetchAPI,
|
fetchAPI,
|
||||||
deleteAPI,
|
deleteAPI,
|
||||||
|
afterDelete,
|
||||||
contentForDelete,
|
contentForDelete,
|
||||||
API,
|
API,
|
||||||
polling = false,
|
polling = false,
|
||||||
@@ -363,7 +365,7 @@ export default function useTableFetch<T>(
|
|||||||
|
|
||||||
// remove the deleted id from selected ids in row selection
|
// remove the deleted id from selected ids in row selection
|
||||||
rowSelection.removeSelectedKeys([row.id]);
|
rowSelection.removeSelectedKeys([row.id]);
|
||||||
|
afterDelete?.(row.id);
|
||||||
// ======== to avoid fetch data twice, because of debounceFetchData has been run =======
|
// ======== to avoid fetch data twice, because of debounceFetchData has been run =======
|
||||||
if (!updateManually) {
|
if (!updateManually) {
|
||||||
fetchData();
|
fetchData();
|
||||||
@@ -390,6 +392,7 @@ export default function useTableFetch<T>(
|
|||||||
successIds.push(id);
|
successIds.push(id);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
afterDelete?.(successIds);
|
||||||
rowSelection.removeSelectedKeys(successIds);
|
rowSelection.removeSelectedKeys(successIds);
|
||||||
fetchData();
|
fetchData();
|
||||||
return res;
|
return res;
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ type EventsType = 'CREATE' | 'UPDATE' | 'DELETE' | 'INSERT';
|
|||||||
export function useUpdateChunkedList(options: {
|
export function useUpdateChunkedList(options: {
|
||||||
events?: EventsType[];
|
events?: EventsType[];
|
||||||
dataList?: any[];
|
dataList?: any[];
|
||||||
triggerAt?: React.MutableRefObject<number>;
|
|
||||||
limit?: number;
|
limit?: number;
|
||||||
onCreate?: (args: any) => void;
|
onCreate?: (args: any) => void;
|
||||||
onUpdate?: (args: any) => void;
|
onUpdate?: (args: any) => void;
|
||||||
@@ -24,9 +23,9 @@ export function useUpdateChunkedList(options: {
|
|||||||
filterFun?: (args: any) => boolean;
|
filterFun?: (args: any) => boolean;
|
||||||
mapFun?: (args: any) => any;
|
mapFun?: (args: any) => any;
|
||||||
computedID?: (d: object) => string;
|
computedID?: (d: object) => string;
|
||||||
|
isNewItem?: (item: any) => boolean;
|
||||||
}) {
|
}) {
|
||||||
const { events = ['CREATE', 'DELETE', 'UPDATE', 'INSERT'], triggerAt } =
|
const { events = ['CREATE', 'DELETE', 'UPDATE', 'INSERT'] } = options;
|
||||||
options;
|
|
||||||
const deletedIdsRef = useRef<Set<number | string>>(new Set());
|
const deletedIdsRef = useRef<Set<number | string>>(new Set());
|
||||||
const cacheDataListRef = useRef<any[]>(options.dataList || []);
|
const cacheDataListRef = useRef<any[]>(options.dataList || []);
|
||||||
const timerRef = useRef<any>(null);
|
const timerRef = useRef<any>(null);
|
||||||
@@ -71,17 +70,14 @@ export function useUpdateChunkedList(options: {
|
|||||||
(sItem: any) => sItem.id === item.id
|
(sItem: any) => sItem.id === item.id
|
||||||
);
|
);
|
||||||
const updateItem = { ...item };
|
const updateItem = { ...item };
|
||||||
if (updateIndex === -1 && !triggerAt?.current) {
|
if (updateIndex === -1) {
|
||||||
acc.push(updateItem);
|
acc.push(updateItem);
|
||||||
} else if (!triggerAt?.current) {
|
} else {
|
||||||
cacheDataListRef.current[updateIndex] = updateItem;
|
cacheDataListRef.current[updateIndex] = updateItem;
|
||||||
}
|
}
|
||||||
|
// only push items created after the watch started
|
||||||
|
|
||||||
// TODO: only push items created after triggerAt
|
if (options.isNewItem?.(item)) {
|
||||||
if (
|
|
||||||
triggerAt?.current &&
|
|
||||||
Date.parse(item.created_at) >= triggerAt.current
|
|
||||||
) {
|
|
||||||
latestCreateList.push(updateItem);
|
latestCreateList.push(updateItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,6 +89,10 @@ export function useUpdateChunkedList(options: {
|
|||||||
...cacheDataListRef.current
|
...cacheDataListRef.current
|
||||||
].slice(0, limit);
|
].slice(0, limit);
|
||||||
|
|
||||||
|
options.setDataList?.([...cacheDataListRef.current], {
|
||||||
|
createdIds: newDataList.map((item) => item.id)
|
||||||
|
});
|
||||||
|
|
||||||
options.onCreate?.(latestCreateList);
|
options.onCreate?.(latestCreateList);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,11 +103,9 @@ export function useUpdateChunkedList(options: {
|
|||||||
cacheDataListRef.current = cacheDataListRef.current?.filter(
|
cacheDataListRef.current = cacheDataListRef.current?.filter(
|
||||||
(item: any) => {
|
(item: any) => {
|
||||||
// collect deleted items
|
// collect deleted items
|
||||||
if (triggerAt?.current) {
|
if (ids?.includes(item.id) && !options.isNewItem?.(item)) {
|
||||||
if (ids?.includes(item.id)) {
|
|
||||||
deletedList.push(item);
|
deletedList.push(item);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return !ids?.includes(item.id);
|
return !ids?.includes(item.id);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -133,12 +131,10 @@ export function useUpdateChunkedList(options: {
|
|||||||
updateItem,
|
updateItem,
|
||||||
...cacheDataListRef.current.slice(0, limit - 1)
|
...cacheDataListRef.current.slice(0, limit - 1)
|
||||||
];
|
];
|
||||||
if (options.onUpdate && triggerAt?.current) {
|
if (options.onUpdate && options.isNewItem?.(item)) {
|
||||||
if (Date.parse(item.created_at) >= triggerAt.current) {
|
|
||||||
options.onUpdate?.([updateItem]);
|
options.onUpdate?.([updateItem]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,8 +63,7 @@ export default function useWatchList<T = Record<string, any>>(API: string) {
|
|||||||
listRequestTokenRef.current?.cancel?.();
|
listRequestTokenRef.current?.cancel?.();
|
||||||
listRequestTokenRef.current = createAxiosToken();
|
listRequestTokenRef.current = createAxiosToken();
|
||||||
const params = {
|
const params = {
|
||||||
page: 1,
|
page: -1
|
||||||
perPage: 100
|
|
||||||
};
|
};
|
||||||
const res: any = await queryAllDataList(params, {
|
const res: any = await queryAllDataList(params, {
|
||||||
token: listRequestTokenRef.current.token
|
token: listRequestTokenRef.current.token
|
||||||
@@ -86,6 +85,7 @@ export default function useWatchList<T = Record<string, any>>(API: string) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
watchDataList,
|
watchDataList,
|
||||||
|
setWatchDataList,
|
||||||
deleteItemFromCache: handleDeleteItemFromCache
|
deleteItemFromCache: handleDeleteItemFromCache
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { useAtom } from 'jotai';
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { DEFAULT_ENTER_PAGE } from '../config/settings';
|
import { DEFAULT_ENTER_PAGE } from '../config/settings';
|
||||||
|
import GithubStar from './github-star';
|
||||||
|
|
||||||
const NewLabel = styled.span`
|
const NewLabel = styled.span`
|
||||||
position: relative;
|
position: relative;
|
||||||
@@ -210,14 +211,14 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
|
|||||||
key: 'settings',
|
key: 'settings',
|
||||||
label: (
|
label: (
|
||||||
<span className="flex flex-center">
|
<span className="flex flex-center">
|
||||||
<IconFont type="icon-settings-02" />
|
<IconFont type="icon-preferences" />
|
||||||
<span className="m-l-8" style={{ marginLeft: 8 }}>
|
<span className="m-l-8" style={{ marginLeft: 8 }}>
|
||||||
{intl?.formatMessage?.({ id: 'common.button.settings' })}
|
{intl?.formatMessage?.({ id: 'common.preferences' })}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
history.push('/profile');
|
history.push('/preferences');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -260,6 +261,7 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
|
|||||||
<Wrapper>
|
<Wrapper>
|
||||||
{contextHolder}
|
{contextHolder}
|
||||||
<PluginExtraField name="OrgSwitcher" isDarkTheme={isDarkTheme} />
|
<PluginExtraField name="OrgSwitcher" isDarkTheme={isDarkTheme} />
|
||||||
|
{process.env.ENABLE_ENTERPRISE !== 'true' && <GithubStar />}
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
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;
|
||||||
+20
-36
@@ -21,11 +21,7 @@ import {
|
|||||||
import { useAccessMarkedRoutes } from '@@/plugin-access';
|
import { useAccessMarkedRoutes } from '@@/plugin-access';
|
||||||
import { useModel } from '@@/plugin-model';
|
import { useModel } from '@@/plugin-model';
|
||||||
import { ProLayout } from '@ant-design/pro-components';
|
import { ProLayout } from '@ant-design/pro-components';
|
||||||
import {
|
import { CoreUIProvider, IconFont } from '@gpustack/core-ui';
|
||||||
CoreUIProvider,
|
|
||||||
IconFont,
|
|
||||||
useOverlayScroller
|
|
||||||
} from '@gpustack/core-ui';
|
|
||||||
import {
|
import {
|
||||||
Access,
|
Access,
|
||||||
Outlet,
|
Outlet,
|
||||||
@@ -72,9 +68,9 @@ const NO_CONTAINER_PAGES = [
|
|||||||
|
|
||||||
const CHECK_RESOURCE_PATH = [
|
const CHECK_RESOURCE_PATH = [
|
||||||
'/resources/workers',
|
'/resources/workers',
|
||||||
'/cluster-management/clusters/list',
|
'/resources/clusters/list',
|
||||||
'/cluster-management/credentials',
|
'/resources/credentials',
|
||||||
'/cluster-management/clusters/create'
|
'/resources/clusters/create'
|
||||||
];
|
];
|
||||||
|
|
||||||
type NewRoute = IRoute & {
|
type NewRoute = IRoute & {
|
||||||
@@ -135,9 +131,6 @@ const mapRoutes = (routes: IRoute[], role: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default (props: any) => {
|
export default (props: any) => {
|
||||||
const { initialize: initialize } = useOverlayScroller({
|
|
||||||
defer: false
|
|
||||||
});
|
|
||||||
const [, contextHolder] = Modal.useModal();
|
const [, contextHolder] = Modal.useModal();
|
||||||
const { themeData, setUserSettings, userSettings } = useUserSettings();
|
const { themeData, setUserSettings, userSettings } = useUserSettings();
|
||||||
const [userInfo] = useAtom(userAtom);
|
const [userInfo] = useAtom(userAtom);
|
||||||
@@ -241,30 +234,7 @@ export default (props: any) => {
|
|||||||
}, [userSettings.collapsed]);
|
}, [userSettings.collapsed]);
|
||||||
|
|
||||||
const renderMenuHeader = (logo: React.ReactNode, title: React.ReactNode) => {
|
const renderMenuHeader = (logo: React.ReactNode, title: React.ReactNode) => {
|
||||||
return (
|
return <>{logo}</>;
|
||||||
<>
|
|
||||||
{logo}
|
|
||||||
<div className="collapse-wrap" onClick={handleToggleCollapse}>
|
|
||||||
<Button
|
|
||||||
style={{
|
|
||||||
marginRight: collapsed ? 0 : -14,
|
|
||||||
border: 'none',
|
|
||||||
cursor: 'w-resize'
|
|
||||||
}}
|
|
||||||
size="small"
|
|
||||||
type={collapsed ? 'default' : 'text'}
|
|
||||||
>
|
|
||||||
<IconFont
|
|
||||||
type={collapsed ? 'icon-expand-left' : 'icon-expand-right'}
|
|
||||||
className="font-size-18 text-secondary"
|
|
||||||
style={{
|
|
||||||
display: 'block'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const menuContentRender = (menuProps: any, defaultDom: React.ReactNode) => {
|
const menuContentRender = (menuProps: any, defaultDom: React.ReactNode) => {
|
||||||
@@ -408,9 +378,23 @@ export default (props: any) => {
|
|||||||
openKeys={false}
|
openKeys={false}
|
||||||
disableMobile={true}
|
disableMobile={true}
|
||||||
siderWidth={220}
|
siderWidth={220}
|
||||||
|
menuFooterRender={() => (
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
border: 'none'
|
||||||
|
}}
|
||||||
|
size="small"
|
||||||
|
type={'text'}
|
||||||
|
onClick={handleToggleCollapse}
|
||||||
|
>
|
||||||
|
<IconFont
|
||||||
|
type={collapsed ? 'icon-expand-left' : 'icon-expand-right'}
|
||||||
|
className="font-size-18"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
onCollapse={onCollapse}
|
onCollapse={onCollapse}
|
||||||
onMenuHeaderClick={onMenuHeaderClick}
|
onMenuHeaderClick={onMenuHeaderClick}
|
||||||
menuHeaderRender={renderMenuHeader}
|
|
||||||
collapsed={userSettings.collapsed}
|
collapsed={userSettings.collapsed}
|
||||||
onPageChange={onPageChange}
|
onPageChange={onPageChange}
|
||||||
formatMessage={formatMessage}
|
formatMessage={formatMessage}
|
||||||
|
|||||||
@@ -256,7 +256,7 @@ export const getRightRenderContent = (opts: {
|
|||||||
</span>
|
</span>
|
||||||
),
|
),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
history.push('/profile');
|
history.push('/preferences');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+38
-12
@@ -1,9 +1,11 @@
|
|||||||
|
import { collapsedMenuGroupsAtom } from '@/atoms/settings';
|
||||||
import { CaretDownOutlined } from '@ant-design/icons';
|
import { CaretDownOutlined } from '@ant-design/icons';
|
||||||
import { IconFont } from '@gpustack/core-ui';
|
import { IconFont, OverlayScroller } from '@gpustack/core-ui';
|
||||||
import { Link, useLocation } from '@umijs/max';
|
import { Link, useLocation } from '@umijs/max';
|
||||||
import { Tooltip } from 'antd';
|
import { Tooltip } from 'antd';
|
||||||
import { createStyles, type FullToken } from 'antd-style';
|
import { createStyles, type FullToken } from 'antd-style';
|
||||||
import React, { useMemo, useState } from 'react';
|
import { useAtom } from 'jotai';
|
||||||
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
interface MenuItem {
|
interface MenuItem {
|
||||||
icon?: string;
|
icon?: string;
|
||||||
@@ -29,12 +31,17 @@ const useStyles = createStyles(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
siderMenu: css`
|
siderMenu: css`
|
||||||
|
width: 100%;
|
||||||
&.sider-menu-collapsed {
|
&.sider-menu-collapsed {
|
||||||
.menu-item {
|
.menu-item {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.os-scrollbar-vertical .os-scrollbar-handle {
|
||||||
|
min-width: 4px;
|
||||||
|
max-width: 4px;
|
||||||
|
}
|
||||||
`,
|
`,
|
||||||
groupTitle: css`
|
groupTitle: css`
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -139,8 +146,19 @@ const SiderMenu: React.FC<SiderMenuProps> = (props) => {
|
|||||||
const { menuData, collapsed } = props;
|
const { menuData, collapsed } = props;
|
||||||
const { styles, cx } = useStyles();
|
const { styles, cx } = useStyles();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [collapseKeys, setCollapseKeys] = useState<Set<string>>(new Set());
|
const [storedCollapsedGroups, setCollapsedGroups] = useAtom(
|
||||||
console.log('SiderMenu', location.pathname);
|
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(() => {
|
const dividerStyles = useMemo(() => {
|
||||||
if (collapsed) {
|
if (collapsed) {
|
||||||
@@ -159,14 +177,11 @@ const SiderMenu: React.FC<SiderMenuProps> = (props) => {
|
|||||||
const handleToggleGroup = (e: any, menuGroup: any) => {
|
const handleToggleGroup = (e: any, menuGroup: any) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
console.log('handleToggleGroup', menuGroup.key);
|
setCollapsedGroups(
|
||||||
|
collapsedGroups.includes(menuGroup.key)
|
||||||
if (collapseKeys.has(menuGroup.key)) {
|
? collapsedGroups.filter((key) => key !== menuGroup.key)
|
||||||
collapseKeys.delete(menuGroup.key);
|
: [...collapsedGroups, menuGroup.key]
|
||||||
} else {
|
);
|
||||||
collapseKeys.add(menuGroup.key);
|
|
||||||
}
|
|
||||||
setCollapseKeys(new Set(collapseKeys));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const menuItemRender = (menuItem: MenuItem, key: string) => {
|
const menuItemRender = (menuItem: MenuItem, key: string) => {
|
||||||
@@ -222,6 +237,15 @@ const SiderMenu: React.FC<SiderMenuProps> = (props) => {
|
|||||||
'sider-menu-collapsed': collapsed
|
'sider-menu-collapsed': collapsed
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
<OverlayScroller
|
||||||
|
styles={{
|
||||||
|
wrapper: {
|
||||||
|
paddingInline: 0,
|
||||||
|
maxHeight: '100%'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ paddingRight: 8 }}>
|
||||||
{menuData.map((item: MenuItem, index: number) => (
|
{menuData.map((item: MenuItem, index: number) => (
|
||||||
<div key={item.key}>
|
<div key={item.key}>
|
||||||
{item.children && item.children.length > 0 ? (
|
{item.children && item.children.length > 0 ? (
|
||||||
@@ -261,6 +285,8 @@ const SiderMenu: React.FC<SiderMenuProps> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
</OverlayScroller>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default {
|
|||||||
'ai.provider.ollama': 'Ollama',
|
'ai.provider.ollama': 'Ollama',
|
||||||
'ai.provider.openai': 'OpenAI',
|
'ai.provider.openai': 'OpenAI',
|
||||||
'ai.provider.openrouter': 'OpenRouter',
|
'ai.provider.openrouter': 'OpenRouter',
|
||||||
'ai.provider.qwen': 'Qwen',
|
'ai.provider.qwen': 'Alibaba Cloud Model Studio',
|
||||||
'ai.provider.spark': 'Spark',
|
'ai.provider.spark': 'Spark',
|
||||||
'ai.provider.stepfun': 'StepFun',
|
'ai.provider.stepfun': 'StepFun',
|
||||||
'ai.provider.together-ai': 'TogetherAI',
|
'ai.provider.together-ai': 'TogetherAI',
|
||||||
|
|||||||
@@ -23,5 +23,7 @@ export default {
|
|||||||
'apikeys.accessScope.inference': 'Inference APIs',
|
'apikeys.accessScope.inference': 'Inference APIs',
|
||||||
'apikeys.access.permissions': 'Access Permissions',
|
'apikeys.access.permissions': 'Access Permissions',
|
||||||
'apikeys.type.auto': 'Auto-generated',
|
'apikeys.type.auto': 'Auto-generated',
|
||||||
'apikeys.type.custom': 'Custom'
|
'apikeys.type.custom': 'Custom',
|
||||||
|
'apikeys.button.ipConfig': 'IP Access Control',
|
||||||
|
'quotaLimits.button.title': 'Quota Limit'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ export default {
|
|||||||
'backend.form.defaultExecuteCommand': 'Default Execution Command',
|
'backend.form.defaultExecuteCommand': 'Default Execution Command',
|
||||||
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' and '{{'model_name'}}' are placeholders that will be substituted with the actual values during deployment.`,
|
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' and '{{'model_name'}}' are placeholders that will be substituted with the actual values during deployment.`,
|
||||||
'backend.form.defaultBackendParameters': 'Default Backend Parameters',
|
'backend.form.defaultBackendParameters': 'Default Backend Parameters',
|
||||||
'backend.form.parameterFormat': 'Parameter Format',
|
'backend.form.flagFormat': 'Flag Format',
|
||||||
'backend.form.parameterFormat.default': 'Backend Default',
|
'backend.form.flagFormat.tips':
|
||||||
'backend.form.parameterFormat.space': 'Space (--key value)',
|
'The format applied between an option and its value. Leave empty to keep each parameter as entered, without normalizing it.',
|
||||||
'backend.form.parameterFormat.equal': 'Equal (--key=value)',
|
'backend.form.flagFormat.space': 'Space Separated (--key value)',
|
||||||
'backend.form.commonParameters': 'Common Parameters',
|
'backend.form.flagFormat.equal': 'Equal Sign (--key=value)',
|
||||||
|
'backend.form.commonParameters': 'Common Backend Parameters',
|
||||||
'backend.form.commonParameters.tips':
|
'backend.form.commonParameters.tips':
|
||||||
'Shown as suggestions in the backend parameters input during deployment.',
|
'Shown as suggestions in the backend parameters input during deployment.',
|
||||||
'backend.form.versionConfig': 'Versions Config',
|
'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.',
|
'Stress test for long-context handling. Evaluates KV cache behavior, memory usage, and backend stability.',
|
||||||
'benchmark.form.profile.heavy.tips':
|
'benchmark.form.profile.heavy.tips':
|
||||||
'Decode-heavy generation benchmark. Measures sustained decoding speed and output token throughput.',
|
'Decode-heavy generation benchmark. Measures sustained decoding speed and output token throughput.',
|
||||||
'benchmark.table.filter.bygpu': 'Filter by GPU',
|
'benchmark.table.filter.bygpu': 'Search by GPU',
|
||||||
'benchmark.table.filter.bymodel': 'Filter by Model',
|
'benchmark.table.filter.bymodel': 'Search by model',
|
||||||
'benchmark.table.filter.bydataset': 'Filter by Dataset',
|
'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.avg': 'Avg',
|
||||||
'benchmark.table.columnSettings': 'Column Settings',
|
'benchmark.table.columnSettings': 'Column Settings',
|
||||||
'benchmark.detail.summary.results': 'Test Results',
|
'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'
|
||||||
|
};
|
||||||
@@ -39,7 +39,7 @@ export default {
|
|||||||
'clusters.workerpool.batchSize.desc':
|
'clusters.workerpool.batchSize.desc':
|
||||||
'Number of workers created simultaneously in the Worker pool',
|
'Number of workers created simultaneously in the Worker pool',
|
||||||
'clusters.create.addworker.tips':
|
'clusters.create.addworker.tips':
|
||||||
'Please make sure the <a href={link} target="_blank">prerequisites</a> for {label} are met before executing the following command.',
|
'Please make sure the <a href={link} target="_blank">prerequisites</a> are met before executing the following command.',
|
||||||
'clusters.create.addCommand.tips':
|
'clusters.create.addCommand.tips':
|
||||||
'On the Worker that needs to be added, run the following command to join it to the cluster.',
|
'On the Worker that needs to be added, run the following command to join it to the cluster.',
|
||||||
'clusters.create.addCommand.k8s.tips':
|
'clusters.create.addCommand.k8s.tips':
|
||||||
@@ -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.',
|
'For <span class="bold-text">non-Docker</span> clusters, please register clusters or manage worker pools from the Clusters page.',
|
||||||
'clusters.addworker.selectGPU': 'Select GPU Vendor',
|
'clusters.addworker.selectGPU': 'Select GPU Vendor',
|
||||||
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
|
'clusters.addworker.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': '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.specifyArgs': 'Specify Arguments',
|
||||||
|
'clusters.addworker.dtkVersion': 'DTK Version',
|
||||||
'clusters.addworker.runCommand': 'Run Command',
|
'clusters.addworker.runCommand': 'Run Command',
|
||||||
'clusters.addworker.specifyWorkerIP': 'Worker IP',
|
'clusters.addworker.specifyWorkerIP': 'Worker IP',
|
||||||
'clusters.addworker.detectWorkerIP': 'Auto-detect Worker IP',
|
'clusters.addworker.detectWorkerIP': 'Auto-detect Worker IP',
|
||||||
@@ -110,6 +115,8 @@ export default {
|
|||||||
'{count} new workers have been added to the cluster.',
|
'{count} new workers have been added to the cluster.',
|
||||||
'clusters.create.serverUrl': 'GPUStack Server URL',
|
'clusters.create.serverUrl': 'GPUStack Server URL',
|
||||||
'clusters.create.workerConfig': 'Worker Configuration',
|
'clusters.create.workerConfig': 'Worker Configuration',
|
||||||
|
'clusters.edit.k8sOptions.changed.tip':
|
||||||
|
'You have changed the Kubernetes options. Re-run the registration command on the target cluster for the changes to take effect.',
|
||||||
'clusters.addworker.containerName': 'Worker Container Name',
|
'clusters.addworker.containerName': 'Worker Container Name',
|
||||||
'clusters.addworker.containerName.tips':
|
'clusters.addworker.containerName.tips':
|
||||||
'Specify a name for the worker container.',
|
'Specify a name for the worker container.',
|
||||||
@@ -137,7 +144,7 @@ export default {
|
|||||||
'clusters.addworker.theadNotes-02':
|
'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.',
|
'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':
|
'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.title': 'Volume Mounts',
|
||||||
'clusters.volume.name': 'Volume Name',
|
'clusters.volume.name': 'Volume Name',
|
||||||
'clusters.volume.mountPath': 'Container Path',
|
'clusters.volume.mountPath': 'Container Path',
|
||||||
@@ -165,7 +172,7 @@ export default {
|
|||||||
'clusters.systemDefaultContainerRegistry.title': 'Default Container Registry',
|
'clusters.systemDefaultContainerRegistry.title': 'Default Container Registry',
|
||||||
'clusters.systemDefaultContainerRegistry.tip':
|
'clusters.systemDefaultContainerRegistry.tip':
|
||||||
'Default registry used to resolve GPUStack images for this cluster. Falls back to the server default when unset.',
|
'Default registry used to resolve GPUStack images for this cluster. Falls back to the server default when unset.',
|
||||||
'clusters.k8sOptions.title': 'K8s Deployment Options',
|
'clusters.k8sOptions.title': 'Kubernetes Deployment Options',
|
||||||
'clusters.imageCredentials.title': 'Image Credentials',
|
'clusters.imageCredentials.title': 'Image Credentials',
|
||||||
'clusters.imageCredentials.add': 'Add Credential',
|
'clusters.imageCredentials.add': 'Add Credential',
|
||||||
'clusters.imageCredentials.registry': 'Registry',
|
'clusters.imageCredentials.registry': 'Registry',
|
||||||
@@ -180,9 +187,14 @@ export default {
|
|||||||
'clusters.namespace.title': 'Namespace',
|
'clusters.namespace.title': 'Namespace',
|
||||||
'clusters.namespace.tip':
|
'clusters.namespace.tip':
|
||||||
'Kubernetes namespace the cluster’s manifests render into. Leave empty to use gpustack-system.',
|
'Kubernetes namespace the cluster’s manifests render into. Leave empty to use gpustack-system.',
|
||||||
'clusters.gpuInstances.title': 'GPU Instances',
|
'clusters.clusterType.title': 'Cluster Type',
|
||||||
'clusters.gpuInstances.tip': 'Enable GPU instance support for this cluster.',
|
'clusters.modelService.title': 'Model Service',
|
||||||
'clusters.gpuInstances.staticAddress': 'Static Access Address',
|
'clusters.modelService.tip':
|
||||||
|
'For LLM inference and API serving — e.g. exposing model APIs and token-based services.',
|
||||||
|
'clusters.gpuInstances.title': 'GPU Service',
|
||||||
|
'clusters.gpuInstances.tip':
|
||||||
|
'For on-demand GPU compute — e.g. interactive development, training jobs, or custom environments.',
|
||||||
|
'clusters.gpuInstances.staticAddress': 'GPU Service Static Access Address',
|
||||||
'clusters.gpuInstances.staticAddress.tip':
|
'clusters.gpuInstances.staticAddress.tip':
|
||||||
'Static address the operator uses to access GPU instances in this cluster (e.g. a LoadBalancer VIP). Optional.'
|
'Static address the operator uses to access GPU instances in this cluster (e.g. a LoadBalancer VIP). Optional.'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export default {
|
|||||||
'common.button.enabled': 'Enabled',
|
'common.button.enabled': 'Enabled',
|
||||||
'common.button.disabled': 'Disabled',
|
'common.button.disabled': 'Disabled',
|
||||||
'common.button.upgrade': 'Upgrade',
|
'common.button.upgrade': 'Upgrade',
|
||||||
|
'common.enterprise.feature': 'Available in GPUStack Enterprise',
|
||||||
'common.input.holder': 'Please enter',
|
'common.input.holder': 'Please enter',
|
||||||
'common.validate.value': '{name} value is required',
|
'common.validate.value': '{name} value is required',
|
||||||
'common.button.edit': 'Edit',
|
'common.button.edit': 'Edit',
|
||||||
@@ -55,6 +56,7 @@ export default {
|
|||||||
'common.button.viewevent': 'View Events',
|
'common.button.viewevent': 'View Events',
|
||||||
'common.button.recreate': 'Recreate',
|
'common.button.recreate': 'Recreate',
|
||||||
'common.table.operation': 'Operations',
|
'common.table.operation': 'Operations',
|
||||||
|
'common.table.creator': 'Creator',
|
||||||
'common.table.createTime': 'Created',
|
'common.table.createTime': 'Created',
|
||||||
'common.table.updateTime': 'Updated',
|
'common.table.updateTime': 'Updated',
|
||||||
'common.table.description': 'Description',
|
'common.table.description': 'Description',
|
||||||
@@ -65,6 +67,7 @@ export default {
|
|||||||
'common.search.name.placeholder': 'filter by name',
|
'common.search.name.placeholder': 'filter by name',
|
||||||
'common.search.id.placeholder': 'filter by ID',
|
'common.search.id.placeholder': 'filter by ID',
|
||||||
'common.filter.byId': 'filter by ID',
|
'common.filter.byId': 'filter by ID',
|
||||||
|
'common.filter.byCreator': 'Filter by creator',
|
||||||
'common.table.type': 'Type',
|
'common.table.type': 'Type',
|
||||||
'common.table.default': 'Default Value',
|
'common.table.default': 'Default Value',
|
||||||
'common.copy.success': 'Copied success!',
|
'common.copy.success': 'Copied success!',
|
||||||
@@ -166,6 +169,7 @@ export default {
|
|||||||
'common.time.hour': 'hour',
|
'common.time.hour': 'hour',
|
||||||
'common.time.minute': 'minutes',
|
'common.time.minute': 'minutes',
|
||||||
'common.issue.report': 'Report an issue',
|
'common.issue.report': 'Report an issue',
|
||||||
|
'common.github.star.tooltip': 'Star us on GitHub',
|
||||||
'common.social.discord': 'Join Our Discord',
|
'common.social.discord': 'Join Our Discord',
|
||||||
'common.table.mark': 'Comment',
|
'common.table.mark': 'Comment',
|
||||||
'common.table.rollback.mark': 'Rollback Comment',
|
'common.table.rollback.mark': 'Rollback Comment',
|
||||||
@@ -257,6 +261,10 @@ export default {
|
|||||||
'common.login.auth': 'Authenticating...',
|
'common.login.auth': 'Authenticating...',
|
||||||
'common.login.auth.failed': 'Authentication failed',
|
'common.login.auth.failed': 'Authentication failed',
|
||||||
'common.login.password': 'Log in with Password',
|
'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.external.login': 'Log in with {type}',
|
||||||
'common.sso.noConfig':
|
'common.sso.noConfig':
|
||||||
'Single sign-on is not enabled on this system. Please contact your administrator.',
|
'Single sign-on is not enabled on this system. Please contact your administrator.',
|
||||||
@@ -285,5 +293,7 @@ export default {
|
|||||||
'common.image.limit.height': 'Image height must be {height}.',
|
'common.image.limit.height': 'Image height must be {height}.',
|
||||||
'common.remaining': 'Remaining {count}',
|
'common.remaining': 'Remaining {count}',
|
||||||
'common.max': 'Max {count}',
|
'common.max': 'Max {count}',
|
||||||
'common.validate.group': 'Please complete the {group} configuration'
|
'common.max.count': '{label} Count',
|
||||||
|
'common.validate.group': 'Please complete the {group} configuration',
|
||||||
|
'common.preferences': 'Preferences'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ export default {
|
|||||||
'gpuservice.template.command.placeholder':
|
'gpuservice.template.command.placeholder':
|
||||||
'Separate arguments with spaces; wrap arguments containing spaces in quotes, e.g.: /bin/bash -c "echo hello world"',
|
'Separate arguments with spaces; wrap arguments containing spaces in quotes, e.g.: /bin/bash -c "echo hello world"',
|
||||||
'gpuservice.template.mountPath': 'Mount Path',
|
'gpuservice.template.mountPath': 'Mount Path',
|
||||||
|
'gpuservice.template.mountPath.tips':
|
||||||
|
'The default mount path for the storage volume when creating an instance from this template. Useful for persisting data that needs to be retained while the instance is running.',
|
||||||
'gpuservice.template.containerDisk': 'Container Disk (GB)',
|
'gpuservice.template.containerDisk': 'Container Disk (GB)',
|
||||||
|
'gpuservice.template.containerDisk.tips':
|
||||||
|
'The size of the container system disk.',
|
||||||
'gpuservice.template.memory': 'Memory (GB)',
|
'gpuservice.template.memory': 'Memory (GB)',
|
||||||
'gpuservice.instance.containerDisk.remaining':
|
'gpuservice.instance.containerDisk.remaining':
|
||||||
'Container Disk (Max {count} GB)',
|
'Container Disk (Max {count} GB)',
|
||||||
@@ -97,12 +101,17 @@ export default {
|
|||||||
'gpuservice.instance.templates': 'Instance Templates',
|
'gpuservice.instance.templates': 'Instance Templates',
|
||||||
'gpuservice.instance.section.storage': 'Storage',
|
'gpuservice.instance.section.storage': 'Storage',
|
||||||
'gpuservice.instance.type.required': 'Please select an instance type',
|
'gpuservice.instance.type.required': 'Please select an instance type',
|
||||||
|
'gpuservice.instance.type.noAvailable': 'No instance type available',
|
||||||
'gpuservice.instance.gpuCount': 'GPU Count',
|
'gpuservice.instance.gpuCount': 'GPU Count',
|
||||||
'gpuservice.instance.gpuCount.required': 'Please enter the GPU count',
|
'gpuservice.instance.gpuCount.required': 'Please enter the GPU count',
|
||||||
'gpuservice.instance.gpuCount.max':
|
'gpuservice.instance.gpuCount.max':
|
||||||
'Please select at most {count} GPU card(s)',
|
'Please select at most {count} GPU card(s)',
|
||||||
'gpuservice.instance.gpuCount.min':
|
'gpuservice.instance.gpuCount.min':
|
||||||
'Please select at least {count} GPU card(s)',
|
'Please select at least {count} GPU card(s)',
|
||||||
|
'gpuservice.instance.cpuCount.max':
|
||||||
|
'Please select at most {count} CPU core(s)',
|
||||||
|
'gpuservice.instance.cpuCount.min':
|
||||||
|
'Please select at least {count} CPU core(s)',
|
||||||
'gpuservice.instance.gpuCount.noAvailable':
|
'gpuservice.instance.gpuCount.noAvailable':
|
||||||
'No available GPU resources, please choose another instance type.',
|
'No available GPU resources, please choose another instance type.',
|
||||||
'gpuservice.instance.gpuCount.zero':
|
'gpuservice.instance.gpuCount.zero':
|
||||||
@@ -111,7 +120,13 @@ export default {
|
|||||||
'gpuservice.instance.sliced': 'Sliced',
|
'gpuservice.instance.sliced': 'Sliced',
|
||||||
'gpuservice.instance.memory': 'VRAM',
|
'gpuservice.instance.memory': 'VRAM',
|
||||||
'gpuservice.instance.ram': 'RAM',
|
'gpuservice.instance.ram': 'RAM',
|
||||||
|
'gpuservice.instance.os': 'OS',
|
||||||
|
'gpuservice.instance.arch': 'Arch',
|
||||||
'gpuservice.instance.disk': 'Disk',
|
'gpuservice.instance.disk': 'Disk',
|
||||||
|
'gpuservice.table.count': 'Count',
|
||||||
|
'gpuservice.instance.disk.system': 'System Disk',
|
||||||
|
'gpuservice.instance.disk.ephemeral': 'Ephemeral Storage',
|
||||||
|
'gpuservice.instance.disk.persistent': 'Persistent Storage',
|
||||||
'gpuservice.instance.search.type.placeholder': 'Search by name',
|
'gpuservice.instance.search.type.placeholder': 'Search by name',
|
||||||
'gpuservice.instance.search.template.placeholder':
|
'gpuservice.instance.search.template.placeholder':
|
||||||
'Search by template name, image or mount path',
|
'Search by template name, image or mount path',
|
||||||
@@ -147,9 +162,8 @@ export default {
|
|||||||
'gpuservice.storage.temporary.tips':
|
'gpuservice.storage.temporary.tips':
|
||||||
'Data is cleared when the instance stops.',
|
'Data is cleared when the instance stops.',
|
||||||
'gpuservice.storage.persistentVolume.tips':
|
'gpuservice.storage.persistentVolume.tips':
|
||||||
'Data persists across restarts and is deleted only when the instance is terminated. Cannot be shared with other instances.',
|
'Data persists across instance restarts. Persistent volumes remain intact after instance termination and can be shared by multiple instances.',
|
||||||
'gpuservice.storage.persistentVolume.required':
|
'gpuservice.storage.persistentVolume.required': 'Please select a storage',
|
||||||
'Please select a persistent volume',
|
|
||||||
'gpuservice.storage.persistentVolume.capacity': 'Capacity (GB)',
|
'gpuservice.storage.persistentVolume.capacity': 'Capacity (GB)',
|
||||||
'gpuservice.storage.persistentVolume.capacity.required':
|
'gpuservice.storage.persistentVolume.capacity.required':
|
||||||
'Please enter capacity',
|
'Please enter capacity',
|
||||||
@@ -159,5 +173,10 @@ export default {
|
|||||||
'gpuservice.storage.tempCapacity.required':
|
'gpuservice.storage.tempCapacity.required':
|
||||||
'Please enter the temporary storage capacity',
|
'Please enter the temporary storage capacity',
|
||||||
'gpuservice.form.rule.name':
|
'gpuservice.form.rule.name':
|
||||||
"Lowercase letters, numbers, and '-'. Start and end with a letter or number, no consecutive '-', max 63 characters."
|
"Lowercase letters, numbers, and '-'. Start and end with a letter or number, no consecutive '-', max 63 characters.",
|
||||||
|
'gpuservice.form.storage.select': 'Select Storage',
|
||||||
|
'gpuservice.creator': 'Creator',
|
||||||
|
'gpuservice.owner.global': 'Global',
|
||||||
|
'gpuservice.template.group.yours': 'Your Templates',
|
||||||
|
'gpuservice.template.group.global': 'Global Templates'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,15 +31,19 @@ export default {
|
|||||||
'menu.accessControl': 'Access Control',
|
'menu.accessControl': 'Access Control',
|
||||||
'menu.accessControl.apikeys': 'API Keys',
|
'menu.accessControl.apikeys': 'API Keys',
|
||||||
'menu.accessControl.users': 'Users',
|
'menu.accessControl.users': 'Users',
|
||||||
'menu.profile': 'Profile',
|
'menu.accessControl.organizations': 'Organizations',
|
||||||
|
'menu.profile': 'Preferences',
|
||||||
'menu.login': 'Login',
|
'menu.login': 'Login',
|
||||||
'menu.usage': 'Usage',
|
'menu.usage': 'Usage',
|
||||||
|
'menu.usage.usage': 'Usage',
|
||||||
|
'menu.billingAndUsage': 'Usage & Billing',
|
||||||
|
'menu.billingAndUsage.usage': 'Usage',
|
||||||
|
'menu.billingAndUsage.billing': 'Billing',
|
||||||
'menu.404': '404',
|
'menu.404': '404',
|
||||||
'menu.clusterManagement': 'Cluster Management',
|
'menu.resources.clusters': 'Clusters',
|
||||||
'menu.clusterManagement.clusters': 'Clusters',
|
'menu.resources.credentials': 'Cloud Credentials',
|
||||||
'menu.clusterManagement.credentials': 'Cloud Credentials',
|
'menu.resources.clusterDetail': 'Cluster Detail',
|
||||||
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
'menu.resources.clusterCreate': 'Create Cluster',
|
||||||
'menu.clusterManagement.clusterCreate': 'Create Cluster',
|
|
||||||
'menu.models.backendsList': 'Inference Backends',
|
'menu.models.backendsList': 'Inference Backends',
|
||||||
'menu.gpuService': 'GPU Service',
|
'menu.gpuService': 'GPU Service',
|
||||||
'menu.gpuService.instances': 'GPU Instances',
|
'menu.gpuService.instances': 'GPU Instances',
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ export default {
|
|||||||
'models.instance.startHistory': 'Run History',
|
'models.instance.startHistory': 'Run History',
|
||||||
'models.instance.startHistory.tips':
|
'models.instance.startHistory.tips':
|
||||||
'Shows logs from the run before the last error-triggered restart.',
|
'Shows logs from the run before the last error-triggered restart.',
|
||||||
'models.form.lora.label': 'LoRA Adapter',
|
'models.form.lora.label': 'LoRA Adapters',
|
||||||
'models.form.lora.add': 'Add LoRA Adapter',
|
'models.form.lora.add': 'Add LoRA Adapter',
|
||||||
'models.form.lora.select': 'Select LoRA',
|
'models.form.lora.select': 'Select LoRA',
|
||||||
'models.form.lora.name': 'LoRA name',
|
'models.form.lora.name': 'LoRA name',
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export default {
|
||||||
|
'organizations.upsell.title': 'Organizations are an Enterprise feature',
|
||||||
|
'organizations.upsell.subtitle':
|
||||||
|
'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to GPUStack Enterprise to manage organizations.',
|
||||||
|
'organizations.upsell.featuresTitle': 'What you get in Enterprise',
|
||||||
|
'organizations.upsell.feature.orgs':
|
||||||
|
'Create organizations to group users and isolate workloads',
|
||||||
|
'organizations.upsell.feature.members':
|
||||||
|
'Manage members and roles per organization',
|
||||||
|
'organizations.upsell.feature.quotas':
|
||||||
|
'Set resource and token quotas per organization',
|
||||||
|
'organizations.upsell.feature.isolation':
|
||||||
|
'Scope API keys, model deployments, and resources per organization',
|
||||||
|
'organizations.upsell.cta': 'Learn about Enterprise'
|
||||||
|
};
|
||||||
@@ -17,9 +17,15 @@ export default {
|
|||||||
'usage.table.user.apiKeysUsed': 'API Keys Used',
|
'usage.table.user.apiKeysUsed': 'API Keys Used',
|
||||||
'usage.table.lastActive': 'Last Active',
|
'usage.table.lastActive': 'Last Active',
|
||||||
'usage.filter.granularity': 'Granularity',
|
'usage.filter.granularity': 'Granularity',
|
||||||
|
'usage.filter.granularity.hour': 'Hour',
|
||||||
'usage.filter.granularity.day': 'Day',
|
'usage.filter.granularity.day': 'Day',
|
||||||
'usage.filter.granularity.week': 'Week',
|
'usage.filter.granularity.week': 'Week',
|
||||||
'usage.filter.granularity.month': 'Month',
|
'usage.filter.granularity.month': 'Month',
|
||||||
|
'usage.tabs.summary': 'Summary',
|
||||||
|
'usage.tabs.tokens': 'Tokens',
|
||||||
|
'usage.tabs.gpuInstances': 'GPU Instances',
|
||||||
|
'usage.tabs.storage': 'Storage',
|
||||||
|
'usage.tabs.resourceEvents': 'Resource Events',
|
||||||
'usage.tabs.models': 'Models',
|
'usage.tabs.models': 'Models',
|
||||||
'usage.tabs.apikeys': 'API Keys',
|
'usage.tabs.apikeys': 'API Keys',
|
||||||
'usage.tabs.users': 'User',
|
'usage.tabs.users': 'User',
|
||||||
@@ -32,5 +38,70 @@ export default {
|
|||||||
'usage.chart.cached': 'Cached',
|
'usage.chart.cached': 'Cached',
|
||||||
'usage.chart.uncached': 'Uncached',
|
'usage.chart.uncached': 'Uncached',
|
||||||
'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)',
|
'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)',
|
||||||
'usage.table.inputTokensCached': 'Input Tokens Cached'
|
'usage.table.inputTokensCached': 'Input Tokens Cached',
|
||||||
|
|
||||||
|
// --- Resource usage: shared metrics & units ---
|
||||||
|
'usage.metric.tokens': 'Tokens',
|
||||||
|
'usage.metric.input': 'Input',
|
||||||
|
'usage.metric.output': 'Output',
|
||||||
|
'usage.metric.gpuHours': 'GPU Hours',
|
||||||
|
'usage.metric.instanceHours': 'Instance Hours',
|
||||||
|
'usage.metric.gbDays': 'GB-Days',
|
||||||
|
'usage.metric.gbHours': 'GB-Hours',
|
||||||
|
'usage.metric.activeUsers': 'Active Users',
|
||||||
|
'usage.metric.activeInstances': 'Active Instances',
|
||||||
|
'usage.metric.activeStorage': 'Active Storage',
|
||||||
|
'usage.metric.activeVolumes': 'Active Volumes',
|
||||||
|
'usage.metric.storageTypes': 'Storage Types',
|
||||||
|
'usage.metric.gpuHours.tip':
|
||||||
|
'Instance running time weighted by GPU count: an instance with N GPUs running for H hours counts as N × H GPU-hours. Equal to Instance Hours when every instance uses a single GPU.',
|
||||||
|
'usage.metric.instanceHours.tip':
|
||||||
|
'Total running time summed across all instances, regardless of how many GPUs each uses. One instance running for 2 hours = 2 instance-hours.',
|
||||||
|
'usage.metric.gbDays.tip':
|
||||||
|
'Storage capacity integrated over time, in GB × days: 10 GB kept for 5 days = 50 GB-days. (= GB-Hours ÷ 24)',
|
||||||
|
'usage.metric.gbHours.tip':
|
||||||
|
'Storage capacity integrated over time, in GB × hours: 10 GB kept for 5 hours = 50 GB-hours.',
|
||||||
|
|
||||||
|
// --- Resource usage: common table / labels ---
|
||||||
|
'usage.common.noData': 'No data',
|
||||||
|
'usage.common.unknown': 'unknown',
|
||||||
|
'usage.table.date': 'Date',
|
||||||
|
'usage.table.name': 'Name',
|
||||||
|
'usage.table.user': 'User',
|
||||||
|
'usage.table.users': 'Users',
|
||||||
|
'usage.table.type': 'Type',
|
||||||
|
'usage.table.instance': 'Instance',
|
||||||
|
'usage.table.instanceType': 'Instance Type',
|
||||||
|
'usage.table.instanceTypes': 'Instance Types',
|
||||||
|
'usage.table.instances': 'Instances',
|
||||||
|
'usage.table.capacity': 'Capacity',
|
||||||
|
'usage.export.tableNamed': 'Export Table Data — {name}',
|
||||||
|
|
||||||
|
// --- Summary tab ---
|
||||||
|
'usage.summary.compute': 'Compute',
|
||||||
|
'usage.summary.tokensOverTime': 'Tokens over time',
|
||||||
|
'usage.summary.gpuHoursOverTime': 'GPU Hours over time',
|
||||||
|
'usage.summary.gbDaysOverTime': 'GB-Days over time',
|
||||||
|
|
||||||
|
// --- GPU Instances / Storage filters ---
|
||||||
|
'usage.filter.instance': 'Filter by instance',
|
||||||
|
'usage.filter.storage': 'Filter by storage',
|
||||||
|
|
||||||
|
// --- Resource events ---
|
||||||
|
'usage.events.resourceType': 'Resource type',
|
||||||
|
'usage.events.eventType': 'Event type',
|
||||||
|
'usage.events.resourceName': 'Filter by name',
|
||||||
|
'usage.events.col.time': 'Time',
|
||||||
|
'usage.events.col.resource': 'Resource',
|
||||||
|
'usage.events.col.event': 'Event',
|
||||||
|
'usage.events.col.message': 'Message',
|
||||||
|
'usage.events.resource.gpuInstance': 'GPU Instance',
|
||||||
|
'usage.events.resource.cpuInstance': 'CPU Instance',
|
||||||
|
'usage.events.type.created': 'Created',
|
||||||
|
'usage.events.type.deleted': 'Deleted',
|
||||||
|
'usage.events.type.started': 'Started',
|
||||||
|
'usage.events.type.stopped': 'Stopped',
|
||||||
|
'usage.events.type.updated': 'Updated',
|
||||||
|
'usage.events.type.attached': 'Attached',
|
||||||
|
'usage.events.type.detached': 'Detached'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export default {
|
|||||||
'vendor.hygon': 'Hygon',
|
'vendor.hygon': 'Hygon',
|
||||||
'vendor.moorthreads': 'Moore Threads',
|
'vendor.moorthreads': 'Moore Threads',
|
||||||
'vendor.iluvatar': 'Iluvatar',
|
'vendor.iluvatar': 'Iluvatar',
|
||||||
'vendor.metax': 'Metax',
|
'vendor.metax': 'MetaX',
|
||||||
'vendor.cambricon': 'Cambricon',
|
'vendor.cambricon': 'Cambricon',
|
||||||
'vendor.thead': 'T-Head PPU'
|
'vendor.thead': 'T-Head PPU'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default {
|
|||||||
'ai.provider.ollama': 'Ollama',
|
'ai.provider.ollama': 'Ollama',
|
||||||
'ai.provider.openai': 'OpenAI',
|
'ai.provider.openai': 'OpenAI',
|
||||||
'ai.provider.openrouter': 'OpenRouter',
|
'ai.provider.openrouter': 'OpenRouter',
|
||||||
'ai.provider.qwen': 'Qwen',
|
'ai.provider.qwen': 'Alibaba Cloud Model Studio',
|
||||||
'ai.provider.spark': 'Spark',
|
'ai.provider.spark': 'Spark',
|
||||||
'ai.provider.stepfun': 'StepFun',
|
'ai.provider.stepfun': 'StepFun',
|
||||||
'ai.provider.together-ai': 'TogetherAI',
|
'ai.provider.together-ai': 'TogetherAI',
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ export default {
|
|||||||
'apikeys.accessScope.inference': 'Inference APIs',
|
'apikeys.accessScope.inference': 'Inference APIs',
|
||||||
'apikeys.access.permissions': 'Access Permissions',
|
'apikeys.access.permissions': 'Access Permissions',
|
||||||
'apikeys.type.auto': 'Auto-generated',
|
'apikeys.type.auto': 'Auto-generated',
|
||||||
'apikeys.type.custom': 'Custom'
|
'apikeys.type.custom': 'Custom',
|
||||||
|
'apikeys.button.ipConfig': 'IP Access Control',
|
||||||
|
'quotaLimits.button.title': 'Quota Limit'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ export default {
|
|||||||
'backend.form.defaultExecuteCommand': 'Default Execution Command',
|
'backend.form.defaultExecuteCommand': 'Default Execution Command',
|
||||||
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' and '{{'model_name'}}' are placeholders that will be substituted with the actual values during deployment.`,
|
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' and '{{'model_name'}}' are placeholders that will be substituted with the actual values during deployment.`,
|
||||||
'backend.form.defaultBackendParameters': 'Default Backend Parameters',
|
'backend.form.defaultBackendParameters': 'Default Backend Parameters',
|
||||||
'backend.form.parameterFormat': 'パラメータ形式',
|
'backend.form.flagFormat': 'フラグ形式',
|
||||||
'backend.form.parameterFormat.default': 'バックエンドのデフォルト',
|
'backend.form.flagFormat.tips':
|
||||||
'backend.form.parameterFormat.space': 'スペース区切り (--key value)',
|
'オプションとその値を連結する形式。空欄の場合は各パラメータを入力されたまま保持し、形式を統一しません。',
|
||||||
'backend.form.parameterFormat.equal': 'イコール連結 (--key=value)',
|
'backend.form.flagFormat.space': 'スペース区切り (--key value)',
|
||||||
'backend.form.commonParameters': 'よく使うパラメータ',
|
'backend.form.flagFormat.equal': 'イコール記号 (--key=value)',
|
||||||
|
'backend.form.commonParameters': 'よく使うバックエンドパラメータ',
|
||||||
'backend.form.commonParameters.tips':
|
'backend.form.commonParameters.tips':
|
||||||
'モデルのデプロイ時にバックエンドパラメータ入力欄の候補として表示されます。',
|
'モデルのデプロイ時にバックエンドパラメータ入力欄の候補として表示されます。',
|
||||||
'backend.form.versionConfig': 'Versions Config',
|
'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.',
|
'Stress test for long-context handling. Evaluates KV cache behavior, memory usage, and backend stability.',
|
||||||
'benchmark.form.profile.heavy.tips':
|
'benchmark.form.profile.heavy.tips':
|
||||||
'Decode-heavy generation benchmark. Measures sustained decoding speed and output token throughput.',
|
'Decode-heavy generation benchmark. Measures sustained decoding speed and output token throughput.',
|
||||||
'benchmark.table.filter.bygpu': 'Filter by GPU',
|
'benchmark.table.filter.bygpu': 'GPU 検索',
|
||||||
'benchmark.table.filter.bymodel': 'Filter by Model',
|
'benchmark.table.filter.bymodel': 'モデル検索',
|
||||||
'benchmark.table.filter.bydataset': 'Filter by Dataset',
|
'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.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'
|
||||||
|
};
|
||||||
@@ -39,7 +39,7 @@ export default {
|
|||||||
'clusters.workerpool.batchSize.desc':
|
'clusters.workerpool.batchSize.desc':
|
||||||
'Number of workers created simultaneously in the Worker pool',
|
'Number of workers created simultaneously in the Worker pool',
|
||||||
'clusters.create.addworker.tips':
|
'clusters.create.addworker.tips':
|
||||||
'Please make sure the <a href={link} target="_blank">prerequisites</a> for {label} are met before executing the following command.',
|
'Please make sure the <a href={link} target="_blank">prerequisites</a> are met before executing the following command.',
|
||||||
'clusters.create.addCommand.tips':
|
'clusters.create.addCommand.tips':
|
||||||
'On the Worker that needs to be added, run the following command to join it to the cluster.',
|
'On the Worker that needs to be added, run the following command to join it to the cluster.',
|
||||||
'clusters.create.addCommand.k8s.tips':
|
'clusters.create.addCommand.k8s.tips':
|
||||||
@@ -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.',
|
'For <span class="bold-text">non-Docker</span> clusters, please register clusters or manage worker pools from the Clusters page.',
|
||||||
'clusters.addworker.selectGPU': 'Select GPU Vendor',
|
'clusters.addworker.selectGPU': 'Select GPU Vendor',
|
||||||
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
|
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
|
||||||
|
'clusters.addworker.selectGPU.subtitle':
|
||||||
|
'複数の GPU ベンダーを選択するか、CPU クラスター専用の場合は選択不要です',
|
||||||
'clusters.addworker.checkEnv': 'Check Environment',
|
'clusters.addworker.checkEnv': 'Check Environment',
|
||||||
|
'clusters.addworker.checkEnv.cpuOnlyTips':
|
||||||
|
'以下のコマンドを使用して、Kubernetes クラスターに少なくとも 1 つのレディーノードがあることを確認してください。CPU クラスターを登録しています。',
|
||||||
'clusters.addworker.specifyArgs': 'Specify Arguments',
|
'clusters.addworker.specifyArgs': 'Specify Arguments',
|
||||||
|
'clusters.addworker.dtkVersion': 'DTK バージョン',
|
||||||
'clusters.addworker.runCommand': 'Run Command',
|
'clusters.addworker.runCommand': 'Run Command',
|
||||||
'clusters.addworker.specifyWorkerIP': 'Worker IP',
|
'clusters.addworker.specifyWorkerIP': 'Worker IP',
|
||||||
'clusters.addworker.detectWorkerIP': 'Worker IP を自動検出',
|
'clusters.addworker.detectWorkerIP': 'Worker IP を自動検出',
|
||||||
@@ -110,6 +115,8 @@ export default {
|
|||||||
'{count} new workers have been added to the cluster.',
|
'{count} new workers have been added to the cluster.',
|
||||||
'clusters.create.serverUrl': 'GPUStack Server URL',
|
'clusters.create.serverUrl': 'GPUStack Server URL',
|
||||||
'clusters.create.workerConfig': 'Worker Configuration',
|
'clusters.create.workerConfig': 'Worker Configuration',
|
||||||
|
'clusters.edit.k8sOptions.changed.tip':
|
||||||
|
'Kubernetes オプションを変更しました。変更を有効にするには、対象クラスターで登録コマンドを再実行してください。',
|
||||||
'clusters.addworker.containerName': 'Worker Container Name',
|
'clusters.addworker.containerName': 'Worker Container Name',
|
||||||
'clusters.addworker.containerName.tips':
|
'clusters.addworker.containerName.tips':
|
||||||
'Specify a name for the worker container.',
|
'Specify a name for the worker container.',
|
||||||
@@ -137,7 +144,7 @@ export default {
|
|||||||
'clusters.addworker.theadNotes-02':
|
'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.',
|
'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':
|
'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.title': 'Volume Mounts',
|
||||||
'clusters.volume.name': 'Volume Name',
|
'clusters.volume.name': 'Volume Name',
|
||||||
'clusters.volume.mountPath': 'Container Path',
|
'clusters.volume.mountPath': 'Container Path',
|
||||||
@@ -165,7 +172,7 @@ export default {
|
|||||||
'clusters.systemDefaultContainerRegistry.title': 'Default Container Registry',
|
'clusters.systemDefaultContainerRegistry.title': 'Default Container Registry',
|
||||||
'clusters.systemDefaultContainerRegistry.tip':
|
'clusters.systemDefaultContainerRegistry.tip':
|
||||||
'Default registry used to resolve GPUStack images for this cluster. Falls back to the server default when unset.',
|
'Default registry used to resolve GPUStack images for this cluster. Falls back to the server default when unset.',
|
||||||
'clusters.k8sOptions.title': 'K8s Deployment Options',
|
'clusters.k8sOptions.title': 'Kubernetes Deployment Options',
|
||||||
'clusters.imageCredentials.title': 'Image Credentials',
|
'clusters.imageCredentials.title': 'Image Credentials',
|
||||||
'clusters.imageCredentials.add': 'Add Credential',
|
'clusters.imageCredentials.add': 'Add Credential',
|
||||||
'clusters.imageCredentials.registry': 'Registry',
|
'clusters.imageCredentials.registry': 'Registry',
|
||||||
@@ -180,9 +187,14 @@ export default {
|
|||||||
'clusters.namespace.title': 'Namespace',
|
'clusters.namespace.title': 'Namespace',
|
||||||
'clusters.namespace.tip':
|
'clusters.namespace.tip':
|
||||||
'Kubernetes namespace the cluster’s manifests render into. Leave empty to use gpustack-system.',
|
'Kubernetes namespace the cluster’s manifests render into. Leave empty to use gpustack-system.',
|
||||||
'clusters.gpuInstances.title': 'GPU Instances',
|
'clusters.clusterType.title': 'Cluster Type',
|
||||||
'clusters.gpuInstances.tip': 'Enable GPU instance support for this cluster.',
|
'clusters.modelService.title': 'Model Service',
|
||||||
'clusters.gpuInstances.staticAddress': 'Static Access Address',
|
'clusters.modelService.tip':
|
||||||
|
'For LLM inference and API serving — e.g. exposing model APIs and token-based services.',
|
||||||
|
'clusters.gpuInstances.title': 'GPU Service',
|
||||||
|
'clusters.gpuInstances.tip':
|
||||||
|
'For on-demand GPU compute — e.g. interactive development, training jobs, or custom environments.',
|
||||||
|
'clusters.gpuInstances.staticAddress': 'GPU Service Static Access Address',
|
||||||
'clusters.gpuInstances.staticAddress.tip':
|
'clusters.gpuInstances.staticAddress.tip':
|
||||||
'Static address the operator uses to access GPU instances in this cluster (e.g. a LoadBalancer VIP). Optional.'
|
'Static address the operator uses to access GPU instances in this cluster (e.g. a LoadBalancer VIP). Optional.'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export default {
|
|||||||
'common.button.enabled': '有効',
|
'common.button.enabled': '有効',
|
||||||
'common.button.disabled': '無効',
|
'common.button.disabled': '無効',
|
||||||
'common.button.upgrade': 'アップグレード',
|
'common.button.upgrade': 'アップグレード',
|
||||||
|
'common.enterprise.feature': 'Available in GPUStack Enterprise',
|
||||||
'common.input.holder': '入力してください',
|
'common.input.holder': '入力してください',
|
||||||
'common.validate.value': '{name} の値は必須です',
|
'common.validate.value': '{name} の値は必須です',
|
||||||
'common.button.edit': '編集',
|
'common.button.edit': '編集',
|
||||||
@@ -55,6 +56,7 @@ export default {
|
|||||||
'common.button.viewevent': 'イベントを表示',
|
'common.button.viewevent': 'イベントを表示',
|
||||||
'common.button.recreate': '再作成',
|
'common.button.recreate': '再作成',
|
||||||
'common.table.operation': '操作',
|
'common.table.operation': '操作',
|
||||||
|
'common.table.creator': '作成者',
|
||||||
'common.table.createTime': '作成日時',
|
'common.table.createTime': '作成日時',
|
||||||
'common.table.updateTime': '更新日時',
|
'common.table.updateTime': '更新日時',
|
||||||
'common.table.description': '説明',
|
'common.table.description': '説明',
|
||||||
@@ -65,6 +67,7 @@ export default {
|
|||||||
'common.search.name.placeholder': '名前でフィルタ',
|
'common.search.name.placeholder': '名前でフィルタ',
|
||||||
'common.search.id.placeholder': 'IDでフィルタ',
|
'common.search.id.placeholder': 'IDでフィルタ',
|
||||||
'common.filter.byId': 'IDでフィルタ',
|
'common.filter.byId': 'IDでフィルタ',
|
||||||
|
'common.filter.byCreator': '作成者でフィルタ',
|
||||||
'common.table.type': 'タイプ',
|
'common.table.type': 'タイプ',
|
||||||
'common.table.default': 'デフォルト値',
|
'common.table.default': 'デフォルト値',
|
||||||
'common.copy.success': 'コピー成功!',
|
'common.copy.success': 'コピー成功!',
|
||||||
@@ -167,6 +170,7 @@ export default {
|
|||||||
'common.time.hour': '時間',
|
'common.time.hour': '時間',
|
||||||
'common.time.minute': '分',
|
'common.time.minute': '分',
|
||||||
'common.issue.report': '問題を報告',
|
'common.issue.report': '問題を報告',
|
||||||
|
'common.github.star.tooltip': 'GitHub でスターをつける',
|
||||||
'common.social.discord': 'Discordに参加',
|
'common.social.discord': 'Discordに参加',
|
||||||
'common.table.mark': 'コメント',
|
'common.table.mark': 'コメント',
|
||||||
'common.table.rollback.mark': 'ロールバックコメント',
|
'common.table.rollback.mark': 'ロールバックコメント',
|
||||||
@@ -257,6 +261,10 @@ export default {
|
|||||||
'common.login.auth': 'Authenticating...',
|
'common.login.auth': 'Authenticating...',
|
||||||
'common.login.auth.failed': 'Authentication failed',
|
'common.login.auth.failed': 'Authentication failed',
|
||||||
'common.login.password': 'Log in with Password',
|
'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.external.login': 'Log in with {type}',
|
||||||
'common.sso.noConfig':
|
'common.sso.noConfig':
|
||||||
'Single sign-on is not enabled on this system. Please contact your administrator.',
|
'Single sign-on is not enabled on this system. Please contact your administrator.',
|
||||||
@@ -285,7 +293,9 @@ export default {
|
|||||||
'common.image.limit.height': 'Image height must be {height}.',
|
'common.image.limit.height': 'Image height must be {height}.',
|
||||||
'common.remaining': '残り {count}',
|
'common.remaining': '残り {count}',
|
||||||
'common.max': '最大 {count}',
|
'common.max': '最大 {count}',
|
||||||
'common.validate.group': 'Please complete the {group} configuration'
|
'common.max.count': '{label} 数',
|
||||||
|
'common.validate.group': 'Please complete the {group} configuration',
|
||||||
|
'common.preferences': 'Preferences'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ export default {
|
|||||||
'gpuservice.template.command.placeholder':
|
'gpuservice.template.command.placeholder':
|
||||||
'引数はスペースで区切り、スペースを含む引数は引用符で囲んでください。例:/bin/bash -c "echo hello world"',
|
'引数はスペースで区切り、スペースを含む引数は引用符で囲んでください。例:/bin/bash -c "echo hello world"',
|
||||||
'gpuservice.template.mountPath': 'マウントパス',
|
'gpuservice.template.mountPath': 'マウントパス',
|
||||||
|
'gpuservice.template.mountPath.tips':
|
||||||
|
'このテンプレートからインスタンスを作成する際に、ストレージボリュームがデフォルトでマウントされるパスです。インスタンスの実行中に保持する必要があるデータの永続化に使用できます。',
|
||||||
'gpuservice.template.containerDisk': 'コンテナディスク (GB)',
|
'gpuservice.template.containerDisk': 'コンテナディスク (GB)',
|
||||||
|
'gpuservice.template.containerDisk.tips':
|
||||||
|
'コンテナシステムディスクのサイズです。',
|
||||||
'gpuservice.template.memory': 'メモリ (GB)',
|
'gpuservice.template.memory': 'メモリ (GB)',
|
||||||
'gpuservice.instance.containerDisk.remaining':
|
'gpuservice.instance.containerDisk.remaining':
|
||||||
'コンテナディスク (最大 {count} GB)',
|
'コンテナディスク (最大 {count} GB)',
|
||||||
@@ -96,12 +100,18 @@ export default {
|
|||||||
'gpuservice.instance.templates': 'インスタンステンプレート',
|
'gpuservice.instance.templates': 'インスタンステンプレート',
|
||||||
'gpuservice.instance.section.storage': 'ストレージボリューム',
|
'gpuservice.instance.section.storage': 'ストレージボリューム',
|
||||||
'gpuservice.instance.type.required': 'インスタンスタイプを選択してください',
|
'gpuservice.instance.type.required': 'インスタンスタイプを選択してください',
|
||||||
|
'gpuservice.instance.type.noAvailable':
|
||||||
|
'利用可能なインスタンスタイプがありません',
|
||||||
'gpuservice.instance.gpuCount': 'GPU 数',
|
'gpuservice.instance.gpuCount': 'GPU 数',
|
||||||
'gpuservice.instance.gpuCount.required': 'GPU 数を入力してください',
|
'gpuservice.instance.gpuCount.required': 'GPU 数を入力してください',
|
||||||
'gpuservice.instance.gpuCount.max':
|
'gpuservice.instance.gpuCount.max':
|
||||||
'最大 {count} 枚の GPU カードを選択してください',
|
'最大 {count} 枚の GPU カードを選択してください',
|
||||||
'gpuservice.instance.gpuCount.min':
|
'gpuservice.instance.gpuCount.min':
|
||||||
'少なくとも {count} 枚の GPU カードを選択してください',
|
'少なくとも {count} 枚の GPU カードを選択してください',
|
||||||
|
'gpuservice.instance.cpuCount.max':
|
||||||
|
'最大 {count} 個の CPU コアを選択してください',
|
||||||
|
'gpuservice.instance.cpuCount.min':
|
||||||
|
'少なくとも {count} 個の CPU コアを選択してください',
|
||||||
'gpuservice.instance.gpuCount.noAvailable':
|
'gpuservice.instance.gpuCount.noAvailable':
|
||||||
'利用可能な GPU リソースがありません。別のインスタンスタイプを選択してください。',
|
'利用可能な GPU リソースがありません。別のインスタンスタイプを選択してください。',
|
||||||
'gpuservice.instance.gpuCount.zero': 'CPU のみを使用し、環境準備用です。',
|
'gpuservice.instance.gpuCount.zero': 'CPU のみを使用し、環境準備用です。',
|
||||||
@@ -109,7 +119,13 @@ export default {
|
|||||||
'gpuservice.instance.sliced': '分割',
|
'gpuservice.instance.sliced': '分割',
|
||||||
'gpuservice.instance.memory': 'VRAM',
|
'gpuservice.instance.memory': 'VRAM',
|
||||||
'gpuservice.instance.ram': 'RAM',
|
'gpuservice.instance.ram': 'RAM',
|
||||||
|
'gpuservice.instance.os': 'OS',
|
||||||
|
'gpuservice.instance.arch': 'アーキテクチャ',
|
||||||
'gpuservice.instance.disk': 'ディスク',
|
'gpuservice.instance.disk': 'ディスク',
|
||||||
|
'gpuservice.table.count': '数量',
|
||||||
|
'gpuservice.instance.disk.system': 'システムディスク',
|
||||||
|
'gpuservice.instance.disk.ephemeral': '一時ストレージ',
|
||||||
|
'gpuservice.instance.disk.persistent': '永続ストレージ',
|
||||||
'gpuservice.instance.search.type.placeholder': '名前で検索',
|
'gpuservice.instance.search.type.placeholder': '名前で検索',
|
||||||
'gpuservice.instance.search.template.placeholder':
|
'gpuservice.instance.search.template.placeholder':
|
||||||
'テンプレート名、イメージまたはマウントパスで検索',
|
'テンプレート名、イメージまたはマウントパスで検索',
|
||||||
@@ -143,7 +159,7 @@ export default {
|
|||||||
'gpuservice.storage.temporary': '一時',
|
'gpuservice.storage.temporary': '一時',
|
||||||
'gpuservice.storage.persistentVolume': '永続',
|
'gpuservice.storage.persistentVolume': '永続',
|
||||||
'gpuservice.storage.persistentVolume.required':
|
'gpuservice.storage.persistentVolume.required':
|
||||||
'永続ボリュームを選択してください',
|
'ストレージを選択してください',
|
||||||
'gpuservice.storage.persistentVolume.capacity': '容量 (GB)',
|
'gpuservice.storage.persistentVolume.capacity': '容量 (GB)',
|
||||||
'gpuservice.storage.persistentVolume.capacity.required':
|
'gpuservice.storage.persistentVolume.capacity.required':
|
||||||
'容量を入力してください',
|
'容量を入力してください',
|
||||||
@@ -157,5 +173,10 @@ export default {
|
|||||||
'gpuservice.storage.temporary.tips':
|
'gpuservice.storage.temporary.tips':
|
||||||
'Data is cleared when the instance stops.',
|
'Data is cleared when the instance stops.',
|
||||||
'gpuservice.storage.persistentVolume.tips':
|
'gpuservice.storage.persistentVolume.tips':
|
||||||
'Data persists across restarts and is deleted only when the instance is terminated. Cannot be shared with other instances.'
|
'Data persists across instance restarts. Persistent volumes remain intact after instance termination and can be shared by multiple instances.',
|
||||||
|
'gpuservice.form.storage.select': 'ストレージを選択',
|
||||||
|
'gpuservice.creator': '作成者',
|
||||||
|
'gpuservice.owner.global': 'グローバル',
|
||||||
|
'gpuservice.template.group.yours': '自分のテンプレート',
|
||||||
|
'gpuservice.template.group.global': 'グローバルテンプレート'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,9 +23,13 @@ export default {
|
|||||||
'menu.resources': 'リソース',
|
'menu.resources': 'リソース',
|
||||||
'menu.apikeys': 'APIキー',
|
'menu.apikeys': 'APIキー',
|
||||||
'menu.users': 'ユーザー',
|
'menu.users': 'ユーザー',
|
||||||
'menu.profile': 'プロフィール',
|
'menu.profile': 'Preferences',
|
||||||
'menu.login': 'ログイン',
|
'menu.login': 'ログイン',
|
||||||
'menu.usage': '使用状況',
|
'menu.usage': '使用状況',
|
||||||
|
'menu.usage.usage': '使用状況',
|
||||||
|
'menu.billingAndUsage': '使用状況と請求',
|
||||||
|
'menu.billingAndUsage.usage': '使用状況',
|
||||||
|
'menu.billingAndUsage.billing': '請求',
|
||||||
'menu.404': '404',
|
'menu.404': '404',
|
||||||
'menu.settings': 'Settings',
|
'menu.settings': 'Settings',
|
||||||
'menu.resources.workers': 'Workers',
|
'menu.resources.workers': 'Workers',
|
||||||
@@ -34,12 +38,12 @@ export default {
|
|||||||
'menu.accessControl': 'Access Control',
|
'menu.accessControl': 'Access Control',
|
||||||
'menu.accessControl.apikeys': 'API Keys',
|
'menu.accessControl.apikeys': 'API Keys',
|
||||||
'menu.accessControl.users': 'Users',
|
'menu.accessControl.users': 'Users',
|
||||||
'menu.clusterManagement': 'Cluster Management',
|
'menu.accessControl.organizations': 'Organizations',
|
||||||
'menu.clusterManagement.clusters': 'Clusters',
|
'menu.resources.clusters': 'Clusters',
|
||||||
'menu.clusterManagement.credentials': 'Cloud Credentials',
|
'menu.resources.credentials': 'Cloud Credentials',
|
||||||
'menu.models.userModels': 'My Models',
|
'menu.models.userModels': 'My Models',
|
||||||
'menu.clusterManagement.clusterDetail': 'Cluster Detail',
|
'menu.resources.clusterDetail': 'Cluster Detail',
|
||||||
'menu.clusterManagement.clusterCreate': 'Create Cluster',
|
'menu.resources.clusterCreate': 'Create Cluster',
|
||||||
'menu.models.backendsList': 'Inference Backends',
|
'menu.models.backendsList': 'Inference Backends',
|
||||||
'menu.gpuService': 'GPU Service',
|
'menu.gpuService': 'GPU Service',
|
||||||
'menu.gpuService.instances': 'GPU Instances',
|
'menu.gpuService.instances': 'GPU Instances',
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ export default {
|
|||||||
'models.instance.startHistory': 'Run History',
|
'models.instance.startHistory': 'Run History',
|
||||||
'models.instance.startHistory.tips':
|
'models.instance.startHistory.tips':
|
||||||
'Shows logs from the run before the last error-triggered restart.',
|
'Shows logs from the run before the last error-triggered restart.',
|
||||||
'models.form.lora.label': 'LoRA Adapter',
|
'models.form.lora.label': 'LoRA Adapters',
|
||||||
'models.form.lora.add': 'Add LoRA Adapter',
|
'models.form.lora.add': 'Add LoRA Adapter',
|
||||||
'models.form.lora.select': 'Select LoRA',
|
'models.form.lora.select': 'Select LoRA',
|
||||||
'models.form.lora.name': 'LoRA name',
|
'models.form.lora.name': 'LoRA name',
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export default {
|
||||||
|
'organizations.upsell.title': 'Organizations are an Enterprise feature',
|
||||||
|
'organizations.upsell.subtitle':
|
||||||
|
'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to GPUStack Enterprise to manage organizations.',
|
||||||
|
'organizations.upsell.featuresTitle': 'What you get in Enterprise',
|
||||||
|
'organizations.upsell.feature.orgs':
|
||||||
|
'Create organizations to group users and isolate workloads',
|
||||||
|
'organizations.upsell.feature.members':
|
||||||
|
'Manage members and roles per organization',
|
||||||
|
'organizations.upsell.feature.quotas':
|
||||||
|
'Set resource and token quotas per organization',
|
||||||
|
'organizations.upsell.feature.isolation':
|
||||||
|
'Scope API keys, model deployments, and resources per organization',
|
||||||
|
'organizations.upsell.cta': 'Learn about Enterprise'
|
||||||
|
};
|
||||||
@@ -17,9 +17,15 @@ export default {
|
|||||||
'usage.table.user.apiKeysUsed': 'API Keys Used',
|
'usage.table.user.apiKeysUsed': 'API Keys Used',
|
||||||
'usage.table.lastActive': 'Last Active',
|
'usage.table.lastActive': 'Last Active',
|
||||||
'usage.filter.granularity': 'Granularity',
|
'usage.filter.granularity': 'Granularity',
|
||||||
|
'usage.filter.granularity.hour': '時間',
|
||||||
'usage.filter.granularity.day': 'Day',
|
'usage.filter.granularity.day': 'Day',
|
||||||
'usage.filter.granularity.week': 'Week',
|
'usage.filter.granularity.week': 'Week',
|
||||||
'usage.filter.granularity.month': 'Month',
|
'usage.filter.granularity.month': 'Month',
|
||||||
|
'usage.tabs.summary': '概要',
|
||||||
|
'usage.tabs.tokens': 'トークン',
|
||||||
|
'usage.tabs.gpuInstances': 'GPU インスタンス',
|
||||||
|
'usage.tabs.storage': 'ストレージ',
|
||||||
|
'usage.tabs.resourceEvents': 'リソースイベント',
|
||||||
'usage.tabs.models': 'Models',
|
'usage.tabs.models': 'Models',
|
||||||
'usage.tabs.apikeys': 'API Keys',
|
'usage.tabs.apikeys': 'API Keys',
|
||||||
'usage.tabs.users': 'User',
|
'usage.tabs.users': 'User',
|
||||||
@@ -32,5 +38,70 @@ export default {
|
|||||||
'usage.chart.cached': 'Cached',
|
'usage.chart.cached': 'Cached',
|
||||||
'usage.chart.uncached': 'Uncached',
|
'usage.chart.uncached': 'Uncached',
|
||||||
'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)',
|
'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)',
|
||||||
'usage.table.inputTokensCached': 'Input Tokens Cached'
|
'usage.table.inputTokensCached': 'Input Tokens Cached',
|
||||||
|
|
||||||
|
// --- Resource usage: shared metrics & units ---
|
||||||
|
'usage.metric.tokens': 'トークン数',
|
||||||
|
'usage.metric.input': '入力',
|
||||||
|
'usage.metric.output': '出力',
|
||||||
|
'usage.metric.gpuHours': 'GPU 時間',
|
||||||
|
'usage.metric.instanceHours': 'インスタンス時間',
|
||||||
|
'usage.metric.gbDays': 'GB·日',
|
||||||
|
'usage.metric.gbHours': 'GB·時間',
|
||||||
|
'usage.metric.activeUsers': 'アクティブユーザー',
|
||||||
|
'usage.metric.activeInstances': 'アクティブインスタンス',
|
||||||
|
'usage.metric.activeStorage': 'アクティブストレージ',
|
||||||
|
'usage.metric.activeVolumes': 'アクティブボリューム',
|
||||||
|
'usage.metric.storageTypes': 'ストレージタイプ',
|
||||||
|
'usage.metric.gpuHours.tip':
|
||||||
|
'インスタンスの稼働時間を GPU 数で重み付けした値:N 個の GPU を使用するインスタンスが H 時間稼働すると N × H GPU 時間としてカウントされます。すべてのインスタンスが単一の GPU を使用する場合はインスタンス時間と等しくなります。',
|
||||||
|
'usage.metric.instanceHours.tip':
|
||||||
|
'GPU の数に関係なく、すべてのインスタンスの稼働時間を合計した値。1 つのインスタンスが 2 時間稼働 = 2 インスタンス時間。',
|
||||||
|
'usage.metric.gbDays.tip':
|
||||||
|
'ストレージ容量を時間で積分した値(GB × 日):10 GB を 5 日間保持 = 50 GB·日。(= GB·時間 ÷ 24)',
|
||||||
|
'usage.metric.gbHours.tip':
|
||||||
|
'ストレージ容量を時間で積分した値(GB × 時間):10 GB を 5 時間保持 = 50 GB·時間。',
|
||||||
|
|
||||||
|
// --- Resource usage: common table / labels ---
|
||||||
|
'usage.common.noData': 'データがありません',
|
||||||
|
'usage.common.unknown': '不明',
|
||||||
|
'usage.table.date': '日付',
|
||||||
|
'usage.table.name': '名前',
|
||||||
|
'usage.table.user': 'ユーザー',
|
||||||
|
'usage.table.users': 'ユーザー',
|
||||||
|
'usage.table.type': 'タイプ',
|
||||||
|
'usage.table.instance': 'インスタンス',
|
||||||
|
'usage.table.instanceType': 'インスタンスタイプ',
|
||||||
|
'usage.table.instanceTypes': 'インスタンスタイプ',
|
||||||
|
'usage.table.instances': 'インスタンス',
|
||||||
|
'usage.table.capacity': '容量',
|
||||||
|
'usage.export.tableNamed': 'テーブルデータをエクスポート — {name}',
|
||||||
|
|
||||||
|
// --- Summary tab ---
|
||||||
|
'usage.summary.compute': 'コンピュート',
|
||||||
|
'usage.summary.tokensOverTime': 'トークン数の推移',
|
||||||
|
'usage.summary.gpuHoursOverTime': 'GPU 時間の推移',
|
||||||
|
'usage.summary.gbDaysOverTime': 'GB·日の推移',
|
||||||
|
|
||||||
|
// --- GPU Instances / Storage filters ---
|
||||||
|
'usage.filter.instance': 'インスタンスで絞り込み',
|
||||||
|
'usage.filter.storage': 'ストレージで絞り込み',
|
||||||
|
|
||||||
|
// --- Resource events ---
|
||||||
|
'usage.events.resourceType': 'リソースタイプ',
|
||||||
|
'usage.events.eventType': 'イベントタイプ',
|
||||||
|
'usage.events.resourceName': '名前で絞り込み',
|
||||||
|
'usage.events.col.time': '時刻',
|
||||||
|
'usage.events.col.resource': 'リソース',
|
||||||
|
'usage.events.col.event': 'イベント',
|
||||||
|
'usage.events.col.message': 'メッセージ',
|
||||||
|
'usage.events.resource.gpuInstance': 'GPU インスタンス',
|
||||||
|
'usage.events.resource.cpuInstance': 'CPU インスタンス',
|
||||||
|
'usage.events.type.created': '作成済み',
|
||||||
|
'usage.events.type.deleted': '削除済み',
|
||||||
|
'usage.events.type.started': '開始',
|
||||||
|
'usage.events.type.stopped': '停止',
|
||||||
|
'usage.events.type.updated': '更新済み',
|
||||||
|
'usage.events.type.attached': 'アタッチ済み',
|
||||||
|
'usage.events.type.detached': 'デタッチ済み'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export default {
|
|||||||
'vendor.hygon': 'Hygon',
|
'vendor.hygon': 'Hygon',
|
||||||
'vendor.moorthreads': 'Moore Threads',
|
'vendor.moorthreads': 'Moore Threads',
|
||||||
'vendor.iluvatar': 'Iluvatar',
|
'vendor.iluvatar': 'Iluvatar',
|
||||||
'vendor.metax': 'Metax',
|
'vendor.metax': 'MetaX',
|
||||||
'vendor.cambricon': 'Cambricon',
|
'vendor.cambricon': 'Cambricon',
|
||||||
'vendor.thead': 'T-Head PPU'
|
'vendor.thead': 'T-Head PPU'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default {
|
|||||||
'ai.provider.ollama': 'Ollama',
|
'ai.provider.ollama': 'Ollama',
|
||||||
'ai.provider.openai': 'OpenAI',
|
'ai.provider.openai': 'OpenAI',
|
||||||
'ai.provider.openrouter': 'OpenRouter',
|
'ai.provider.openrouter': 'OpenRouter',
|
||||||
'ai.provider.qwen': 'Qwen',
|
'ai.provider.qwen': 'Alibaba Cloud Model Studio',
|
||||||
'ai.provider.spark': 'Spark',
|
'ai.provider.spark': 'Spark',
|
||||||
'ai.provider.stepfun': 'StepFun',
|
'ai.provider.stepfun': 'StepFun',
|
||||||
'ai.provider.together-ai': 'TogetherAI',
|
'ai.provider.together-ai': 'TogetherAI',
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ export default {
|
|||||||
'apikeys.accessScope.inference': 'Inference APIs',
|
'apikeys.accessScope.inference': 'Inference APIs',
|
||||||
'apikeys.access.permissions': 'Access Permissions',
|
'apikeys.access.permissions': 'Access Permissions',
|
||||||
'apikeys.type.auto': 'Auto-generated',
|
'apikeys.type.auto': 'Auto-generated',
|
||||||
'apikeys.type.custom': 'Custom'
|
'apikeys.type.custom': 'Custom',
|
||||||
|
'apikeys.button.ipConfig': 'IP Access Control',
|
||||||
|
'quotaLimits.button.title': 'Quota Limit'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ export default {
|
|||||||
'backend.form.defaultExecuteCommand': 'Команда выполнения по умолчанию',
|
'backend.form.defaultExecuteCommand': 'Команда выполнения по умолчанию',
|
||||||
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' и '{{'model_name'}}' заполняются реальными значениями во время запуска`,
|
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' и '{{'model_name'}}' заполняются реальными значениями во время запуска`,
|
||||||
'backend.form.defaultBackendParameters': 'Параметры бэкенда по умолчанию',
|
'backend.form.defaultBackendParameters': 'Параметры бэкенда по умолчанию',
|
||||||
'backend.form.parameterFormat': 'Формат параметров',
|
'backend.form.flagFormat': 'Формат флага',
|
||||||
'backend.form.parameterFormat.default': 'По умолчанию бэкенда',
|
'backend.form.flagFormat.tips':
|
||||||
'backend.form.parameterFormat.space': 'Пробел (--key value)',
|
'Формат соединения опции и её значения. Оставьте пустым, чтобы сохранить каждый параметр в исходном виде, без приведения к единому формату.',
|
||||||
'backend.form.parameterFormat.equal': 'Знак равенства (--key=value)',
|
'backend.form.flagFormat.space': 'Разделение пробелом (--key value)',
|
||||||
'backend.form.commonParameters': 'Часто используемые параметры',
|
'backend.form.flagFormat.equal': 'Знак равенства (--key=value)',
|
||||||
|
'backend.form.commonParameters': 'Общие параметры бэкенда',
|
||||||
'backend.form.commonParameters.tips':
|
'backend.form.commonParameters.tips':
|
||||||
'Отображаются как подсказки в поле параметров бэкенда при развёртывании.',
|
'Отображаются как подсказки в поле параметров бэкенда при развёртывании.',
|
||||||
'backend.form.versionConfig': 'Конфигурация версий',
|
'backend.form.versionConfig': 'Конфигурация версий',
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ export default {
|
|||||||
'Stress test for long-context handling. Evaluates KV cache behavior, memory usage, and backend stability.',
|
'Stress test for long-context handling. Evaluates KV cache behavior, memory usage, and backend stability.',
|
||||||
'benchmark.form.profile.heavy.tips':
|
'benchmark.form.profile.heavy.tips':
|
||||||
'Decode-heavy generation benchmark. Measures sustained decoding speed and output token throughput.',
|
'Decode-heavy generation benchmark. Measures sustained decoding speed and output token throughput.',
|
||||||
'benchmark.table.filter.bygpu': 'Filter by GPU',
|
'benchmark.table.filter.bygpu': 'Поиск GPU',
|
||||||
'benchmark.table.filter.bymodel': 'Filter by Model',
|
'benchmark.table.filter.bymodel': 'Поиск модели',
|
||||||
'benchmark.table.filter.bydataset': 'Filter by Dataset',
|
'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.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'
|
||||||
|
};
|
||||||
@@ -39,7 +39,7 @@ export default {
|
|||||||
'clusters.workerpool.batchSize.desc':
|
'clusters.workerpool.batchSize.desc':
|
||||||
'Количество воркеров, создаваемых одновременно в пуле воркеров',
|
'Количество воркеров, создаваемых одновременно в пуле воркеров',
|
||||||
'clusters.create.addworker.tips':
|
'clusters.create.addworker.tips':
|
||||||
'Пожалуйста, убедитесь, что выполнены <a href={link} target="_blank">предварительные условия</a> для {label} перед выполнением следующей команды.',
|
'Пожалуйста, убедитесь, что выполнены <a href={link} target="_blank">предварительные условия</a> перед выполнением следующей команды.',
|
||||||
'clusters.create.addCommand.tips':
|
'clusters.create.addCommand.tips':
|
||||||
'На воркере, который необходимо добавить, выполните следующую команду, чтобы присоединить его к кластеру.',
|
'На воркере, который необходимо добавить, выполните следующую команду, чтобы присоединить его к кластеру.',
|
||||||
'clusters.create.addCommand.k8s.tips':
|
'clusters.create.addCommand.k8s.tips':
|
||||||
@@ -70,8 +70,13 @@ export default {
|
|||||||
'Для <span class="bold-text">не-Docker</span> кластеров, пожалуйста, регистрируйте кластеры или управляйте пулами воркеров на странице Кластеры.',
|
'Для <span class="bold-text">не-Docker</span> кластеров, пожалуйста, регистрируйте кластеры или управляйте пулами воркеров на странице Кластеры.',
|
||||||
'clusters.addworker.selectGPU': 'Выбрать производителя GPU',
|
'clusters.addworker.selectGPU': 'Выбрать производителя GPU',
|
||||||
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
|
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
|
||||||
|
'clusters.addworker.selectGPU.subtitle':
|
||||||
|
'Вы можете выбрать несколько производителей GPU или не выбирать для кластера только с CPU',
|
||||||
'clusters.addworker.checkEnv': 'Проверить окружение',
|
'clusters.addworker.checkEnv': 'Проверить окружение',
|
||||||
|
'clusters.addworker.checkEnv.cpuOnlyTips':
|
||||||
|
'Используйте следующую команду, чтобы убедиться, что в кластере Kubernetes есть хотя бы один готовый узел. Вы регистрируете кластер только с CPU.',
|
||||||
'clusters.addworker.specifyArgs': 'Указать аргументы',
|
'clusters.addworker.specifyArgs': 'Указать аргументы',
|
||||||
|
'clusters.addworker.dtkVersion': 'Версия DTK',
|
||||||
'clusters.addworker.runCommand': 'Выполнить команду',
|
'clusters.addworker.runCommand': 'Выполнить команду',
|
||||||
'clusters.addworker.specifyWorkerIP': 'Указать IP воркера',
|
'clusters.addworker.specifyWorkerIP': 'Указать IP воркера',
|
||||||
'clusters.addworker.detectWorkerIP': 'Автоматически определить IP воркера',
|
'clusters.addworker.detectWorkerIP': 'Автоматически определить IP воркера',
|
||||||
@@ -110,6 +115,8 @@ export default {
|
|||||||
'{count} новых воркеров были добавлены в кластер.',
|
'{count} новых воркеров были добавлены в кластер.',
|
||||||
'clusters.create.serverUrl': 'URL сервера GPUStack',
|
'clusters.create.serverUrl': 'URL сервера GPUStack',
|
||||||
'clusters.create.workerConfig': 'Конфигурация воркера',
|
'clusters.create.workerConfig': 'Конфигурация воркера',
|
||||||
|
'clusters.edit.k8sOptions.changed.tip':
|
||||||
|
'Вы изменили параметры Kubernetes. Чтобы изменения вступили в силу, повторно выполните команду регистрации в целевом кластере.',
|
||||||
'clusters.addworker.containerName': 'Имя контейнера воркера',
|
'clusters.addworker.containerName': 'Имя контейнера воркера',
|
||||||
'clusters.addworker.containerName.tips':
|
'clusters.addworker.containerName.tips':
|
||||||
'Укажите имя для контейнера воркера.',
|
'Укажите имя для контейнера воркера.',
|
||||||
@@ -138,7 +145,7 @@ export default {
|
|||||||
'clusters.addworker.theadNotes-02':
|
'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.',
|
'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':
|
'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.title': 'Volume Mounts',
|
||||||
'clusters.volume.name': 'Volume Name',
|
'clusters.volume.name': 'Volume Name',
|
||||||
'clusters.volume.mountPath': 'Container Path',
|
'clusters.volume.mountPath': 'Container Path',
|
||||||
@@ -166,7 +173,7 @@ export default {
|
|||||||
'clusters.systemDefaultContainerRegistry.title': 'Default Container Registry',
|
'clusters.systemDefaultContainerRegistry.title': 'Default Container Registry',
|
||||||
'clusters.systemDefaultContainerRegistry.tip':
|
'clusters.systemDefaultContainerRegistry.tip':
|
||||||
'Default registry used to resolve GPUStack images for this cluster. Falls back to the server default when unset.',
|
'Default registry used to resolve GPUStack images for this cluster. Falls back to the server default when unset.',
|
||||||
'clusters.k8sOptions.title': 'K8s Deployment Options',
|
'clusters.k8sOptions.title': 'Kubernetes Deployment Options',
|
||||||
'clusters.imageCredentials.title': 'Image Credentials',
|
'clusters.imageCredentials.title': 'Image Credentials',
|
||||||
'clusters.imageCredentials.add': 'Add Credential',
|
'clusters.imageCredentials.add': 'Add Credential',
|
||||||
'clusters.imageCredentials.registry': 'Registry',
|
'clusters.imageCredentials.registry': 'Registry',
|
||||||
@@ -181,9 +188,14 @@ export default {
|
|||||||
'clusters.namespace.title': 'Namespace',
|
'clusters.namespace.title': 'Namespace',
|
||||||
'clusters.namespace.tip':
|
'clusters.namespace.tip':
|
||||||
'Kubernetes namespace the cluster’s manifests render into. Leave empty to use gpustack-system.',
|
'Kubernetes namespace the cluster’s manifests render into. Leave empty to use gpustack-system.',
|
||||||
'clusters.gpuInstances.title': 'GPU Instances',
|
'clusters.clusterType.title': 'Cluster Type',
|
||||||
'clusters.gpuInstances.tip': 'Enable GPU instance support for this cluster.',
|
'clusters.modelService.title': 'Model Service',
|
||||||
'clusters.gpuInstances.staticAddress': 'Static Access Address',
|
'clusters.modelService.tip':
|
||||||
|
'For LLM inference and API serving — e.g. exposing model APIs and token-based services.',
|
||||||
|
'clusters.gpuInstances.title': 'GPU Service',
|
||||||
|
'clusters.gpuInstances.tip':
|
||||||
|
'For on-demand GPU compute — e.g. interactive development, training jobs, or custom environments.',
|
||||||
|
'clusters.gpuInstances.staticAddress': 'GPU Service Static Access Address',
|
||||||
'clusters.gpuInstances.staticAddress.tip':
|
'clusters.gpuInstances.staticAddress.tip':
|
||||||
'Static address the operator uses to access GPU instances in this cluster (e.g. a LoadBalancer VIP). Optional.'
|
'Static address the operator uses to access GPU instances in this cluster (e.g. a LoadBalancer VIP). Optional.'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export default {
|
|||||||
'common.button.enabled': 'Активно',
|
'common.button.enabled': 'Активно',
|
||||||
'common.button.disabled': 'Отключено',
|
'common.button.disabled': 'Отключено',
|
||||||
'common.button.upgrade': 'Обновить',
|
'common.button.upgrade': 'Обновить',
|
||||||
|
'common.enterprise.feature': 'Available in GPUStack Enterprise',
|
||||||
'common.input.holder': 'Введите значение',
|
'common.input.holder': 'Введите значение',
|
||||||
'common.validate.value': 'Поле {name} обязательно',
|
'common.validate.value': 'Поле {name} обязательно',
|
||||||
'common.button.edit': 'Редактировать',
|
'common.button.edit': 'Редактировать',
|
||||||
@@ -55,6 +56,7 @@ export default {
|
|||||||
'common.button.viewevent': 'Просмотр событий',
|
'common.button.viewevent': 'Просмотр событий',
|
||||||
'common.button.recreate': 'Пересоздать',
|
'common.button.recreate': 'Пересоздать',
|
||||||
'common.table.operation': 'Действия',
|
'common.table.operation': 'Действия',
|
||||||
|
'common.table.creator': 'Создатель',
|
||||||
'common.table.createTime': 'Создано',
|
'common.table.createTime': 'Создано',
|
||||||
'common.table.updateTime': 'Обновлено',
|
'common.table.updateTime': 'Обновлено',
|
||||||
'common.table.description': 'Описание',
|
'common.table.description': 'Описание',
|
||||||
@@ -65,6 +67,7 @@ export default {
|
|||||||
'common.search.name.placeholder': 'Фильтр по названию',
|
'common.search.name.placeholder': 'Фильтр по названию',
|
||||||
'common.search.id.placeholder': 'Фильтр по ID',
|
'common.search.id.placeholder': 'Фильтр по ID',
|
||||||
'common.filter.byId': 'Фильтр по ID',
|
'common.filter.byId': 'Фильтр по ID',
|
||||||
|
'common.filter.byCreator': 'Фильтр по создателю',
|
||||||
'common.table.type': 'Тип',
|
'common.table.type': 'Тип',
|
||||||
'common.table.default': 'Значение по умолчанию',
|
'common.table.default': 'Значение по умолчанию',
|
||||||
'common.copy.success': 'Скопировано!',
|
'common.copy.success': 'Скопировано!',
|
||||||
@@ -164,6 +167,7 @@ export default {
|
|||||||
'common.time.hour': 'Час',
|
'common.time.hour': 'Час',
|
||||||
'common.time.minute': 'Минута',
|
'common.time.minute': 'Минута',
|
||||||
'common.issue.report': 'Сообщить о проблеме',
|
'common.issue.report': 'Сообщить о проблеме',
|
||||||
|
'common.github.star.tooltip': 'Поставьте нам звезду на GitHub',
|
||||||
'common.social.discord': 'Присоединиться к Discord',
|
'common.social.discord': 'Присоединиться к Discord',
|
||||||
'common.table.mark': 'Комментарий',
|
'common.table.mark': 'Комментарий',
|
||||||
'common.table.rollback.mark': 'Комментарий к откату',
|
'common.table.rollback.mark': 'Комментарий к откату',
|
||||||
@@ -256,11 +260,15 @@ export default {
|
|||||||
'common.login.auth': 'Аутентификация...',
|
'common.login.auth': 'Аутентификация...',
|
||||||
'common.login.auth.failed': 'Ошибка аутентификации',
|
'common.login.auth.failed': 'Ошибка аутентификации',
|
||||||
'common.login.password': 'Войти с паролем',
|
'common.login.password': 'Войти с паролем',
|
||||||
|
'common.login.username.holder': 'Введите имя пользователя',
|
||||||
|
'common.login.password.holder': 'Введите пароль',
|
||||||
|
'common.login.newpassword.holder': 'Введите новый пароль',
|
||||||
|
'common.login.confirm.holder': 'Введите пароль ещё раз',
|
||||||
'common.external.login': 'Войти через {type}',
|
'common.external.login': 'Войти через {type}',
|
||||||
'common.sso.noConfig':
|
'common.sso.noConfig':
|
||||||
'Единый вход не настроен в этой системе. Пожалуйста, обратитесь к администратору.',
|
'Единый вход не настроен в этой системе. Пожалуйста, обратитесь к администратору.',
|
||||||
'common.button.edit.item': 'Редактировать {name}',
|
'common.button.edit.item': 'Редактировать {name}',
|
||||||
'common.button.copy.item': 'Duplicate {name}',
|
'common.button.copy.item': 'Дублировать {name}',
|
||||||
'common.button.terminal': 'Терминал',
|
'common.button.terminal': 'Терминал',
|
||||||
'common.button.addItem': 'Добавить элемент',
|
'common.button.addItem': 'Добавить элемент',
|
||||||
'common.help.default': 'По умолчанию: {content}',
|
'common.help.default': 'По умолчанию: {content}',
|
||||||
@@ -284,7 +292,9 @@ export default {
|
|||||||
'common.image.limit.height': 'Image height must be {height}.',
|
'common.image.limit.height': 'Image height must be {height}.',
|
||||||
'common.remaining': 'Остаток {count}',
|
'common.remaining': 'Остаток {count}',
|
||||||
'common.max': 'Макс. {count}',
|
'common.max': 'Макс. {count}',
|
||||||
'common.validate.group': 'Please complete the {group} configuration'
|
'common.max.count': 'Количество {label}',
|
||||||
|
'common.validate.group': 'Please complete the {group} configuration',
|
||||||
|
'common.preferences': 'Preferences'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
|||||||
@@ -14,7 +14,11 @@ export default {
|
|||||||
'gpuservice.template.command.placeholder':
|
'gpuservice.template.command.placeholder':
|
||||||
'Разделяйте аргументы пробелами; аргументы с пробелами заключайте в кавычки, например: /bin/bash -c "echo hello world"',
|
'Разделяйте аргументы пробелами; аргументы с пробелами заключайте в кавычки, например: /bin/bash -c "echo hello world"',
|
||||||
'gpuservice.template.mountPath': 'Путь монтирования',
|
'gpuservice.template.mountPath': 'Путь монтирования',
|
||||||
|
'gpuservice.template.mountPath.tips':
|
||||||
|
'Путь, по которому том хранилища монтируется по умолчанию при создании экземпляра из этого шаблона. Может использоваться для сохранения данных, которые нужно сохранить во время работы экземпляра.',
|
||||||
'gpuservice.template.containerDisk': 'Диск контейнера (GB)',
|
'gpuservice.template.containerDisk': 'Диск контейнера (GB)',
|
||||||
|
'gpuservice.template.containerDisk.tips':
|
||||||
|
'Размер системного диска контейнера.',
|
||||||
'gpuservice.template.memory': 'Память (GB)',
|
'gpuservice.template.memory': 'Память (GB)',
|
||||||
'gpuservice.instance.containerDisk.remaining':
|
'gpuservice.instance.containerDisk.remaining':
|
||||||
'Диск контейнера (Макс. {count} GB)',
|
'Диск контейнера (Макс. {count} GB)',
|
||||||
@@ -100,10 +104,13 @@ export default {
|
|||||||
'gpuservice.instance.templates': 'Шаблоны экземпляров',
|
'gpuservice.instance.templates': 'Шаблоны экземпляров',
|
||||||
'gpuservice.instance.section.storage': 'Том хранилища',
|
'gpuservice.instance.section.storage': 'Том хранилища',
|
||||||
'gpuservice.instance.type.required': 'Выберите тип экземпляра',
|
'gpuservice.instance.type.required': 'Выберите тип экземпляра',
|
||||||
|
'gpuservice.instance.type.noAvailable': 'Нет доступных типов экземпляров',
|
||||||
'gpuservice.instance.gpuCount': 'Количество GPU',
|
'gpuservice.instance.gpuCount': 'Количество GPU',
|
||||||
'gpuservice.instance.gpuCount.required': 'Введите количество GPU',
|
'gpuservice.instance.gpuCount.required': 'Введите количество GPU',
|
||||||
'gpuservice.instance.gpuCount.max': 'Выберите максимум {count} GPU-карт',
|
'gpuservice.instance.gpuCount.max': 'Выберите максимум {count} GPU-карт',
|
||||||
'gpuservice.instance.gpuCount.min': 'Выберите минимум {count} GPU-карт',
|
'gpuservice.instance.gpuCount.min': 'Выберите минимум {count} GPU-карт',
|
||||||
|
'gpuservice.instance.cpuCount.max': 'Выберите максимум {count} ядер CPU',
|
||||||
|
'gpuservice.instance.cpuCount.min': 'Выберите минимум {count} ядер CPU',
|
||||||
'gpuservice.instance.gpuCount.noAvailable':
|
'gpuservice.instance.gpuCount.noAvailable':
|
||||||
'Нет доступных ресурсов GPU, выберите другой тип экземпляра.',
|
'Нет доступных ресурсов GPU, выберите другой тип экземпляра.',
|
||||||
'gpuservice.instance.gpuCount.zero': 'Только CPU, для подготовки окружения.',
|
'gpuservice.instance.gpuCount.zero': 'Только CPU, для подготовки окружения.',
|
||||||
@@ -111,7 +118,13 @@ export default {
|
|||||||
'gpuservice.instance.sliced': 'Разделено',
|
'gpuservice.instance.sliced': 'Разделено',
|
||||||
'gpuservice.instance.memory': 'VRAM',
|
'gpuservice.instance.memory': 'VRAM',
|
||||||
'gpuservice.instance.ram': 'RAM',
|
'gpuservice.instance.ram': 'RAM',
|
||||||
|
'gpuservice.instance.os': 'ОС',
|
||||||
|
'gpuservice.instance.arch': 'Архитектура',
|
||||||
'gpuservice.instance.disk': 'Диск',
|
'gpuservice.instance.disk': 'Диск',
|
||||||
|
'gpuservice.table.count': 'Количество',
|
||||||
|
'gpuservice.instance.disk.system': 'Системный диск',
|
||||||
|
'gpuservice.instance.disk.ephemeral': 'Временное хранилище',
|
||||||
|
'gpuservice.instance.disk.persistent': 'Постоянное хранилище',
|
||||||
'gpuservice.instance.search.type.placeholder': 'Поиск по имени',
|
'gpuservice.instance.search.type.placeholder': 'Поиск по имени',
|
||||||
'gpuservice.instance.search.template.placeholder':
|
'gpuservice.instance.search.template.placeholder':
|
||||||
'Поиск по имени шаблона, образу или пути монтирования',
|
'Поиск по имени шаблона, образу или пути монтирования',
|
||||||
@@ -144,7 +157,7 @@ export default {
|
|||||||
'gpuservice.storage.persistent': 'Постоянное',
|
'gpuservice.storage.persistent': 'Постоянное',
|
||||||
'gpuservice.storage.temporary': 'Временное',
|
'gpuservice.storage.temporary': 'Временное',
|
||||||
'gpuservice.storage.persistentVolume': 'Постоянное',
|
'gpuservice.storage.persistentVolume': 'Постоянное',
|
||||||
'gpuservice.storage.persistentVolume.required': 'Выберите постоянный том',
|
'gpuservice.storage.persistentVolume.required': 'Выберите хранилище',
|
||||||
'gpuservice.storage.persistentVolume.capacity': 'Ёмкость (ГБ)',
|
'gpuservice.storage.persistentVolume.capacity': 'Ёмкость (ГБ)',
|
||||||
'gpuservice.storage.persistentVolume.capacity.required': 'Введите ёмкость',
|
'gpuservice.storage.persistentVolume.capacity.required': 'Введите ёмкость',
|
||||||
'gpuservice.storage.persistentVolume.releaseWithInstance':
|
'gpuservice.storage.persistentVolume.releaseWithInstance':
|
||||||
@@ -157,5 +170,10 @@ export default {
|
|||||||
'gpuservice.storage.temporary.tips':
|
'gpuservice.storage.temporary.tips':
|
||||||
'Data is cleared when the instance stops.',
|
'Data is cleared when the instance stops.',
|
||||||
'gpuservice.storage.persistentVolume.tips':
|
'gpuservice.storage.persistentVolume.tips':
|
||||||
'Data persists across restarts and is deleted only when the instance is terminated. Cannot be shared with other instances.'
|
'Data persists across instance restarts. Persistent volumes remain intact after instance termination and can be shared by multiple instances.',
|
||||||
|
'gpuservice.form.storage.select': 'Выберите хранилище',
|
||||||
|
'gpuservice.creator': 'Создатель',
|
||||||
|
'gpuservice.owner.global': 'Глобальный',
|
||||||
|
'gpuservice.template.group.yours': 'Ваши шаблоны',
|
||||||
|
'gpuservice.template.group.global': 'Глобальные шаблоны'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,9 +23,13 @@ export default {
|
|||||||
'menu.resources': 'Ресурсы',
|
'menu.resources': 'Ресурсы',
|
||||||
'menu.apikeys': 'API-ключи',
|
'menu.apikeys': 'API-ключи',
|
||||||
'menu.users': 'Пользователи',
|
'menu.users': 'Пользователи',
|
||||||
'menu.profile': 'Профиль',
|
'menu.profile': 'Preferences',
|
||||||
'menu.login': 'Авторизация',
|
'menu.login': 'Авторизация',
|
||||||
'menu.usage': 'Использование',
|
'menu.usage': 'Использование',
|
||||||
|
'menu.usage.usage': 'Использование',
|
||||||
|
'menu.billingAndUsage': 'Использование и биллинг',
|
||||||
|
'menu.billingAndUsage.usage': 'Использование',
|
||||||
|
'menu.billingAndUsage.billing': 'Биллинг',
|
||||||
'menu.404': 'Ошибка 404',
|
'menu.404': 'Ошибка 404',
|
||||||
'menu.resources.workers': 'Воркеры',
|
'menu.resources.workers': 'Воркеры',
|
||||||
'menu.resources.gpus': 'GPUs',
|
'menu.resources.gpus': 'GPUs',
|
||||||
@@ -33,12 +37,12 @@ export default {
|
|||||||
'menu.accessControl': 'Управление доступом',
|
'menu.accessControl': 'Управление доступом',
|
||||||
'menu.accessControl.apikeys': 'API Ключи',
|
'menu.accessControl.apikeys': 'API Ключи',
|
||||||
'menu.accessControl.users': 'Пользователи',
|
'menu.accessControl.users': 'Пользователи',
|
||||||
'menu.clusterManagement': 'Управление кластерами',
|
'menu.accessControl.organizations': 'Организации',
|
||||||
'menu.clusterManagement.clusters': 'Кластеры',
|
'menu.resources.clusters': 'Кластеры',
|
||||||
'menu.clusterManagement.credentials': 'Облачные аккаунты',
|
'menu.resources.credentials': 'Облачные аккаунты',
|
||||||
'menu.models.userModels': 'Мои модели',
|
'menu.models.userModels': 'Мои модели',
|
||||||
'menu.clusterManagement.clusterDetail': 'Детали кластера',
|
'menu.resources.clusterDetail': 'Детали кластера',
|
||||||
'menu.clusterManagement.clusterCreate': 'Создать кластер',
|
'menu.resources.clusterCreate': 'Создать кластер',
|
||||||
'menu.models.backendsList': 'Бэкенды запуска',
|
'menu.models.backendsList': 'Бэкенды запуска',
|
||||||
'menu.settings': 'Settings',
|
'menu.settings': 'Settings',
|
||||||
'menu.gpuService': 'GPU Service',
|
'menu.gpuService': 'GPU Service',
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ export default {
|
|||||||
'models.instance.startHistory': 'Run History',
|
'models.instance.startHistory': 'Run History',
|
||||||
'models.instance.startHistory.tips':
|
'models.instance.startHistory.tips':
|
||||||
'Shows logs from the run before the last error-triggered restart.',
|
'Shows logs from the run before the last error-triggered restart.',
|
||||||
'models.form.lora.label': 'LoRA Adapter',
|
'models.form.lora.label': 'LoRA Adapters',
|
||||||
'models.form.lora.add': 'Add LoRA Adapter',
|
'models.form.lora.add': 'Add LoRA Adapter',
|
||||||
'models.form.lora.select': 'Select LoRA',
|
'models.form.lora.select': 'Select LoRA',
|
||||||
'models.form.lora.name': 'LoRA name',
|
'models.form.lora.name': 'LoRA name',
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export default {
|
||||||
|
'organizations.upsell.title': 'Organizations are an Enterprise feature',
|
||||||
|
'organizations.upsell.subtitle':
|
||||||
|
'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to GPUStack Enterprise to manage organizations.',
|
||||||
|
'organizations.upsell.featuresTitle': 'What you get in Enterprise',
|
||||||
|
'organizations.upsell.feature.orgs':
|
||||||
|
'Create organizations to group users and isolate workloads',
|
||||||
|
'organizations.upsell.feature.members':
|
||||||
|
'Manage members and roles per organization',
|
||||||
|
'organizations.upsell.feature.quotas':
|
||||||
|
'Set resource and token quotas per organization',
|
||||||
|
'organizations.upsell.feature.isolation':
|
||||||
|
'Scope API keys, model deployments, and resources per organization',
|
||||||
|
'organizations.upsell.cta': 'Learn about Enterprise'
|
||||||
|
};
|
||||||
@@ -17,9 +17,15 @@ export default {
|
|||||||
'usage.table.user.apiKeysUsed': 'API Keys Used',
|
'usage.table.user.apiKeysUsed': 'API Keys Used',
|
||||||
'usage.table.lastActive': 'Last Active',
|
'usage.table.lastActive': 'Last Active',
|
||||||
'usage.filter.granularity': 'Granularity',
|
'usage.filter.granularity': 'Granularity',
|
||||||
|
'usage.filter.granularity.hour': 'Час',
|
||||||
'usage.filter.granularity.day': 'Day',
|
'usage.filter.granularity.day': 'Day',
|
||||||
'usage.filter.granularity.week': 'Week',
|
'usage.filter.granularity.week': 'Week',
|
||||||
'usage.filter.granularity.month': 'Month',
|
'usage.filter.granularity.month': 'Month',
|
||||||
|
'usage.tabs.summary': 'Обзор',
|
||||||
|
'usage.tabs.tokens': 'Токены',
|
||||||
|
'usage.tabs.gpuInstances': 'GPU-инстансы',
|
||||||
|
'usage.tabs.storage': 'Хранилище',
|
||||||
|
'usage.tabs.resourceEvents': 'События ресурсов',
|
||||||
'usage.tabs.models': 'Models',
|
'usage.tabs.models': 'Models',
|
||||||
'usage.tabs.apikeys': 'API Keys',
|
'usage.tabs.apikeys': 'API Keys',
|
||||||
'usage.tabs.users': 'User',
|
'usage.tabs.users': 'User',
|
||||||
@@ -32,5 +38,70 @@ export default {
|
|||||||
'usage.chart.cached': 'Cached',
|
'usage.chart.cached': 'Cached',
|
||||||
'usage.chart.uncached': 'Uncached',
|
'usage.chart.uncached': 'Uncached',
|
||||||
'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)',
|
'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)',
|
||||||
'usage.table.inputTokensCached': 'Input Tokens Cached'
|
'usage.table.inputTokensCached': 'Input Tokens Cached',
|
||||||
|
|
||||||
|
// --- Resource usage: shared metrics & units ---
|
||||||
|
'usage.metric.tokens': 'Токены',
|
||||||
|
'usage.metric.input': 'Вход',
|
||||||
|
'usage.metric.output': 'Выход',
|
||||||
|
'usage.metric.gpuHours': 'GPU-часы',
|
||||||
|
'usage.metric.instanceHours': 'Часы инстансов',
|
||||||
|
'usage.metric.gbDays': 'ГБ-дни',
|
||||||
|
'usage.metric.gbHours': 'ГБ-часы',
|
||||||
|
'usage.metric.activeUsers': 'Активные пользователи',
|
||||||
|
'usage.metric.activeInstances': 'Активные инстансы',
|
||||||
|
'usage.metric.activeStorage': 'Активное хранилище',
|
||||||
|
'usage.metric.activeVolumes': 'Активные тома',
|
||||||
|
'usage.metric.storageTypes': 'Типы хранилищ',
|
||||||
|
'usage.metric.gpuHours.tip':
|
||||||
|
'Время работы инстанса, взвешенное по количеству GPU: инстанс с N GPU, работающий H часов, считается как N × H GPU-часов. Равно часам инстансов, когда каждый инстанс использует один GPU.',
|
||||||
|
'usage.metric.instanceHours.tip':
|
||||||
|
'Суммарное время работы всех инстансов независимо от количества используемых GPU. Один инстанс, работающий 2 часа = 2 часа инстанса.',
|
||||||
|
'usage.metric.gbDays.tip':
|
||||||
|
'Ёмкость хранилища, проинтегрированная по времени, в ГБ × дни: 10 ГБ в течение 5 дней = 50 ГБ-дней. (= ГБ-часы ÷ 24)',
|
||||||
|
'usage.metric.gbHours.tip':
|
||||||
|
'Ёмкость хранилища, проинтегрированная по времени, в ГБ × часы: 10 ГБ в течение 5 часов = 50 ГБ-часов.',
|
||||||
|
|
||||||
|
// --- Resource usage: common table / labels ---
|
||||||
|
'usage.common.noData': 'Нет данных',
|
||||||
|
'usage.common.unknown': 'неизвестно',
|
||||||
|
'usage.table.date': 'Дата',
|
||||||
|
'usage.table.name': 'Имя',
|
||||||
|
'usage.table.user': 'Пользователь',
|
||||||
|
'usage.table.users': 'Пользователи',
|
||||||
|
'usage.table.type': 'Тип',
|
||||||
|
'usage.table.instance': 'Инстанс',
|
||||||
|
'usage.table.instanceType': 'Тип инстанса',
|
||||||
|
'usage.table.instanceTypes': 'Типы инстансов',
|
||||||
|
'usage.table.instances': 'Инстансы',
|
||||||
|
'usage.table.capacity': 'Ёмкость',
|
||||||
|
'usage.export.tableNamed': 'Экспорт данных таблицы — {name}',
|
||||||
|
|
||||||
|
// --- Summary tab ---
|
||||||
|
'usage.summary.compute': 'Вычисления',
|
||||||
|
'usage.summary.tokensOverTime': 'Токены по времени',
|
||||||
|
'usage.summary.gpuHoursOverTime': 'GPU-часы по времени',
|
||||||
|
'usage.summary.gbDaysOverTime': 'ГБ-дни по времени',
|
||||||
|
|
||||||
|
// --- GPU Instances / Storage filters ---
|
||||||
|
'usage.filter.instance': 'Фильтр по инстансу',
|
||||||
|
'usage.filter.storage': 'Фильтр по хранилищу',
|
||||||
|
|
||||||
|
// --- Resource events ---
|
||||||
|
'usage.events.resourceType': 'Тип ресурса',
|
||||||
|
'usage.events.eventType': 'Тип события',
|
||||||
|
'usage.events.resourceName': 'Фильтр по имени',
|
||||||
|
'usage.events.col.time': 'Время',
|
||||||
|
'usage.events.col.resource': 'Ресурс',
|
||||||
|
'usage.events.col.event': 'Событие',
|
||||||
|
'usage.events.col.message': 'Сообщение',
|
||||||
|
'usage.events.resource.gpuInstance': 'GPU-инстанс',
|
||||||
|
'usage.events.resource.cpuInstance': 'CPU-инстанс',
|
||||||
|
'usage.events.type.created': 'Создан',
|
||||||
|
'usage.events.type.deleted': 'Удалён',
|
||||||
|
'usage.events.type.started': 'Запущен',
|
||||||
|
'usage.events.type.stopped': 'Остановлен',
|
||||||
|
'usage.events.type.updated': 'Обновлён',
|
||||||
|
'usage.events.type.attached': 'Подключён',
|
||||||
|
'usage.events.type.detached': 'Отключён'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export default {
|
|||||||
'vendor.hygon': 'Hygon',
|
'vendor.hygon': 'Hygon',
|
||||||
'vendor.moorthreads': 'Moore Threads',
|
'vendor.moorthreads': 'Moore Threads',
|
||||||
'vendor.iluvatar': 'Iluvatar',
|
'vendor.iluvatar': 'Iluvatar',
|
||||||
'vendor.metax': 'Metax',
|
'vendor.metax': 'MetaX',
|
||||||
'vendor.cambricon': 'Cambricon',
|
'vendor.cambricon': 'Cambricon',
|
||||||
'vendor.thead': 'T-Head PPU'
|
'vendor.thead': 'T-Head PPU'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default {
|
|||||||
'ai.provider.ollama': 'Ollama',
|
'ai.provider.ollama': 'Ollama',
|
||||||
'ai.provider.openai': 'OpenAI',
|
'ai.provider.openai': 'OpenAI',
|
||||||
'ai.provider.openrouter': 'OpenRouter',
|
'ai.provider.openrouter': 'OpenRouter',
|
||||||
'ai.provider.qwen': 'Qwen',
|
'ai.provider.qwen': 'Alibaba Cloud Model Studio',
|
||||||
'ai.provider.spark': 'Spark',
|
'ai.provider.spark': 'Spark',
|
||||||
'ai.provider.stepfun': 'StepFun',
|
'ai.provider.stepfun': 'StepFun',
|
||||||
'ai.provider.together-ai': 'TogetherAI',
|
'ai.provider.together-ai': 'TogetherAI',
|
||||||
|
|||||||
@@ -23,5 +23,7 @@ export default {
|
|||||||
'apikeys.accessScope.inference': 'Inference APIs',
|
'apikeys.accessScope.inference': 'Inference APIs',
|
||||||
'apikeys.access.permissions': 'Access Permissions',
|
'apikeys.access.permissions': 'Access Permissions',
|
||||||
'apikeys.type.auto': 'Auto-generated',
|
'apikeys.type.auto': 'Auto-generated',
|
||||||
'apikeys.type.custom': 'Custom'
|
'apikeys.type.custom': 'Custom',
|
||||||
|
'apikeys.button.ipConfig': 'IP Access Control',
|
||||||
|
'quotaLimits.button.title': 'Quota Limit'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ export default {
|
|||||||
'backend.form.defaultExecuteCommand': 'Varsayılan Çalıştırma Komutu',
|
'backend.form.defaultExecuteCommand': 'Varsayılan Çalıştırma Komutu',
|
||||||
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' ve '{{'model_name'}}' dağıtım sırasında gerçek değerlerle değiştirilecek yer tutuculardır.`,
|
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}', '{{'port'}}', '{{'worker_ip'}}' ve '{{'model_name'}}' dağıtım sırasında gerçek değerlerle değiştirilecek yer tutuculardır.`,
|
||||||
'backend.form.defaultBackendParameters': 'Varsayılan Altyapı Parametreleri',
|
'backend.form.defaultBackendParameters': 'Varsayılan Altyapı Parametreleri',
|
||||||
'backend.form.parameterFormat': 'Parametre Biçimi',
|
'backend.form.flagFormat': 'Bayrak Biçimi',
|
||||||
'backend.form.parameterFormat.default': 'Varsayılan',
|
'backend.form.flagFormat.tips':
|
||||||
'backend.form.parameterFormat.space': 'Boşluk (--key value)',
|
'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.parameterFormat.equal': 'Eşittir (--key=value)',
|
'backend.form.flagFormat.space': 'Boşlukla Ayır (--key value)',
|
||||||
'backend.form.commonParameters': 'Yaygın Parametreler',
|
'backend.form.flagFormat.equal': 'Eşittir İşareti (--key=value)',
|
||||||
|
'backend.form.commonParameters': 'Ortak Altyapı Parametreleri',
|
||||||
'backend.form.commonParameters.tips':
|
'backend.form.commonParameters.tips':
|
||||||
'Dağıtım sırasında altyapı parametreleri girişinde öneri olarak gösterilir.',
|
'Dağıtım sırasında altyapı parametreleri girişinde öneri olarak gösterilir.',
|
||||||
'backend.form.versionConfig': 'Sürüm Yapılandırması',
|
'backend.form.versionConfig': 'Sürüm Yapılandırması',
|
||||||
|
|||||||
@@ -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.',
|
'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':
|
'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.',
|
'Çö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.bygpu': 'GPU ara',
|
||||||
'benchmark.table.filter.bymodel': 'Modele göre filtrele',
|
'benchmark.table.filter.bymodel': 'Model ara',
|
||||||
'benchmark.table.filter.bydataset': 'Veri Kümesine göre filtrele',
|
'benchmark.table.filter.bydataset': 'Veri Kümesine göre filtrele',
|
||||||
'benchmark.table.filter.byProfile': 'Profile göre filtrele',
|
'benchmark.table.filter.byProfile': 'Profile göre filtrele',
|
||||||
'benchmark.table.avg': 'Ort.',
|
'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'
|
||||||
|
};
|
||||||
@@ -39,7 +39,7 @@ export default {
|
|||||||
'clusters.workerpool.batchSize.desc':
|
'clusters.workerpool.batchSize.desc':
|
||||||
'İşçi havuzunda eşzamanlı olarak oluşturulan işçi düğüm sayısı',
|
'İşçi havuzunda eşzamanlı olarak oluşturulan işçi düğüm sayısı',
|
||||||
'clusters.create.addworker.tips':
|
'clusters.create.addworker.tips':
|
||||||
'Aşağıdaki komutu çalıştırmadan önce lütfen {label} için <a href={link} target="_blank">ön koşulların</a> karşılandığından emin olun.',
|
'Aşağıdaki komutu çalıştırmadan önce lütfen <a href={link} target="_blank">ön koşulların</a> karşılandığından emin olun.',
|
||||||
'clusters.create.addCommand.tips':
|
'clusters.create.addCommand.tips':
|
||||||
'Eklenmesi gereken İşçi Düğümde, kümeye katılması için aşağıdaki komutu çalıştırın.',
|
'Eklenmesi gereken İşçi Düğümde, kümeye katılması için aşağıdaki komutu çalıştırın.',
|
||||||
'clusters.create.addCommand.k8s.tips':
|
'clusters.create.addCommand.k8s.tips':
|
||||||
@@ -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.',
|
'<span class="bold-text">Docker dışı</span> kümeler için lütfen Kümeler sayfasından küme kaydı oluşturun veya işçi havuzlarını yönetin.',
|
||||||
'clusters.addworker.selectGPU': 'GPU Üreticisi Seç',
|
'clusters.addworker.selectGPU': 'GPU Üreticisi Seç',
|
||||||
'clusters.addworker.selectGPU.multiTag': 'Multi-select',
|
'clusters.addworker.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': '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.specifyArgs': 'Argümanları Belirle',
|
||||||
|
'clusters.addworker.dtkVersion': 'DTK Sürümü',
|
||||||
'clusters.addworker.runCommand': 'Komutu Çalıştır',
|
'clusters.addworker.runCommand': 'Komutu Çalıştır',
|
||||||
'clusters.addworker.specifyWorkerIP': "İşçi Düğüm IP'si",
|
'clusters.addworker.specifyWorkerIP': "İşçi Düğüm IP'si",
|
||||||
'clusters.addworker.detectWorkerIP': "İşçi Düğüm IP'sini Otomatik Algıla",
|
'clusters.addworker.detectWorkerIP': "İşçi Düğüm IP'sini Otomatik Algıla",
|
||||||
@@ -110,6 +115,8 @@ export default {
|
|||||||
'{count} yeni işçi düğüm kümeye eklendi.',
|
'{count} yeni işçi düğüm kümeye eklendi.',
|
||||||
'clusters.create.serverUrl': "GPUStack Sunucu URL'si",
|
'clusters.create.serverUrl': "GPUStack Sunucu URL'si",
|
||||||
'clusters.create.workerConfig': 'İşçi Düğüm Yapılandırması',
|
'clusters.create.workerConfig': 'İşçi Düğüm Yapılandırması',
|
||||||
|
'clusters.edit.k8sOptions.changed.tip':
|
||||||
|
'Kubernetes seçeneklerini değiştirdiniz. Değişikliklerin etkili olması için kayıt komutunu hedef kümede yeniden çalıştırın.',
|
||||||
'clusters.addworker.containerName': 'İşçi Düğüm Konteyner Adı',
|
'clusters.addworker.containerName': 'İşçi Düğüm Konteyner Adı',
|
||||||
'clusters.addworker.containerName.tips':
|
'clusters.addworker.containerName.tips':
|
||||||
'İşçi düğüm konteyneri için bir ad belirtin.',
|
'İşçi düğüm konteyneri için bir ad belirtin.',
|
||||||
@@ -138,7 +145,7 @@ export default {
|
|||||||
'clusters.addworker.theadNotes-02':
|
'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.',
|
'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':
|
'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.title': 'Volume Mounts',
|
||||||
'clusters.volume.name': 'Volume Name',
|
'clusters.volume.name': 'Volume Name',
|
||||||
'clusters.volume.mountPath': 'Container Path',
|
'clusters.volume.mountPath': 'Container Path',
|
||||||
@@ -166,7 +173,7 @@ export default {
|
|||||||
'clusters.systemDefaultContainerRegistry.title': 'Default Container Registry',
|
'clusters.systemDefaultContainerRegistry.title': 'Default Container Registry',
|
||||||
'clusters.systemDefaultContainerRegistry.tip':
|
'clusters.systemDefaultContainerRegistry.tip':
|
||||||
'Default registry used to resolve GPUStack images for this cluster. Falls back to the server default when unset.',
|
'Default registry used to resolve GPUStack images for this cluster. Falls back to the server default when unset.',
|
||||||
'clusters.k8sOptions.title': 'K8s Deployment Options',
|
'clusters.k8sOptions.title': 'Kubernetes Deployment Options',
|
||||||
'clusters.imageCredentials.title': 'Image Credentials',
|
'clusters.imageCredentials.title': 'Image Credentials',
|
||||||
'clusters.imageCredentials.add': 'Add Credential',
|
'clusters.imageCredentials.add': 'Add Credential',
|
||||||
'clusters.imageCredentials.registry': 'Registry',
|
'clusters.imageCredentials.registry': 'Registry',
|
||||||
@@ -181,9 +188,14 @@ export default {
|
|||||||
'clusters.namespace.title': 'Namespace',
|
'clusters.namespace.title': 'Namespace',
|
||||||
'clusters.namespace.tip':
|
'clusters.namespace.tip':
|
||||||
'Kubernetes namespace the cluster’s manifests render into. Leave empty to use gpustack-system.',
|
'Kubernetes namespace the cluster’s manifests render into. Leave empty to use gpustack-system.',
|
||||||
'clusters.gpuInstances.title': 'GPU Instances',
|
'clusters.clusterType.title': 'Cluster Type',
|
||||||
'clusters.gpuInstances.tip': 'Enable GPU instance support for this cluster.',
|
'clusters.modelService.title': 'Model Service',
|
||||||
'clusters.gpuInstances.staticAddress': 'Static Access Address',
|
'clusters.modelService.tip':
|
||||||
|
'For LLM inference and API serving — e.g. exposing model APIs and token-based services.',
|
||||||
|
'clusters.gpuInstances.title': 'GPU Service',
|
||||||
|
'clusters.gpuInstances.tip':
|
||||||
|
'For on-demand GPU compute — e.g. interactive development, training jobs, or custom environments.',
|
||||||
|
'clusters.gpuInstances.staticAddress': 'GPU Service Static Access Address',
|
||||||
'clusters.gpuInstances.staticAddress.tip':
|
'clusters.gpuInstances.staticAddress.tip':
|
||||||
'Static address the operator uses to access GPU instances in this cluster (e.g. a LoadBalancer VIP). Optional.'
|
'Static address the operator uses to access GPU instances in this cluster (e.g. a LoadBalancer VIP). Optional.'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export default {
|
|||||||
'common.button.enabled': 'Etkin',
|
'common.button.enabled': 'Etkin',
|
||||||
'common.button.disabled': 'Devre dışı',
|
'common.button.disabled': 'Devre dışı',
|
||||||
'common.button.upgrade': 'Yükselt',
|
'common.button.upgrade': 'Yükselt',
|
||||||
|
'common.enterprise.feature': 'Available in GPUStack Enterprise',
|
||||||
'common.input.holder': 'Lütfen girin',
|
'common.input.holder': 'Lütfen girin',
|
||||||
'common.validate.value': '{name} değeri gereklidir',
|
'common.validate.value': '{name} değeri gereklidir',
|
||||||
'common.button.edit': 'Düzenle',
|
'common.button.edit': 'Düzenle',
|
||||||
@@ -55,6 +56,7 @@ export default {
|
|||||||
'common.button.viewevent': 'Olayları Görüntüle',
|
'common.button.viewevent': 'Olayları Görüntüle',
|
||||||
'common.button.recreate': 'Yeniden Oluştur',
|
'common.button.recreate': 'Yeniden Oluştur',
|
||||||
'common.table.operation': 'İşlemler',
|
'common.table.operation': 'İşlemler',
|
||||||
|
'common.table.creator': 'Oluşturan',
|
||||||
'common.table.createTime': 'Oluşturulma',
|
'common.table.createTime': 'Oluşturulma',
|
||||||
'common.table.updateTime': 'Güncellenme',
|
'common.table.updateTime': 'Güncellenme',
|
||||||
'common.table.description': 'Açıklama',
|
'common.table.description': 'Açıklama',
|
||||||
@@ -65,6 +67,7 @@ export default {
|
|||||||
'common.search.name.placeholder': 'ada göre filtrele',
|
'common.search.name.placeholder': 'ada göre filtrele',
|
||||||
'common.search.id.placeholder': 'kimliğe göre filtrele',
|
'common.search.id.placeholder': 'kimliğe göre filtrele',
|
||||||
'common.filter.byId': 'kimliğe göre filtrele',
|
'common.filter.byId': 'kimliğe göre filtrele',
|
||||||
|
'common.filter.byCreator': 'Oluşturana göre filtrele',
|
||||||
'common.table.type': 'Tür',
|
'common.table.type': 'Tür',
|
||||||
'common.table.default': 'Varsayılan Değer',
|
'common.table.default': 'Varsayılan Değer',
|
||||||
'common.copy.success': 'Kopyalama başarılı!',
|
'common.copy.success': 'Kopyalama başarılı!',
|
||||||
@@ -164,6 +167,7 @@ export default {
|
|||||||
'common.time.hour': 'saat',
|
'common.time.hour': 'saat',
|
||||||
'common.time.minute': 'dakika',
|
'common.time.minute': 'dakika',
|
||||||
'common.issue.report': 'Sorun bildir',
|
'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.social.discord': "Discord'umuza Katılın",
|
||||||
'common.table.mark': 'Yorum',
|
'common.table.mark': 'Yorum',
|
||||||
'common.table.rollback.mark': 'Geri Alma Yorumu',
|
'common.table.rollback.mark': 'Geri Alma Yorumu',
|
||||||
@@ -260,6 +264,10 @@ export default {
|
|||||||
'common.login.auth': 'Kimlik doğrulanıyor...',
|
'common.login.auth': 'Kimlik doğrulanıyor...',
|
||||||
'common.login.auth.failed': 'Kimlik doğrulama başarısız',
|
'common.login.auth.failed': 'Kimlik doğrulama başarısız',
|
||||||
'common.login.password': 'Şifre ile giriş yap',
|
'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.external.login': '{type} ile giriş yap',
|
||||||
'common.sso.noConfig':
|
'common.sso.noConfig':
|
||||||
'Bu sistemde çoklu oturum açma etkinleştirilmemiş. Lütfen yöneticinize başvurun.',
|
'Bu sistemde çoklu oturum açma etkinleştirilmemiş. Lütfen yöneticinize başvurun.',
|
||||||
@@ -288,5 +296,7 @@ export default {
|
|||||||
'common.image.limit.height': 'Image height must be {height}.',
|
'common.image.limit.height': 'Image height must be {height}.',
|
||||||
'common.remaining': 'Kalan {count}',
|
'common.remaining': 'Kalan {count}',
|
||||||
'common.max': 'Maks. {count}',
|
'common.max': 'Maks. {count}',
|
||||||
'common.validate.group': 'Please complete the {group} configuration'
|
'common.max.count': '{label} Sayısı',
|
||||||
|
'common.validate.group': 'Please complete the {group} configuration',
|
||||||
|
'common.preferences': 'Tercihler'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,7 +13,10 @@ export default {
|
|||||||
'gpuservice.template.command.placeholder':
|
'gpuservice.template.command.placeholder':
|
||||||
'Argümanları boşlukla ayırın; boşluk içeren argümanları tırnak içine alın, örn.: /bin/bash -c "echo hello world"',
|
'Argümanları boşlukla ayırın; boşluk içeren argümanları tırnak içine alın, örn.: /bin/bash -c "echo hello world"',
|
||||||
'gpuservice.template.mountPath': 'Bağlama Yolu',
|
'gpuservice.template.mountPath': 'Bağlama Yolu',
|
||||||
|
'gpuservice.template.mountPath.tips':
|
||||||
|
'Bu şablondan bir örnek oluşturulurken depolama biriminin varsayılan olarak bağlanacağı yol. Örnek çalışırken saklanması gereken verileri kalıcı hale getirmek için kullanılabilir.',
|
||||||
'gpuservice.template.containerDisk': 'Konteyner Diski (GB)',
|
'gpuservice.template.containerDisk': 'Konteyner Diski (GB)',
|
||||||
|
'gpuservice.template.containerDisk.tips': 'Konteyner sistem diskinin boyutu.',
|
||||||
'gpuservice.template.memory': 'Bellek (GB)',
|
'gpuservice.template.memory': 'Bellek (GB)',
|
||||||
'gpuservice.instance.containerDisk.remaining':
|
'gpuservice.instance.containerDisk.remaining':
|
||||||
'Konteyner Diski (Maks. {count} GB)',
|
'Konteyner Diski (Maks. {count} GB)',
|
||||||
@@ -97,10 +100,13 @@ export default {
|
|||||||
'gpuservice.instance.templates': 'Örnek Şablonları',
|
'gpuservice.instance.templates': 'Örnek Şablonları',
|
||||||
'gpuservice.instance.section.storage': 'Depolama Hacmi',
|
'gpuservice.instance.section.storage': 'Depolama Hacmi',
|
||||||
'gpuservice.instance.type.required': 'Lütfen bir örnek türü seçin',
|
'gpuservice.instance.type.required': 'Lütfen bir örnek türü seçin',
|
||||||
|
'gpuservice.instance.type.noAvailable': 'Kullanılabilir örnek türü yok',
|
||||||
'gpuservice.instance.gpuCount': 'GPU Sayısı',
|
'gpuservice.instance.gpuCount': 'GPU Sayısı',
|
||||||
'gpuservice.instance.gpuCount.required': 'Lütfen GPU sayısını girin',
|
'gpuservice.instance.gpuCount.required': 'Lütfen GPU sayısını girin',
|
||||||
'gpuservice.instance.gpuCount.max': 'En fazla {count} GPU kartı seçin',
|
'gpuservice.instance.gpuCount.max': 'En fazla {count} GPU kartı seçin',
|
||||||
'gpuservice.instance.gpuCount.min': 'En az {count} GPU kartı seçin',
|
'gpuservice.instance.gpuCount.min': 'En az {count} GPU kartı seçin',
|
||||||
|
'gpuservice.instance.cpuCount.max': 'En fazla {count} CPU çekirdeği seçin',
|
||||||
|
'gpuservice.instance.cpuCount.min': 'En az {count} CPU çekirdeği seçin',
|
||||||
'gpuservice.instance.gpuCount.noAvailable':
|
'gpuservice.instance.gpuCount.noAvailable':
|
||||||
'Kullanılabilir GPU kaynağı yok, lütfen başka bir örnek türü seçin.',
|
'Kullanılabilir GPU kaynağı yok, lütfen başka bir örnek türü seçin.',
|
||||||
'gpuservice.instance.gpuCount.zero': 'Yalnızca CPU, ortam hazırlığı için.',
|
'gpuservice.instance.gpuCount.zero': 'Yalnızca CPU, ortam hazırlığı için.',
|
||||||
@@ -108,7 +114,13 @@ export default {
|
|||||||
'gpuservice.instance.sliced': 'Bölünmüş',
|
'gpuservice.instance.sliced': 'Bölünmüş',
|
||||||
'gpuservice.instance.memory': 'VRAM',
|
'gpuservice.instance.memory': 'VRAM',
|
||||||
'gpuservice.instance.ram': 'RAM',
|
'gpuservice.instance.ram': 'RAM',
|
||||||
|
'gpuservice.instance.os': 'OS',
|
||||||
|
'gpuservice.instance.arch': 'Mimari',
|
||||||
'gpuservice.instance.disk': 'Disk',
|
'gpuservice.instance.disk': 'Disk',
|
||||||
|
'gpuservice.table.count': 'Sayı',
|
||||||
|
'gpuservice.instance.disk.system': 'Sistem Diski',
|
||||||
|
'gpuservice.instance.disk.ephemeral': 'Geçici Depolama',
|
||||||
|
'gpuservice.instance.disk.persistent': 'Kalıcı Depolama',
|
||||||
'gpuservice.instance.search.type.placeholder': 'Ada göre ara',
|
'gpuservice.instance.search.type.placeholder': 'Ada göre ara',
|
||||||
'gpuservice.instance.search.template.placeholder':
|
'gpuservice.instance.search.template.placeholder':
|
||||||
'Şablon adına, imaja veya bağlama yoluna göre ara',
|
'Şablon adına, imaja veya bağlama yoluna göre ara',
|
||||||
@@ -142,8 +154,7 @@ export default {
|
|||||||
'gpuservice.storage.persistent': 'Kalıcı',
|
'gpuservice.storage.persistent': 'Kalıcı',
|
||||||
'gpuservice.storage.temporary': 'Geçici',
|
'gpuservice.storage.temporary': 'Geçici',
|
||||||
'gpuservice.storage.persistentVolume': 'Kalıcı',
|
'gpuservice.storage.persistentVolume': 'Kalıcı',
|
||||||
'gpuservice.storage.persistentVolume.required':
|
'gpuservice.storage.persistentVolume.required': 'Lütfen bir depolama seçin',
|
||||||
'Lütfen bir kalıcı hacim seçin',
|
|
||||||
'gpuservice.storage.persistentVolume.capacity': 'Kapasite (GB)',
|
'gpuservice.storage.persistentVolume.capacity': 'Kapasite (GB)',
|
||||||
'gpuservice.storage.persistentVolume.capacity.required':
|
'gpuservice.storage.persistentVolume.capacity.required':
|
||||||
'Lütfen kapasiteyi girin',
|
'Lütfen kapasiteyi girin',
|
||||||
@@ -157,5 +168,10 @@ export default {
|
|||||||
'gpuservice.storage.temporary.tips':
|
'gpuservice.storage.temporary.tips':
|
||||||
'Data is cleared when the instance stops.',
|
'Data is cleared when the instance stops.',
|
||||||
'gpuservice.storage.persistentVolume.tips':
|
'gpuservice.storage.persistentVolume.tips':
|
||||||
'Data persists across restarts and is deleted only when the instance is terminated. Cannot be shared with other instances.'
|
'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.creator': 'Oluşturan',
|
||||||
|
'gpuservice.owner.global': 'Genel',
|
||||||
|
'gpuservice.template.group.yours': 'Şablonlarınız',
|
||||||
|
'gpuservice.template.group.global': 'Genel Şablonlar'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,15 +29,19 @@ export default {
|
|||||||
'menu.accessControl': 'Erişim Kontrolü',
|
'menu.accessControl': 'Erişim Kontrolü',
|
||||||
'menu.accessControl.apikeys': 'API Anahtarları',
|
'menu.accessControl.apikeys': 'API Anahtarları',
|
||||||
'menu.accessControl.users': 'Kullanıcılar',
|
'menu.accessControl.users': 'Kullanıcılar',
|
||||||
'menu.profile': 'Profil',
|
'menu.accessControl.organizations': 'Organizasyonlar',
|
||||||
|
'menu.profile': 'Preferences',
|
||||||
'menu.login': 'Giriş',
|
'menu.login': 'Giriş',
|
||||||
'menu.usage': 'Kullanım',
|
'menu.usage': 'Kullanım',
|
||||||
|
'menu.usage.usage': 'Kullanım',
|
||||||
|
'menu.billingAndUsage': 'Kullanım ve Faturalandırma',
|
||||||
|
'menu.billingAndUsage.usage': 'Kullanım',
|
||||||
|
'menu.billingAndUsage.billing': 'Faturalandırma',
|
||||||
'menu.404': '404',
|
'menu.404': '404',
|
||||||
'menu.clusterManagement': 'Küme Yönetimi',
|
'menu.resources.clusters': 'Kümeler',
|
||||||
'menu.clusterManagement.clusters': 'Kümeler',
|
'menu.resources.credentials': 'Bulut Kimlik Bilgileri',
|
||||||
'menu.clusterManagement.credentials': 'Bulut Kimlik Bilgileri',
|
'menu.resources.clusterDetail': 'Küme Detayı',
|
||||||
'menu.clusterManagement.clusterDetail': 'Küme Detayı',
|
'menu.resources.clusterCreate': 'Küme Oluştur',
|
||||||
'menu.clusterManagement.clusterCreate': 'Küme Oluştur',
|
|
||||||
'menu.models.backendsList': 'Çıkarım Altyapıları',
|
'menu.models.backendsList': 'Çıkarım Altyapıları',
|
||||||
'menu.models.instances': 'Instances',
|
'menu.models.instances': 'Instances',
|
||||||
'menu.settings': 'Settings',
|
'menu.settings': 'Settings',
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ export default {
|
|||||||
'models.instance.startHistory': 'Run History',
|
'models.instance.startHistory': 'Run History',
|
||||||
'models.instance.startHistory.tips':
|
'models.instance.startHistory.tips':
|
||||||
'Shows logs from the run before the last error-triggered restart.',
|
'Shows logs from the run before the last error-triggered restart.',
|
||||||
'models.form.lora.label': 'LoRA Adapter',
|
'models.form.lora.label': 'LoRA Adapters',
|
||||||
'models.form.lora.add': 'Add LoRA Adapter',
|
'models.form.lora.add': 'Add LoRA Adapter',
|
||||||
'models.form.lora.select': 'Select LoRA',
|
'models.form.lora.select': 'Select LoRA',
|
||||||
'models.form.lora.name': 'LoRA name',
|
'models.form.lora.name': 'LoRA name',
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export default {
|
||||||
|
'organizations.upsell.title': 'Organizations are an Enterprise feature',
|
||||||
|
'organizations.upsell.subtitle':
|
||||||
|
'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to GPUStack Enterprise to manage organizations.',
|
||||||
|
'organizations.upsell.featuresTitle': 'What you get in Enterprise',
|
||||||
|
'organizations.upsell.feature.orgs':
|
||||||
|
'Create organizations to group users and isolate workloads',
|
||||||
|
'organizations.upsell.feature.members':
|
||||||
|
'Manage members and roles per organization',
|
||||||
|
'organizations.upsell.feature.quotas':
|
||||||
|
'Set resource and token quotas per organization',
|
||||||
|
'organizations.upsell.feature.isolation':
|
||||||
|
'Scope API keys, model deployments, and resources per organization',
|
||||||
|
'organizations.upsell.cta': 'Learn about Enterprise'
|
||||||
|
};
|
||||||
@@ -17,9 +17,15 @@ export default {
|
|||||||
'usage.table.user.apiKeysUsed': 'API Keys Used',
|
'usage.table.user.apiKeysUsed': 'API Keys Used',
|
||||||
'usage.table.lastActive': 'Last Active',
|
'usage.table.lastActive': 'Last Active',
|
||||||
'usage.filter.granularity': 'Granularity',
|
'usage.filter.granularity': 'Granularity',
|
||||||
|
'usage.filter.granularity.hour': 'Saat',
|
||||||
'usage.filter.granularity.day': 'Day',
|
'usage.filter.granularity.day': 'Day',
|
||||||
'usage.filter.granularity.week': 'Week',
|
'usage.filter.granularity.week': 'Week',
|
||||||
'usage.filter.granularity.month': 'Month',
|
'usage.filter.granularity.month': 'Month',
|
||||||
|
'usage.tabs.summary': 'Özet',
|
||||||
|
'usage.tabs.tokens': 'Token',
|
||||||
|
'usage.tabs.gpuInstances': 'GPU Örnekleri',
|
||||||
|
'usage.tabs.storage': 'Depolama',
|
||||||
|
'usage.tabs.resourceEvents': 'Kaynak Olayları',
|
||||||
'usage.tabs.models': 'Models',
|
'usage.tabs.models': 'Models',
|
||||||
'usage.tabs.apikeys': 'API Keys',
|
'usage.tabs.apikeys': 'API Keys',
|
||||||
'usage.tabs.users': 'User',
|
'usage.tabs.users': 'User',
|
||||||
@@ -32,5 +38,70 @@ export default {
|
|||||||
'usage.chart.cached': 'Cached',
|
'usage.chart.cached': 'Cached',
|
||||||
'usage.chart.uncached': 'Uncached',
|
'usage.chart.uncached': 'Uncached',
|
||||||
'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)',
|
'usage.chart.inputTokensCached': 'Input Tokens (Cached/Uncached)',
|
||||||
'usage.table.inputTokensCached': 'Input Tokens Cached'
|
'usage.table.inputTokensCached': 'Input Tokens Cached',
|
||||||
|
|
||||||
|
// --- Resource usage: shared metrics & units ---
|
||||||
|
'usage.metric.tokens': 'Token',
|
||||||
|
'usage.metric.input': 'Girdi',
|
||||||
|
'usage.metric.output': 'Çıktı',
|
||||||
|
'usage.metric.gpuHours': 'GPU Saati',
|
||||||
|
'usage.metric.instanceHours': 'Örnek Saati',
|
||||||
|
'usage.metric.gbDays': 'GB-Gün',
|
||||||
|
'usage.metric.gbHours': 'GB-Saat',
|
||||||
|
'usage.metric.activeUsers': 'Aktif Kullanıcılar',
|
||||||
|
'usage.metric.activeInstances': 'Aktif Örnekler',
|
||||||
|
'usage.metric.activeStorage': 'Aktif Depolama',
|
||||||
|
'usage.metric.activeVolumes': 'Aktif Birimler',
|
||||||
|
'usage.metric.storageTypes': 'Depolama Türleri',
|
||||||
|
'usage.metric.gpuHours.tip':
|
||||||
|
'Örnek çalışma süresinin GPU sayısına göre ağırlıklandırılmış hali: N GPU kullanan bir örnek H saat çalıştığında N × H GPU-saat olarak sayılır. Her örnek tek bir GPU kullandığında Örnek Saati ile eşittir.',
|
||||||
|
'usage.metric.instanceHours.tip':
|
||||||
|
'Her örneğin kullandığı GPU sayısından bağımsız olarak tüm örneklerin toplam çalışma süresi. 2 saat çalışan bir örnek = 2 örnek-saat.',
|
||||||
|
'usage.metric.gbDays.tip':
|
||||||
|
'Depolama kapasitesinin zamana göre integrali, GB × gün cinsinden: 5 gün boyunca tutulan 10 GB = 50 GB-gün. (= GB-Saat ÷ 24)',
|
||||||
|
'usage.metric.gbHours.tip':
|
||||||
|
'Depolama kapasitesinin zamana göre integrali, GB × saat cinsinden: 5 saat boyunca tutulan 10 GB = 50 GB-saat.',
|
||||||
|
|
||||||
|
// --- Resource usage: common table / labels ---
|
||||||
|
'usage.common.noData': 'Veri yok',
|
||||||
|
'usage.common.unknown': 'bilinmiyor',
|
||||||
|
'usage.table.date': 'Tarih',
|
||||||
|
'usage.table.name': 'Ad',
|
||||||
|
'usage.table.user': 'Kullanıcı',
|
||||||
|
'usage.table.users': 'Kullanıcılar',
|
||||||
|
'usage.table.type': 'Tür',
|
||||||
|
'usage.table.instance': 'Örnek',
|
||||||
|
'usage.table.instanceType': 'Örnek Türü',
|
||||||
|
'usage.table.instanceTypes': 'Örnek Türleri',
|
||||||
|
'usage.table.instances': 'Örnekler',
|
||||||
|
'usage.table.capacity': 'Kapasite',
|
||||||
|
'usage.export.tableNamed': 'Tablo Verilerini Dışa Aktar — {name}',
|
||||||
|
|
||||||
|
// --- Summary tab ---
|
||||||
|
'usage.summary.compute': 'Hesaplama',
|
||||||
|
'usage.summary.tokensOverTime': 'Zaman içinde Token',
|
||||||
|
'usage.summary.gpuHoursOverTime': 'Zaman içinde GPU Saati',
|
||||||
|
'usage.summary.gbDaysOverTime': 'Zaman içinde GB-Gün',
|
||||||
|
|
||||||
|
// --- GPU Instances / Storage filters ---
|
||||||
|
'usage.filter.instance': 'Örneğe göre filtrele',
|
||||||
|
'usage.filter.storage': 'Depolamaya göre filtrele',
|
||||||
|
|
||||||
|
// --- Resource events ---
|
||||||
|
'usage.events.resourceType': 'Kaynak türü',
|
||||||
|
'usage.events.eventType': 'Olay türü',
|
||||||
|
'usage.events.resourceName': 'Ada göre filtrele',
|
||||||
|
'usage.events.col.time': 'Zaman',
|
||||||
|
'usage.events.col.resource': 'Kaynak',
|
||||||
|
'usage.events.col.event': 'Olay',
|
||||||
|
'usage.events.col.message': 'Mesaj',
|
||||||
|
'usage.events.resource.gpuInstance': 'GPU Örneği',
|
||||||
|
'usage.events.resource.cpuInstance': 'CPU Örneği',
|
||||||
|
'usage.events.type.created': 'Oluşturuldu',
|
||||||
|
'usage.events.type.deleted': 'Silindi',
|
||||||
|
'usage.events.type.started': 'Başlatıldı',
|
||||||
|
'usage.events.type.stopped': 'Durduruldu',
|
||||||
|
'usage.events.type.updated': 'Güncellendi',
|
||||||
|
'usage.events.type.attached': 'Eklendi',
|
||||||
|
'usage.events.type.detached': 'Ayrıldı'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export default {
|
|||||||
'vendor.hygon': 'Hygon',
|
'vendor.hygon': 'Hygon',
|
||||||
'vendor.moorthreads': 'Moore Threads',
|
'vendor.moorthreads': 'Moore Threads',
|
||||||
'vendor.iluvatar': 'Iluvatar',
|
'vendor.iluvatar': 'Iluvatar',
|
||||||
'vendor.metax': 'Metax',
|
'vendor.metax': 'MetaX',
|
||||||
'vendor.cambricon': 'Cambricon',
|
'vendor.cambricon': 'Cambricon',
|
||||||
'vendor.thead': 'T-Head PPU'
|
'vendor.thead': 'T-Head PPU'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ export default {
|
|||||||
'ai.provider.ollama': 'Ollama',
|
'ai.provider.ollama': 'Ollama',
|
||||||
'ai.provider.openai': 'OpenAI',
|
'ai.provider.openai': 'OpenAI',
|
||||||
'ai.provider.openrouter': 'OpenRouter',
|
'ai.provider.openrouter': 'OpenRouter',
|
||||||
'ai.provider.qwen': '通义千问',
|
'ai.provider.qwen': '阿里云百炼',
|
||||||
'ai.provider.spark': '讯飞星火',
|
'ai.provider.spark': '讯飞星火',
|
||||||
'ai.provider.stepfun': '阶跃星辰',
|
'ai.provider.stepfun': '阶跃星辰',
|
||||||
'ai.provider.together-ai': 'Together AI',
|
'ai.provider.together-ai': 'Together AI',
|
||||||
|
|||||||
@@ -22,5 +22,7 @@ export default {
|
|||||||
'apikeys.accessScope.inference': '推理接口',
|
'apikeys.accessScope.inference': '推理接口',
|
||||||
'apikeys.access.permissions': '访问权限',
|
'apikeys.access.permissions': '访问权限',
|
||||||
'apikeys.type.auto': '自动生成',
|
'apikeys.type.auto': '自动生成',
|
||||||
'apikeys.type.custom': '自定义'
|
'apikeys.type.custom': '自定义',
|
||||||
|
'apikeys.button.ipConfig': 'IP 访问控制',
|
||||||
|
'quotaLimits.button.title': '配额限制'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ export default {
|
|||||||
'backend.form.defaultExecuteCommand': '默认执行命令',
|
'backend.form.defaultExecuteCommand': '默认执行命令',
|
||||||
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}'、'{{'port'}}'、'{{'worker_ip'}}' 和 '{{'model_name'}}' 都是占位符,在部署过程中会被替换为实际的值。`,
|
'backend.form.defaultExecuteCommand.tips': `'{{'model_path'}}'、'{{'port'}}'、'{{'worker_ip'}}' 和 '{{'model_name'}}' 都是占位符,在部署过程中会被替换为实际的值。`,
|
||||||
'backend.form.defaultBackendParameters': '默认后端参数',
|
'backend.form.defaultBackendParameters': '默认后端参数',
|
||||||
'backend.form.parameterFormat': '参数输出格式',
|
'backend.form.flagFormat': '参数格式',
|
||||||
'backend.form.parameterFormat.default': '后端默认',
|
'backend.form.flagFormat.tips':
|
||||||
'backend.form.parameterFormat.space': '空格分隔 (--key value)',
|
'选项名与取值之间使用的连接格式。留空则保留每个参数原本的写法,不做统一转换。',
|
||||||
'backend.form.parameterFormat.equal': '等号连接 (--key=value)',
|
'backend.form.flagFormat.space': '空格分隔 (--key value)',
|
||||||
'backend.form.commonParameters': '常用参数',
|
'backend.form.flagFormat.equal': '等号连接 (--key=value)',
|
||||||
|
'backend.form.commonParameters': '常用后端参数',
|
||||||
'backend.form.commonParameters.tips': '部署模型时作为后端参数候选项展示。',
|
'backend.form.commonParameters.tips': '部署模型时作为后端参数候选项展示。',
|
||||||
'backend.form.versionConfig': '版本配置',
|
'backend.form.versionConfig': '版本配置',
|
||||||
'backend.form.addParameter': '添加参数',
|
'backend.form.addParameter': '添加参数',
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ export default {
|
|||||||
'长上下文压力测试。评估 KV Cache 行为、内存占用及后端稳定性。',
|
'长上下文压力测试。评估 KV Cache 行为、内存占用及后端稳定性。',
|
||||||
'benchmark.form.profile.heavy.tips':
|
'benchmark.form.profile.heavy.tips':
|
||||||
'以解码为主的生成测试。衡量持续解码速度与输出 Token 吞吐能力。',
|
'以解码为主的生成测试。衡量持续解码速度与输出 Token 吞吐能力。',
|
||||||
'benchmark.table.filter.bygpu': '按 GPU 过滤',
|
'benchmark.table.filter.bygpu': 'GPU 查询',
|
||||||
'benchmark.table.filter.bymodel': '按模型过滤',
|
'benchmark.table.filter.bymodel': '模型查询',
|
||||||
'benchmark.table.filter.bydataset': '按数据集过滤',
|
'benchmark.table.filter.bydataset': '按数据集过滤',
|
||||||
'benchmark.table.filter.byProfile': '按模式过滤',
|
'benchmark.table.filter.byProfile': '按模式过滤',
|
||||||
'benchmark.table.avg': '均值',
|
'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': '了解企业版'
|
||||||
|
};
|
||||||
@@ -38,7 +38,7 @@ export default {
|
|||||||
'clusters.create.noRegions': '无可用的区域',
|
'clusters.create.noRegions': '无可用的区域',
|
||||||
'clusters.workerpool.batchSize.desc': '节点池中同时创建的节点数量。',
|
'clusters.workerpool.batchSize.desc': '节点池中同时创建的节点数量。',
|
||||||
'clusters.create.addworker.tips':
|
'clusters.create.addworker.tips':
|
||||||
'在执行以下命令之前,请确保已满足 {label} 的<a href={link} target="_blank">先决条件</a>。',
|
'在执行以下命令之前,请确保已满足<a href={link} target="_blank">先决条件</a>。',
|
||||||
'clusters.create.addCommand.tips':
|
'clusters.create.addCommand.tips':
|
||||||
'在需要添加的节点上运行以下命令,将其加入到集群中。',
|
'在需要添加的节点上运行以下命令,将其加入到集群中。',
|
||||||
'clusters.create.addCommand.k8s.tips':
|
'clusters.create.addCommand.k8s.tips':
|
||||||
@@ -68,8 +68,13 @@ export default {
|
|||||||
'<span class="bold-text">非 Docker</span> 集群请前往集群页面注册集群或管理节点池。',
|
'<span class="bold-text">非 Docker</span> 集群请前往集群页面注册集群或管理节点池。',
|
||||||
'clusters.addworker.selectGPU': '选择 GPU 厂商',
|
'clusters.addworker.selectGPU': '选择 GPU 厂商',
|
||||||
'clusters.addworker.selectGPU.multiTag': '可多选',
|
'clusters.addworker.selectGPU.multiTag': '可多选',
|
||||||
|
'clusters.addworker.selectGPU.subtitle':
|
||||||
|
'可选择多个 GPU 厂商,或不选择以用于仅 CPU 的集群',
|
||||||
'clusters.addworker.checkEnv': '检查环境',
|
'clusters.addworker.checkEnv': '检查环境',
|
||||||
|
'clusters.addworker.checkEnv.cpuOnlyTips':
|
||||||
|
'使用以下命令验证 Kubernetes 集群中至少有一个就绪节点。你正在注册仅 CPU 的集群。',
|
||||||
'clusters.addworker.specifyArgs': '指定参数',
|
'clusters.addworker.specifyArgs': '指定参数',
|
||||||
|
'clusters.addworker.dtkVersion': 'DTK 版本',
|
||||||
'clusters.addworker.runCommand': '运行指令',
|
'clusters.addworker.runCommand': '运行指令',
|
||||||
'clusters.addworker.specifyWorkerIP': '节点 IP',
|
'clusters.addworker.specifyWorkerIP': '节点 IP',
|
||||||
'clusters.addworker.detectWorkerIP': '自动检测节点 IP',
|
'clusters.addworker.detectWorkerIP': '自动检测节点 IP',
|
||||||
@@ -108,6 +113,8 @@ export default {
|
|||||||
'已将 {count} 个新节点添加到集群中。',
|
'已将 {count} 个新节点添加到集群中。',
|
||||||
'clusters.create.serverUrl': 'GPUStack Server 节点地址',
|
'clusters.create.serverUrl': 'GPUStack Server 节点地址',
|
||||||
'clusters.create.workerConfig': '节点配置',
|
'clusters.create.workerConfig': '节点配置',
|
||||||
|
'clusters.edit.k8sOptions.changed.tip':
|
||||||
|
'您已修改 Kubernetes 选项,需要在目标集群上重新运行注册命令才会生效。',
|
||||||
'clusters.addworker.containerName': '节点容器名称',
|
'clusters.addworker.containerName': '节点容器名称',
|
||||||
'clusters.addworker.containerName.tips': '为节点容器指定一个名称。',
|
'clusters.addworker.containerName.tips': '为节点容器指定一个名称。',
|
||||||
'clusters.addworker.dataVolume': 'GPUStack 数据卷',
|
'clusters.addworker.dataVolume': 'GPUStack 数据卷',
|
||||||
@@ -131,7 +138,7 @@ export default {
|
|||||||
'clusters.addworker.theadNotes-02':
|
'clusters.addworker.theadNotes-02':
|
||||||
'平头哥(T-Head)PPU 使用容器设备接口(CDI)进行设备注入,因此需要确保 <span class="bold-text">/var/run/cdi</span> 目录可用以生成 CDI。',
|
'平头哥(T-Head)PPU 使用容器设备接口(CDI)进行设备注入,因此需要确保 <span class="bold-text">/var/run/cdi</span> 目录可用以生成 CDI。',
|
||||||
'clusters.addworker.nvidiaNotes':
|
'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.title': '卷挂载',
|
||||||
'clusters.volume.name': '卷名称',
|
'clusters.volume.name': '卷名称',
|
||||||
'clusters.volume.mountPath': '容器内路径',
|
'clusters.volume.mountPath': '容器内路径',
|
||||||
@@ -158,7 +165,7 @@ export default {
|
|||||||
'clusters.systemDefaultContainerRegistry.title': '默认容器镜像仓库',
|
'clusters.systemDefaultContainerRegistry.title': '默认容器镜像仓库',
|
||||||
'clusters.systemDefaultContainerRegistry.tip':
|
'clusters.systemDefaultContainerRegistry.tip':
|
||||||
'用于解析该集群 GPUStack 镜像的默认镜像仓库。未设置时回退到服务端默认值。',
|
'用于解析该集群 GPUStack 镜像的默认镜像仓库。未设置时回退到服务端默认值。',
|
||||||
'clusters.k8sOptions.title': 'K8s 部署选项',
|
'clusters.k8sOptions.title': 'Kubernetes 部署选项',
|
||||||
'clusters.imageCredentials.title': '镜像仓库凭证',
|
'clusters.imageCredentials.title': '镜像仓库凭证',
|
||||||
'clusters.imageCredentials.add': '添加凭证',
|
'clusters.imageCredentials.add': '添加凭证',
|
||||||
'clusters.imageCredentials.registry': '镜像仓库地址',
|
'clusters.imageCredentials.registry': '镜像仓库地址',
|
||||||
@@ -173,9 +180,14 @@ export default {
|
|||||||
'clusters.namespace.title': '命名空间',
|
'clusters.namespace.title': '命名空间',
|
||||||
'clusters.namespace.tip':
|
'clusters.namespace.tip':
|
||||||
'集群清单渲染所使用的 Kubernetes 命名空间。留空则使用 gpustack-system。',
|
'集群清单渲染所使用的 Kubernetes 命名空间。留空则使用 gpustack-system。',
|
||||||
'clusters.gpuInstances.title': 'GPU 实例',
|
'clusters.clusterType.title': '集群类型',
|
||||||
'clusters.gpuInstances.tip': '为该集群启用 GPU 实例支持。',
|
'clusters.modelService.title': '模型服务',
|
||||||
'clusters.gpuInstances.staticAddress': '静态访问地址',
|
'clusters.modelService.tip':
|
||||||
|
'适用于大模型推理与 API 服务化场景,例如对外提供模型 API 与 Token 服务能力。',
|
||||||
|
'clusters.gpuInstances.title': 'GPU 服务',
|
||||||
|
'clusters.gpuInstances.tip':
|
||||||
|
'适用于按需分配 GPU 计算资源的场景,例如交互式开发、训练任务或自定义运行环境。',
|
||||||
|
'clusters.gpuInstances.staticAddress': 'GPU 服务静态访问地址',
|
||||||
'clusters.gpuInstances.staticAddress.tip':
|
'clusters.gpuInstances.staticAddress.tip':
|
||||||
'Operator 访问该集群 GPU 实例所使用的静态地址(例如 LoadBalancer VIP)。可选。'
|
'Operator 访问该集群 GPU 实例所使用的静态地址(例如 LoadBalancer VIP)。可选。'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export default {
|
|||||||
'common.button.rollback': '回滚',
|
'common.button.rollback': '回滚',
|
||||||
'common.button.new': '新建{ text }',
|
'common.button.new': '新建{ text }',
|
||||||
'common.button.upgrade': '升级',
|
'common.button.upgrade': '升级',
|
||||||
|
'common.enterprise.feature': 'GPUStack 企业版可用',
|
||||||
'common.input.holder': '请输入',
|
'common.input.holder': '请输入',
|
||||||
'common.holder.search': '搜索',
|
'common.holder.search': '搜索',
|
||||||
'common.button.edit': '编辑',
|
'common.button.edit': '编辑',
|
||||||
@@ -51,8 +52,8 @@ export default {
|
|||||||
'common.button.confirm': '确定',
|
'common.button.confirm': '确定',
|
||||||
'common.button.viewlog': '查看日志',
|
'common.button.viewlog': '查看日志',
|
||||||
'common.button.viewevent': '查看事件',
|
'common.button.viewevent': '查看事件',
|
||||||
'common.button.recreate': '重新创建',
|
|
||||||
'common.table.operation': '操作',
|
'common.table.operation': '操作',
|
||||||
|
'common.table.creator': '创建者',
|
||||||
'common.table.createTime': '创建时间',
|
'common.table.createTime': '创建时间',
|
||||||
'common.table.updateTime': '更新时间',
|
'common.table.updateTime': '更新时间',
|
||||||
'common.table.description': '描述',
|
'common.table.description': '描述',
|
||||||
@@ -160,6 +161,7 @@ export default {
|
|||||||
'common.time.hour': '小时',
|
'common.time.hour': '小时',
|
||||||
'common.time.minute': '分钟',
|
'common.time.minute': '分钟',
|
||||||
'common.issue.report': '问题反馈',
|
'common.issue.report': '问题反馈',
|
||||||
|
'common.github.star.tooltip': '在 GitHub 上 Star 我们',
|
||||||
'common.social.discord': '加入我们的Discord',
|
'common.social.discord': '加入我们的Discord',
|
||||||
'common.table.mark': '备注',
|
'common.table.mark': '备注',
|
||||||
'common.table.rollback.mark': '回滚备注',
|
'common.table.rollback.mark': '回滚备注',
|
||||||
@@ -233,6 +235,7 @@ export default {
|
|||||||
'common.text.tips': '提示',
|
'common.text.tips': '提示',
|
||||||
'settings.system': '系统设置',
|
'settings.system': '系统设置',
|
||||||
'common.filter.byId': '按 ID 查询',
|
'common.filter.byId': '按 ID 查询',
|
||||||
|
'common.filter.byCreator': '按创建者筛选',
|
||||||
'common.appearance': '外观',
|
'common.appearance': '外观',
|
||||||
'common.appearance.dark': '深色',
|
'common.appearance.dark': '深色',
|
||||||
'common.appearance.light': '浅色',
|
'common.appearance.light': '浅色',
|
||||||
@@ -251,6 +254,10 @@ export default {
|
|||||||
'common.login.auth': '认证中...',
|
'common.login.auth': '认证中...',
|
||||||
'common.login.auth.failed': '认证失败',
|
'common.login.auth.failed': '认证失败',
|
||||||
'common.login.password': '使用密码登录',
|
'common.login.password': '使用密码登录',
|
||||||
|
'common.login.username.holder': '请输入用户名',
|
||||||
|
'common.login.password.holder': '请输入密码',
|
||||||
|
'common.login.newpassword.holder': '请输入新密码',
|
||||||
|
'common.login.confirm.holder': '请再次输入密码',
|
||||||
'common.external.login': '使用 {type} 登录',
|
'common.external.login': '使用 {type} 登录',
|
||||||
'common.sso.noConfig': '该系统未启用单点登录,请联系管理员。',
|
'common.sso.noConfig': '该系统未启用单点登录,请联系管理员。',
|
||||||
'common.button.edit.item': '编辑 {name}',
|
'common.button.edit.item': '编辑 {name}',
|
||||||
@@ -258,7 +265,7 @@ export default {
|
|||||||
'common.button.terminal': '终端',
|
'common.button.terminal': '终端',
|
||||||
'common.button.addItem': '添加项',
|
'common.button.addItem': '添加项',
|
||||||
'common.help.default': '默认: {content}',
|
'common.help.default': '默认: {content}',
|
||||||
'common.filter.status': '按状态筛选',
|
'common.filter.status': '按状态过滤',
|
||||||
'common.form.rule.selectInput': '请选择或输入{name}',
|
'common.form.rule.selectInput': '请选择或输入{name}',
|
||||||
'common.tag.experimental': '实验性',
|
'common.tag.experimental': '实验性',
|
||||||
'common.title.example': '示例',
|
'common.title.example': '示例',
|
||||||
@@ -278,5 +285,7 @@ export default {
|
|||||||
'common.image.limit.height': '图片高度须为{height}。',
|
'common.image.limit.height': '图片高度须为{height}。',
|
||||||
'common.remaining': '剩余 {count}',
|
'common.remaining': '剩余 {count}',
|
||||||
'common.max': '最大 {count}',
|
'common.max': '最大 {count}',
|
||||||
'common.validate.group': '请填写完整的{group}配置'
|
'common.max.count': '{label} 数量',
|
||||||
|
'common.validate.group': '请填写完整的{group}配置',
|
||||||
|
'common.preferences': '偏好设置'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ export default {
|
|||||||
'dashboard.workers': '节点',
|
'dashboard.workers': '节点',
|
||||||
'dashboard.deployments': '部署',
|
'dashboard.deployments': '部署',
|
||||||
'dashboard.clusters': '集群',
|
'dashboard.clusters': '集群',
|
||||||
'dashboard.totalgpus': 'GPUs',
|
'dashboard.totalgpus': 'GPU',
|
||||||
'dashboard.systemload': '系统负载',
|
'dashboard.systemload': '系统负载',
|
||||||
'dashboard.memory': '内存',
|
'dashboard.memory': '内存',
|
||||||
'dashboard.vram': '显存',
|
'dashboard.vram': '显存',
|
||||||
|
|||||||
@@ -13,10 +13,13 @@ export default {
|
|||||||
'gpuservice.template.command.placeholder':
|
'gpuservice.template.command.placeholder':
|
||||||
'使用空格分隔参数;含空格的参数请用引号包裹,例如:/bin/bash -c "echo hello world"',
|
'使用空格分隔参数;含空格的参数请用引号包裹,例如:/bin/bash -c "echo hello world"',
|
||||||
'gpuservice.template.mountPath': '挂载路径',
|
'gpuservice.template.mountPath': '挂载路径',
|
||||||
'gpuservice.template.containerDisk': '容器磁盘(GB)',
|
'gpuservice.template.mountPath.tips':
|
||||||
'gpuservice.template.memory': '内存(GB)',
|
'在使用该模板创建实例时,存储卷默认挂载的路径,可用于持久化实例运行过程中需要保留的数据。',
|
||||||
'gpuservice.instance.containerDisk.remaining': '容器磁盘(最大{count}GB)',
|
'gpuservice.template.containerDisk': '容器磁盘(GB)',
|
||||||
'gpuservice.instance.memory.remaining': '内存(最大{count}GB)',
|
'gpuservice.template.containerDisk.tips': '容器系统盘大小。',
|
||||||
|
'gpuservice.template.memory': '内存(GB)',
|
||||||
|
'gpuservice.instance.containerDisk.remaining': '容器磁盘(最大 {count} GB)',
|
||||||
|
'gpuservice.instance.memory.remaining': '内存(最大 {count} GB)',
|
||||||
'gpuservice.template.displayName': '显示名称',
|
'gpuservice.template.displayName': '显示名称',
|
||||||
'gpuservice.template.displayName.max': '显示名称不能超过 63 个字符',
|
'gpuservice.template.displayName.max': '显示名称不能超过 63 个字符',
|
||||||
'gpuservice.template.ports': '端口',
|
'gpuservice.template.ports': '端口',
|
||||||
@@ -92,10 +95,13 @@ export default {
|
|||||||
'gpuservice.instance.templates': '实例模板',
|
'gpuservice.instance.templates': '实例模板',
|
||||||
'gpuservice.instance.section.storage': '存储卷',
|
'gpuservice.instance.section.storage': '存储卷',
|
||||||
'gpuservice.instance.type.required': '请选择实例类型',
|
'gpuservice.instance.type.required': '请选择实例类型',
|
||||||
|
'gpuservice.instance.type.noAvailable': '无可用的实例类型',
|
||||||
'gpuservice.instance.gpuCount': 'GPU 数量',
|
'gpuservice.instance.gpuCount': 'GPU 数量',
|
||||||
'gpuservice.instance.gpuCount.required': '请输入 GPU 数量',
|
'gpuservice.instance.gpuCount.required': '请输入 GPU 数量',
|
||||||
'gpuservice.instance.gpuCount.max': '最多选择 {count} 张卡',
|
'gpuservice.instance.gpuCount.max': '最多选择 {count} 张卡',
|
||||||
'gpuservice.instance.gpuCount.min': '至少选择 {count} 张卡',
|
'gpuservice.instance.gpuCount.min': '至少选择 {count} 张卡',
|
||||||
|
'gpuservice.instance.cpuCount.max': '最多选择 {count} 核',
|
||||||
|
'gpuservice.instance.cpuCount.min': '至少选择 {count} 核',
|
||||||
'gpuservice.instance.gpuCount.noAvailable':
|
'gpuservice.instance.gpuCount.noAvailable':
|
||||||
'没有可用的 GPU 资源,请选择其他实例类型。',
|
'没有可用的 GPU 资源,请选择其他实例类型。',
|
||||||
'gpuservice.instance.gpuCount.zero': '仅使用 CPU,用于环境准备。',
|
'gpuservice.instance.gpuCount.zero': '仅使用 CPU,用于环境准备。',
|
||||||
@@ -103,7 +109,13 @@ export default {
|
|||||||
'gpuservice.instance.sliced': '切分',
|
'gpuservice.instance.sliced': '切分',
|
||||||
'gpuservice.instance.memory': '显存',
|
'gpuservice.instance.memory': '显存',
|
||||||
'gpuservice.instance.ram': '内存',
|
'gpuservice.instance.ram': '内存',
|
||||||
|
'gpuservice.instance.os': '系统',
|
||||||
|
'gpuservice.instance.arch': '架构',
|
||||||
'gpuservice.instance.disk': '磁盘',
|
'gpuservice.instance.disk': '磁盘',
|
||||||
|
'gpuservice.table.count': '数量',
|
||||||
|
'gpuservice.instance.disk.system': '系统盘',
|
||||||
|
'gpuservice.instance.disk.ephemeral': '临时存储',
|
||||||
|
'gpuservice.instance.disk.persistent': '持久存储',
|
||||||
'gpuservice.instance.search.type.placeholder': '搜索名称',
|
'gpuservice.instance.search.type.placeholder': '搜索名称',
|
||||||
'gpuservice.instance.search.template.placeholder':
|
'gpuservice.instance.search.template.placeholder':
|
||||||
'搜索模板名称、镜像或挂载路径',
|
'搜索模板名称、镜像或挂载路径',
|
||||||
@@ -137,13 +149,18 @@ export default {
|
|||||||
'gpuservice.storage.persistentVolume': '持久',
|
'gpuservice.storage.persistentVolume': '持久',
|
||||||
'gpuservice.storage.temporary.tips': '实例停止后,数据将被清除。',
|
'gpuservice.storage.temporary.tips': '实例停止后,数据将被清除。',
|
||||||
'gpuservice.storage.persistentVolume.tips':
|
'gpuservice.storage.persistentVolume.tips':
|
||||||
'数据在实例重启后仍会保留,仅在实例被终止时删除。无法被其它实例共享。',
|
'数据在实例重启后仍会保留。持久卷不会随实例终止而删除,并可被多个实例共享。',
|
||||||
'gpuservice.storage.persistentVolume.required': '请选择持久卷',
|
'gpuservice.storage.persistentVolume.required': '请选择存储',
|
||||||
'gpuservice.storage.persistentVolume.capacity': '容量(GB)',
|
'gpuservice.storage.persistentVolume.capacity': '容量(GB)',
|
||||||
'gpuservice.storage.persistentVolume.capacity.required': '请输入容量',
|
'gpuservice.storage.persistentVolume.capacity.required': '请输入容量',
|
||||||
'gpuservice.storage.persistentVolume.releaseWithInstance': '随实例释放',
|
'gpuservice.storage.persistentVolume.releaseWithInstance': '随实例释放',
|
||||||
'gpuservice.storage.tempCapacity': '容量(GB)',
|
'gpuservice.storage.tempCapacity': '容量(GB)',
|
||||||
'gpuservice.storage.tempCapacity.required': '请输入临时存储容量',
|
'gpuservice.storage.tempCapacity.required': '请输入临时存储容量',
|
||||||
'gpuservice.form.rule.name':
|
'gpuservice.form.rule.name':
|
||||||
'由小写字母、数字和 "-" 组成,以字母或数字开头和结尾,不能包含连续的 "-",最多 63 个字符。'
|
'由小写字母、数字和 "-" 组成,以字母或数字开头和结尾,不能包含连续的 "-",最多 63 个字符。',
|
||||||
|
'gpuservice.form.storage.select': '选择存储',
|
||||||
|
'gpuservice.creator': '创建者',
|
||||||
|
'gpuservice.owner.global': '全局',
|
||||||
|
'gpuservice.template.group.yours': '我的模板',
|
||||||
|
'gpuservice.template.group.global': '全局模板'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,21 +25,25 @@ export default {
|
|||||||
'menu.resources': '资源',
|
'menu.resources': '资源',
|
||||||
'menu.apikeys': 'API 密钥',
|
'menu.apikeys': 'API 密钥',
|
||||||
'menu.users': '用户',
|
'menu.users': '用户',
|
||||||
'menu.profile': '个人信息',
|
'menu.profile': '偏好',
|
||||||
'menu.login': '登录',
|
'menu.login': '登录',
|
||||||
'menu.usage': '使用量',
|
'menu.usage': '使用量',
|
||||||
|
'menu.usage.usage': '使用量',
|
||||||
|
'menu.billingAndUsage': '用量与计费',
|
||||||
|
'menu.billingAndUsage.usage': '用量统计',
|
||||||
|
'menu.billingAndUsage.billing': '计费',
|
||||||
'menu.404': '404',
|
'menu.404': '404',
|
||||||
'menu.resources.workers': '节点',
|
'menu.resources.workers': '节点',
|
||||||
'menu.resources.gpus': 'GPUs',
|
'menu.resources.gpus': 'GPU',
|
||||||
'menu.models.modelfiles': '模型文件',
|
'menu.models.modelfiles': '模型文件',
|
||||||
'menu.accessControl': '访问控制',
|
'menu.accessControl': '访问控制',
|
||||||
'menu.accessControl.apikeys': 'API 密钥',
|
'menu.accessControl.apikeys': 'API 密钥',
|
||||||
'menu.accessControl.users': '用户',
|
'menu.accessControl.users': '用户',
|
||||||
'menu.clusterManagement': '集群管理',
|
'menu.accessControl.organizations': '组织',
|
||||||
'menu.clusterManagement.clusters': '集群',
|
'menu.resources.clusters': '集群',
|
||||||
'menu.clusterManagement.credentials': '云凭证',
|
'menu.resources.credentials': '云凭证',
|
||||||
'menu.clusterManagement.clusterDetail': '集群详情',
|
'menu.resources.clusterDetail': '集群详情',
|
||||||
'menu.clusterManagement.clusterCreate': '创建集群',
|
'menu.resources.clusterCreate': '创建集群',
|
||||||
'menu.models.backendsList': '推理后端',
|
'menu.models.backendsList': '推理后端',
|
||||||
'menu.gpuService': 'GPU 服务',
|
'menu.gpuService': 'GPU 服务',
|
||||||
'menu.gpuService.instances': 'GPU 实例',
|
'menu.gpuService.instances': 'GPU 实例',
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export default {
|
|||||||
'models.form.search.gguftips':
|
'models.form.search.gguftips':
|
||||||
'当 macOS 或 Windows 作节点时勾选 GGUF(搜索语音模型时取消勾选)',
|
'当 macOS 或 Windows 作节点时勾选 GGUF(搜索语音模型时取消勾选)',
|
||||||
'models.form.button.addlabel': '添加标签',
|
'models.form.button.addlabel': '添加标签',
|
||||||
'models.filter.category': '按类别筛选',
|
'models.filter.category': '按类别过滤',
|
||||||
'models.list.more.logs': '查看更多',
|
'models.list.more.logs': '查看更多',
|
||||||
'models.catalog.release.date': '发布日期',
|
'models.catalog.release.date': '发布日期',
|
||||||
'models.localpath.gguf.tips.title': 'GGUF 格式模型',
|
'models.localpath.gguf.tips.title': 'GGUF 格式模型',
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export default {
|
||||||
|
'organizations.upsell.title': '组织是企业版功能',
|
||||||
|
'organizations.upsell.subtitle':
|
||||||
|
'多租户可在团队间隔离用户、资源与配额。升级到 GPUStack 企业版即可管理组织。',
|
||||||
|
'organizations.upsell.featuresTitle': '企业版包含的能力',
|
||||||
|
'organizations.upsell.feature.orgs': '创建组织来分组用户并隔离工作负载',
|
||||||
|
'organizations.upsell.feature.members': '为每个组织管理成员与角色',
|
||||||
|
'organizations.upsell.feature.quotas': '为每个组织设置资源与 Token 配额',
|
||||||
|
'organizations.upsell.feature.isolation':
|
||||||
|
'按组织隔离 API 密钥、模型部署与资源',
|
||||||
|
'organizations.upsell.cta': '了解企业版'
|
||||||
|
};
|
||||||
@@ -17,9 +17,15 @@ export default {
|
|||||||
'usage.table.user.apiKeysUsed': '使用的 API 密钥',
|
'usage.table.user.apiKeysUsed': '使用的 API 密钥',
|
||||||
'usage.table.lastActive': '最后活跃时间',
|
'usage.table.lastActive': '最后活跃时间',
|
||||||
'usage.filter.granularity': '粒度',
|
'usage.filter.granularity': '粒度',
|
||||||
|
'usage.filter.granularity.hour': '按小时',
|
||||||
'usage.filter.granularity.day': '按天',
|
'usage.filter.granularity.day': '按天',
|
||||||
'usage.filter.granularity.week': '按周',
|
'usage.filter.granularity.week': '按周',
|
||||||
'usage.filter.granularity.month': '按月',
|
'usage.filter.granularity.month': '按月',
|
||||||
|
'usage.tabs.summary': '总览',
|
||||||
|
'usage.tabs.tokens': 'Token',
|
||||||
|
'usage.tabs.gpuInstances': 'GPU 实例',
|
||||||
|
'usage.tabs.storage': '存储',
|
||||||
|
'usage.tabs.resourceEvents': '资源事件',
|
||||||
'usage.tabs.models': '模型',
|
'usage.tabs.models': '模型',
|
||||||
'usage.tabs.apikeys': 'API 密钥',
|
'usage.tabs.apikeys': 'API 密钥',
|
||||||
'usage.tabs.users': '用户',
|
'usage.tabs.users': '用户',
|
||||||
@@ -32,5 +38,70 @@ export default {
|
|||||||
'usage.chart.cached': '缓存',
|
'usage.chart.cached': '缓存',
|
||||||
'usage.chart.uncached': '非缓存',
|
'usage.chart.uncached': '非缓存',
|
||||||
'usage.chart.inputTokensCached': '输入 Token 数(缓存/非缓存)',
|
'usage.chart.inputTokensCached': '输入 Token 数(缓存/非缓存)',
|
||||||
'usage.table.inputTokensCached': '输入 Token 缓存数'
|
'usage.table.inputTokensCached': '输入 Token 缓存数',
|
||||||
|
|
||||||
|
// --- Resource usage: shared metrics & units ---
|
||||||
|
'usage.metric.tokens': 'Token 数',
|
||||||
|
'usage.metric.input': '输入',
|
||||||
|
'usage.metric.output': '输出',
|
||||||
|
'usage.metric.gpuHours': 'GPU 小时',
|
||||||
|
'usage.metric.instanceHours': '实例小时',
|
||||||
|
'usage.metric.gbDays': 'GB·天',
|
||||||
|
'usage.metric.gbHours': 'GB·小时',
|
||||||
|
'usage.metric.activeUsers': '活跃用户',
|
||||||
|
'usage.metric.activeInstances': '活跃实例',
|
||||||
|
'usage.metric.activeStorage': '活跃存储',
|
||||||
|
'usage.metric.activeVolumes': '活跃存储卷',
|
||||||
|
'usage.metric.storageTypes': '存储类型',
|
||||||
|
'usage.metric.gpuHours.tip':
|
||||||
|
'实例运行时长按 GPU 数量加权:一个使用 N 个 GPU 的实例运行 H 小时记为 N × H GPU 小时。当每个实例仅使用单个 GPU 时,等于实例小时。',
|
||||||
|
'usage.metric.instanceHours.tip':
|
||||||
|
'所有实例运行时长的总和,与每个实例使用的 GPU 数量无关。一个实例运行 2 小时 = 2 实例小时。',
|
||||||
|
'usage.metric.gbDays.tip':
|
||||||
|
'存储容量随时间的积分,单位为 GB × 天:10 GB 保留 5 天 = 50 GB·天。(= GB·小时 ÷ 24)',
|
||||||
|
'usage.metric.gbHours.tip':
|
||||||
|
'存储容量随时间的积分,单位为 GB × 小时:10 GB 保留 5 小时 = 50 GB·小时。',
|
||||||
|
|
||||||
|
// --- Resource usage: common table / labels ---
|
||||||
|
'usage.common.noData': '暂无数据',
|
||||||
|
'usage.common.unknown': '未知',
|
||||||
|
'usage.table.date': '日期',
|
||||||
|
'usage.table.name': '名称',
|
||||||
|
'usage.table.user': '用户',
|
||||||
|
'usage.table.users': '用户',
|
||||||
|
'usage.table.type': '类型',
|
||||||
|
'usage.table.instance': '实例',
|
||||||
|
'usage.table.instanceType': '实例类型',
|
||||||
|
'usage.table.instanceTypes': '实例类型',
|
||||||
|
'usage.table.instances': '实例',
|
||||||
|
'usage.table.capacity': '容量',
|
||||||
|
'usage.export.tableNamed': '导出表格数据 — {name}',
|
||||||
|
|
||||||
|
// --- Summary tab ---
|
||||||
|
'usage.summary.compute': '算力',
|
||||||
|
'usage.summary.tokensOverTime': 'Token 数趋势',
|
||||||
|
'usage.summary.gpuHoursOverTime': 'GPU 小时趋势',
|
||||||
|
'usage.summary.gbDaysOverTime': 'GB·天趋势',
|
||||||
|
|
||||||
|
// --- GPU Instances / Storage filters ---
|
||||||
|
'usage.filter.instance': '按实例查询',
|
||||||
|
'usage.filter.storage': '按存储查询',
|
||||||
|
|
||||||
|
// --- Resource events ---
|
||||||
|
'usage.events.resourceType': '资源类型',
|
||||||
|
'usage.events.eventType': '事件类型',
|
||||||
|
'usage.events.resourceName': '按名称筛选',
|
||||||
|
'usage.events.col.time': '时间',
|
||||||
|
'usage.events.col.resource': '资源',
|
||||||
|
'usage.events.col.event': '事件',
|
||||||
|
'usage.events.col.message': '消息',
|
||||||
|
'usage.events.resource.gpuInstance': 'GPU 实例',
|
||||||
|
'usage.events.resource.cpuInstance': 'CPU 实例',
|
||||||
|
'usage.events.type.created': '已创建',
|
||||||
|
'usage.events.type.deleted': '已删除',
|
||||||
|
'usage.events.type.started': '已启动',
|
||||||
|
'usage.events.type.stopped': '已停止',
|
||||||
|
'usage.events.type.updated': '已更新',
|
||||||
|
'usage.events.type.attached': '已挂载',
|
||||||
|
'usage.events.type.detached': '已卸载'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import useCoolColors from '@/hooks/use-cool-colors';
|
import useCoolColors from '@/hooks/use-cool-colors';
|
||||||
import { Chart } from '@gpustack/core-ui';
|
import { Chart } from '@gpustack/core-ui';
|
||||||
import { formatLargeNumber } from '@gpustack/core-ui/utils';
|
import { formatLargeNumber } from '@gpustack/core-ui/utils';
|
||||||
import { Empty, theme } from 'antd';
|
import { Empty, Spin, theme } from 'antd';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import React, { useEffect, useMemo, useRef } from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
export interface BarSeriesItem {
|
export interface BarSeriesItem {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -17,10 +23,18 @@ export interface BarChartProps {
|
|||||||
xAxisData: string[];
|
xAxisData: string[];
|
||||||
height: number | string;
|
height: number | string;
|
||||||
width?: number | string;
|
width?: number | string;
|
||||||
|
loading?: boolean;
|
||||||
legendData?: { name: string; icon?: string }[];
|
legendData?: { name: string; icon?: string }[];
|
||||||
labelFormatter?: (val: any, index?: number) => string;
|
labelFormatter?: (val: any, index?: number) => string;
|
||||||
tooltipValueFormatter?: (val: any) => string;
|
tooltipValueFormatter?: (val: any) => string;
|
||||||
title?: string;
|
title?: string;
|
||||||
|
grid?: {
|
||||||
|
left?: number | string;
|
||||||
|
right?: number | string;
|
||||||
|
top?: number | string;
|
||||||
|
bottom?: number | string;
|
||||||
|
containLabel?: boolean;
|
||||||
|
};
|
||||||
legendIsolate?: boolean;
|
legendIsolate?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,15 +45,38 @@ const BarChart: React.FC<BarChartProps> = (props) => {
|
|||||||
height,
|
height,
|
||||||
width,
|
width,
|
||||||
legendData,
|
legendData,
|
||||||
|
loading,
|
||||||
labelFormatter,
|
labelFormatter,
|
||||||
tooltipValueFormatter,
|
tooltipValueFormatter,
|
||||||
title,
|
title,
|
||||||
|
grid,
|
||||||
legendIsolate
|
legendIsolate
|
||||||
} = props;
|
} = props;
|
||||||
const { token } = theme.useToken();
|
const { token } = theme.useToken();
|
||||||
const chartRef = useRef<{ chart: any } | null>(null);
|
const chartRef = useRef<{ chart: any } | null>(null);
|
||||||
const generateCoolColors = useCoolColors();
|
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(
|
const dynamicColors = useMemo(
|
||||||
() => generateCoolColors(seriesData.length),
|
() => generateCoolColors(seriesData.length),
|
||||||
[seriesData.length, generateCoolColors]
|
[seriesData.length, generateCoolColors]
|
||||||
@@ -100,7 +137,11 @@ const BarChart: React.FC<BarChartProps> = (props) => {
|
|||||||
data: processedData,
|
data: processedData,
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
barMaxWidth: 20,
|
barMaxWidth: 20,
|
||||||
barMinWidth: 8,
|
// Keep a small floor only — a large barMinWidth would force wide bars
|
||||||
|
// when there are many categories (e.g. hourly buckets), squeezing out
|
||||||
|
// the category gap so bars look fused. A low floor lets barCategoryGap
|
||||||
|
// win, so even dense hourly views keep visible gaps.
|
||||||
|
barMinWidth: 2,
|
||||||
barGap: '30%',
|
barGap: '30%',
|
||||||
barCategoryGap: '50%',
|
barCategoryGap: '50%',
|
||||||
...(stack === false || stack === undefined ? {} : { stack }),
|
...(stack === false || stack === undefined ? {} : { stack }),
|
||||||
@@ -122,8 +163,9 @@ const BarChart: React.FC<BarChartProps> = (props) => {
|
|||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
top: title ? 30 : 10,
|
top: title ? 30 : 10,
|
||||||
bottom: 28,
|
bottom: 8,
|
||||||
containLabel: true
|
containLabel: true,
|
||||||
|
...grid
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'axis',
|
trigger: 'axis',
|
||||||
@@ -264,18 +306,43 @@ const BarChart: React.FC<BarChartProps> = (props) => {
|
|||||||
justifyContent: 'center'
|
justifyContent: 'center'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{loading ? (
|
||||||
|
<Spin size="middle" />
|
||||||
|
) : (
|
||||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<div
|
||||||
|
ref={measureRef}
|
||||||
|
style={{ width: width || '100%', height, position: 'relative' }}
|
||||||
|
>
|
||||||
<Chart
|
<Chart
|
||||||
ref={chartRef as any}
|
ref={chartRef as any}
|
||||||
options={options as any}
|
options={options as any}
|
||||||
height={height}
|
height={height}
|
||||||
width={width || '100%'}
|
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,36 +0,0 @@
|
|||||||
import { currentOrganizationIdAtom } from '@/atoms/user';
|
|
||||||
import { Input as CInput } from '@gpustack/core-ui';
|
|
||||||
import { Form } from 'antd';
|
|
||||||
import type { NamePath } from 'antd/es/form/interface';
|
|
||||||
import { useAtomValue } from 'jotai';
|
|
||||||
import { useEffect } from 'react';
|
|
||||||
|
|
||||||
interface OwnerPrincipalIdFieldProps {
|
|
||||||
name?: NamePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pins `owner_principal_id` to the Org the caller is currently acting
|
|
||||||
// under. Cluster ownership is irrelevant here: cluster_access grants
|
|
||||||
// let one Org schedule on another Org's cluster, but the resource the
|
|
||||||
// caller creates still belongs to *their* Org, and the backend enforces
|
|
||||||
// `owner_principal_id == ctx.current_principal_id`.
|
|
||||||
const OwnerPrincipalIdField: React.FC<OwnerPrincipalIdFieldProps> = ({
|
|
||||||
name = 'owner_principal_id'
|
|
||||||
}) => {
|
|
||||||
const currentOrgId = useAtomValue(currentOrganizationIdAtom);
|
|
||||||
const form = Form.useFormInstance();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (currentOrgId != null) {
|
|
||||||
form.setFieldValue(name, currentOrgId);
|
|
||||||
}
|
|
||||||
}, [currentOrgId, name, form]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form.Item name={name} hidden>
|
|
||||||
<CInput.Input />
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default OwnerPrincipalIdField;
|
|
||||||
@@ -2,7 +2,13 @@ import useCoolColors from '@/hooks/use-cool-colors';
|
|||||||
import { Chart } from '@gpustack/core-ui';
|
import { Chart } from '@gpustack/core-ui';
|
||||||
import { formatLargeNumber } from '@gpustack/core-ui/utils';
|
import { formatLargeNumber } from '@gpustack/core-ui/utils';
|
||||||
import { Empty, Spin, theme } from 'antd';
|
import { Empty, Spin, theme } from 'antd';
|
||||||
import React, { useMemo, useRef } from 'react';
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
export interface PieChartItem {
|
export interface PieChartItem {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -19,6 +25,11 @@ interface PieChartProps {
|
|||||||
totalLabel?: string;
|
totalLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Donut sits on the left; a vertical legend hugs it just to the right and
|
||||||
|
// scrolls when there are many entries.
|
||||||
|
const CENTER_X = '32%';
|
||||||
|
const round2 = (n: number) => Math.round((Number(n) || 0) * 100) / 100;
|
||||||
|
|
||||||
const PieChart: React.FC<PieChartProps> = ({
|
const PieChart: React.FC<PieChartProps> = ({
|
||||||
data,
|
data,
|
||||||
height = 300,
|
height = 300,
|
||||||
@@ -32,6 +43,26 @@ const PieChart: React.FC<PieChartProps> = ({
|
|||||||
const chartRef = useRef<{ chart: any } | null>(null);
|
const chartRef = useRef<{ chart: any } | null>(null);
|
||||||
const generateCoolColors = useCoolColors();
|
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 colors = useMemo(() => {
|
||||||
const generatedColors = generateCoolColors(data.length + colorOffset);
|
const generatedColors = generateCoolColors(data.length + colorOffset);
|
||||||
return generatedColors.slice(colorOffset);
|
return generatedColors.slice(colorOffset);
|
||||||
@@ -40,8 +71,20 @@ const PieChart: React.FC<PieChartProps> = ({
|
|||||||
const options = useMemo(
|
const options = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
color: colors,
|
color: colors,
|
||||||
|
grid: {
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: 'item',
|
trigger: 'item',
|
||||||
|
position: 'top',
|
||||||
|
// Keep the tooltip inside the chart box — the donut sits on the left, so
|
||||||
|
// a left-slice tooltip could otherwise spill out and get hidden behind
|
||||||
|
// the side menu.
|
||||||
|
confine: false,
|
||||||
backgroundColor: token.colorBgElevated,
|
backgroundColor: token.colorBgElevated,
|
||||||
borderColor: 'transparent',
|
borderColor: 'transparent',
|
||||||
formatter: (params: any) => {
|
formatter: (params: any) => {
|
||||||
@@ -51,7 +94,7 @@ const PieChart: React.FC<PieChartProps> = ({
|
|||||||
<span class="tooltip-item-dot" style="border-radius:50%;background-color:${params.color};"></span>
|
<span class="tooltip-item-dot" style="border-radius:50%;background-color:${params.color};"></span>
|
||||||
<span class="tooltip-item-title">${params.name}</span>:
|
<span class="tooltip-item-title">${params.name}</span>:
|
||||||
</span>
|
</span>
|
||||||
<span class="tooltip-value">${formatLargeNumber(params.value)}</span>
|
<span class="tooltip-value">${formatLargeNumber(round2(params.value))} (${params.percent}%)</span>
|
||||||
</span>
|
</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
@@ -59,18 +102,21 @@ const PieChart: React.FC<PieChartProps> = ({
|
|||||||
legend: {
|
legend: {
|
||||||
type: 'scroll',
|
type: 'scroll',
|
||||||
orient: 'vertical',
|
orient: 'vertical',
|
||||||
right: 0,
|
left: '56%',
|
||||||
top: 18,
|
top: 'middle',
|
||||||
bottom: 18,
|
|
||||||
itemWidth: 8,
|
itemWidth: 8,
|
||||||
itemHeight: 8,
|
itemHeight: 8,
|
||||||
itemGap: 12,
|
itemGap: 10,
|
||||||
|
// Long names are truncated in the legend; hovering shows the full name
|
||||||
|
// in a tooltip.
|
||||||
|
tooltip: { show: true },
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: token.colorTextTertiary,
|
color: token.colorTextTertiary,
|
||||||
overflow: 'truncate',
|
overflow: 'truncate',
|
||||||
width: 180
|
width: 120
|
||||||
},
|
},
|
||||||
pageTextStyle: { color: token.colorTextTertiary },
|
pageTextStyle: { color: token.colorTextTertiary },
|
||||||
|
pageIconSize: 10,
|
||||||
pageIconColor: token.colorTextTertiary,
|
pageIconColor: token.colorTextTertiary,
|
||||||
pageIconInactiveColor: token.colorTextDisabled,
|
pageIconInactiveColor: token.colorTextDisabled,
|
||||||
data: data.map((item) => item.name),
|
data: data.map((item) => item.name),
|
||||||
@@ -79,24 +125,19 @@ const PieChart: React.FC<PieChartProps> = ({
|
|||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'pie',
|
type: 'pie',
|
||||||
radius: ['52%', '72%'],
|
radius: ['54%', '78%'],
|
||||||
center: ['34%', '50%'],
|
center: [CENTER_X, '50%'],
|
||||||
avoidLabelOverlap: true,
|
avoidLabelOverlap: true,
|
||||||
label: {
|
// No on-arc label — the percentage lives in the tooltip instead, so
|
||||||
show: false
|
// hovering shows value + percent in one place.
|
||||||
},
|
label: { show: false },
|
||||||
emphasis: {
|
emphasis: {
|
||||||
label: {
|
label: {
|
||||||
show: true,
|
|
||||||
formatter: (params: any) => `${params.percent}%`,
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: 600,
|
|
||||||
color: token.colorText
|
|
||||||
}
|
|
||||||
},
|
|
||||||
labelLine: {
|
|
||||||
show: false
|
show: false
|
||||||
},
|
},
|
||||||
|
scale: true
|
||||||
|
},
|
||||||
|
labelLine: { show: false },
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -127,17 +168,17 @@ const PieChart: React.FC<PieChartProps> = ({
|
|||||||
const displayTotal = total ?? data.reduce((sum, item) => sum + item.value, 0);
|
const displayTotal = total ?? data.reduce((sum, item) => sum + item.value, 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ width, height, position: 'relative' }}>
|
<div ref={measureRef} style={{ width, height, position: 'relative' }}>
|
||||||
<Chart
|
<Chart
|
||||||
ref={chartRef as any}
|
ref={chartRef as any}
|
||||||
options={options as any}
|
options={options as any}
|
||||||
height={height}
|
height={height}
|
||||||
width={width}
|
width={measuredWidth || width}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: '34%',
|
left: CENTER_X,
|
||||||
top: '50%',
|
top: '50%',
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import { PageActionType } from '@/config/types';
|
import { PageActionType } from '@/config/types';
|
||||||
|
import useSubmitLock from '@/hooks/use-submit-lock';
|
||||||
import {
|
import {
|
||||||
AlertBlockInfo,
|
AlertBlockInfo,
|
||||||
Input as CInput,
|
Input as CInput,
|
||||||
@@ -45,7 +46,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const [showKey, setShowKey] = useState(false);
|
const [showKey, setShowKey] = useState(false);
|
||||||
const [apikeyValue, setAPIKeyValue] = useState('');
|
const [apikeyValue, setAPIKeyValue] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const { loading, guard, run, release } = useSubmitLock();
|
||||||
const [isChanged, setIsChanged] = useState(false);
|
const [isChanged, setIsChanged] = useState(false);
|
||||||
const cacheFormRef = useRef<{
|
const cacheFormRef = useRef<{
|
||||||
allowed_type: string;
|
allowed_type: string;
|
||||||
@@ -98,10 +99,11 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
expires_in: getExpireValue(data.expires_in)
|
expires_in: getExpireValue(data.expires_in)
|
||||||
};
|
};
|
||||||
const res = await createApisKey({ data: params });
|
const res = await createApisKey({ data: params });
|
||||||
|
onOk();
|
||||||
// if custom value
|
// if custom value
|
||||||
if (data.custom) {
|
if (data.custom) {
|
||||||
return onOk();
|
onCancel();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAPIKeyValue(res.value);
|
setAPIKeyValue(res.value);
|
||||||
@@ -111,11 +113,12 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
const updateAPIKey = async (data: FormData) => {
|
const updateAPIKey = async (data: FormData) => {
|
||||||
await updateApisKey(currentData?.id as number, { data });
|
await updateApisKey(currentData?.id as number, { data });
|
||||||
onOk();
|
onOk();
|
||||||
|
onCancel();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOnOk = async (formdata: FormData) => {
|
const handleOnOk = async (formdata: FormData) => {
|
||||||
|
await run(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
|
||||||
const data = {
|
const data = {
|
||||||
..._.omit(formdata, ['allowed_type']),
|
..._.omit(formdata, ['allowed_type']),
|
||||||
allowed_model_names:
|
allowed_model_names:
|
||||||
@@ -131,18 +134,18 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
..._.omit(data, ['expires_in'])
|
..._.omit(data, ['expires_in'])
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
setLoading(false);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setLoading(false);
|
// handled in interceptor
|
||||||
}
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSumit = () => {
|
const handleSumit = () => {
|
||||||
form.submit();
|
guard(() => form.submit());
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDone = () => {
|
const handleDone = () => {
|
||||||
onOk();
|
onCancel();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAfterOpenChange = (isOpen: boolean) => {
|
const handleAfterOpenChange = (isOpen: boolean) => {
|
||||||
@@ -276,6 +279,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
name="addAPIKey"
|
name="addAPIKey"
|
||||||
form={form}
|
form={form}
|
||||||
onFinish={handleOnOk}
|
onFinish={handleOnOk}
|
||||||
|
onFinishFailed={release}
|
||||||
preserve={false}
|
preserve={false}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
allowed_type: 'all',
|
allowed_type: 'all',
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user