Compare commits

...
9 Commits
Author SHA1 Message Date
lofyerandfactory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> a90e02a04c feat(layout): move navigation menu from sidebar to top header
Switch ProLayout to top layout with a custom horizontal HeaderMenu
(dropdowns for grouped items) and relocate the user/help/version actions
into the header right area.

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
2026-07-02 09:17:48 +08:00
lofyerandClaude Opus 4.8 9a223f9565 feat(branding): rebrand footer vendor to MesaStack, trim footer/topbar
- Footer vendor label unified to "MesaStack" across all locales.
- Remove footer Help button; keep version as plain text (no click).
- Remove top-right GitHub star widget (delete unused github-star.tsx);
  keep version as plain text (no click), retain upgrade "New" badge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 12:35:39 +08:00
lofyerandClaude Opus 4.8 4150070829 docs(agents): document rebrand step in fork workflow
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 22:16:54 +08:00
lofyerandClaude Opus 4.8 62351a781f feat(branding): rebrand user-facing GPUStack to MesaStack
Applied via scripts/rebrand across UI labels, page title, and all
locale strings (en/zh/ja/ru/tr). Functional references untouched:
npm package, URLs, paths, JS identifiers, GPUSTACK_ constants, and the
X-GPUStack-Model header. The backend-contract startsWith string in
llmodels/hooks keeps "GPUStack server" to stay matchable. Logo images
intentionally left unchanged for now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 22:16:17 +08:00
lofyerandClaude Opus 4.8 eb2561ff1e feat(scripts): add rebrand script to swap brand identity
Replace the standalone "GPUStack" brand word with a target brand
(default MesaStack) across user-facing text. Word-boundary + case-
sensitive matching deliberately skips functional references: lowercase
gpustack (npm pkg/URLs/paths/k8s ns), ALL-CAPS GPUSTACK_ constants,
JS identifiers (getGPUStackPlugin etc.), and X-* HTTP headers. A
line-level skip list protects backend-contract strings matched at
runtime (e.g. the model-file-not-found startsWith check).

Re-run on each new v<version>-lofyer branch after syncing upstream.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 22:16:01 +08:00
lofyerandClaude Opus 4.8 c6643f2bc9 docs(agents): document downstream fork sync workflow
Describe the upstream->private mirror chain, the v<version>-lofyer branch
naming for appearance customizations, and how to run scripts/sync-github.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 20:56:50 +08:00
lofyerandClaude Opus 4.8 8901118bd2 feat(scripts): add sync-github to mirror upstream to private registry
Fetch all branches and tags from upstream GitHub and force-push them to
the 192.168.0.23 private origin (full mirror). Supports DRY_RUN and an
opt-in PRUNE_BRANCHES for true mirroring.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-30 20:55:01 +08:00
jialinandjialin db17192e55 fix(usage): carry full filter set in breakdown tables and fix double fetch
- breakdown sub-tables now send all active filters (route/user/api_key), matching the trend chart
- summary tab filters the token trend by user and unions user options from both meta APIs (deduped by id)
- stabilize the filters reference so meta load no longer retriggers a second fetch on mount
2026-06-30 17:02:17 +08:00
gitlawr a1dd1ec861 ci: trigger backend pack on UI merge
After build-publish uploads the UI tarball to COS, dispatch a
repository_dispatch event (ui-built) to gpustack/gpustack so the
backend repack picks up the new UI.

Skipped for tag pushes — release tags drive the backend pack via
its own tag trigger. Requires a DISPATCH_PAT secret with
actions:write on gpustack/gpustack.
2026-06-30 15:57:33 +08:00
56 changed files with 659 additions and 428 deletions
+18
View File
@@ -1,6 +1,7 @@
name: CI name: CI
on: on:
workflow_dispatch: {}
push: push:
branches: branches:
- 'main' - 'main'
@@ -117,3 +118,20 @@ jobs:
remote_path: releases/${{ steps.version.outputs.version }}.tar.gz remote_path: releases/${{ steps.version.outputs.version }}.tar.gz
accelerate: true accelerate: true
clean: false clean: false
trigger-backend:
needs: build-publish
if: (github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-22.04
steps:
- name: Dispatch backend build
uses: peter-evans/repository-dispatch@v3
with:
token: ${{ secrets.DISPATCH_PAT }}
repository: gpustack/gpustack
event-type: ui-built
client-payload: |
{
"ref": "${{ github.ref }}",
"sha": "${{ github.sha }}"
}
+69
View File
@@ -3,3 +3,72 @@
This project keeps a single source of truth for agent/contributor conventions in [`CLAUDE.md`](./CLAUDE.md). **Read [`CLAUDE.md`](./CLAUDE.md) and follow it.** This project keeps a single source of truth for agent/contributor conventions in [`CLAUDE.md`](./CLAUDE.md). **Read [`CLAUDE.md`](./CLAUDE.md) and follow it.**
@CLAUDE.md @CLAUDE.md
## Downstream fork workflow
This repo is a **downstream fork** that customizes the product appearance on top of
upstream `gpustack/gpustack-ui`. The mirror chain is:
```
upstream https://github.com/gpustack/gpustack-ui.git
| (fetch)
origin ssh://git@192.168.0.23:11022/root/gpustack-ui.git (private registry, also https://git.digiman.live)
```
### Goal
Track upstream releases. When upstream changes, we pull it in, then apply our own
appearance/customization changes on a dedicated branch so we keep our look-and-feel
on top of the latest upstream product.
### Branch naming
Customization work lives on `v<upstream-version>-lofyer` branches (e.g.
`v2.2.0-lofyer`). Each time upstream ships a new version we want to follow, create a
new `v<version>-lofyer` branch from the corresponding upstream tag/branch and re-apply
(or rebase) our customizations onto it.
### Syncing from upstream
`scripts/sync-github` mirrors upstream into the private `origin` (full mirror, force
push of all branches + tags). It auto-configures the `upstream` remote on first run.
```bash
# preview only, no push
DRY_RUN=1 ./scripts/sync-github
# real sync (force-pushes every upstream branch + tag to origin)
./scripts/sync-github
# also delete origin branches that no longer exist upstream (true mirror, destructive)
PRUNE_BRANCHES=1 ./scripts/sync-github
```
Env knobs: `UPSTREAM_URL`, `ORIGIN_REMOTE`, `UPSTREAM_REMOTE`, `PRUNE_BRANCHES`,
`DRY_RUN`. After syncing, branch a fresh `v<version>-lofyer` off the updated upstream
ref and apply the appearance changes there.
### Re-applying brand customizations
`scripts/rebrand` swaps the standalone brand word `GPUStack` for our brand
(`MesaStack`) across user-facing text. It is the first appearance change to re-apply
on every new `v<version>-lofyer` branch.
```bash
# preview hits, no writes
DRY_RUN=1 ./scripts/rebrand
# apply (default GPUStack -> MesaStack)
./scripts/rebrand
# custom brand words
FROM=GPUStack TO=AcmeStack ./scripts/rebrand
```
It deliberately **does not** touch functional references — lowercase `gpustack`
(npm pkg / URLs / paths / k8s namespace), ALL-CAPS `GPUSTACK_*` constants, JS
identifiers like `getGPUStackPlugin`, and `X-*` HTTP headers — and carries a
line-level skip list for backend-contract strings matched at runtime (see
`SKIP_LINE_PATTERNS` in the script). Always review `git diff` afterwards. Logo
images under `src/assets/images/` are NOT changed by the script — replace those PNGs
separately when new brand assets are available.
+2 -2
View File
@@ -1,6 +1,6 @@
# GPUStack UI # MesaStack UI
UI for [GPUStack](https://github.com/gpustack/gpustack). UI for [MesaStack](https://github.com/gpustack/gpustack).
## Installation ## Installation
+1 -1
View File
@@ -77,7 +77,7 @@ export default defineConfig({
antd: { antd: {
style: 'less' style: 'less'
}, },
title: 'GPUStack', title: 'MesaStack',
hash: true, hash: true,
access: {}, access: {},
model: {}, model: {},
+102
View File
@@ -0,0 +1,102 @@
#!/bin/bash
#
# rebrand: 把面向用户的品牌标识从 GPUStack 批量替换为 MesaStack(或自定义品牌)。
#
# 设计目标:上游每次更新后,在新的 v<version>-lofyer 分支上跑一次即可重新应用品牌定制。
#
# 只替换「独立的品牌词」FROM,并刻意跳过所有功能性引用:
# - 小写 `gpustack` —— npm 包名 (@gpustack/core-ui)、URL、路径、k8s 命名空间、author(大小写敏感,天然不匹配)
# - 全大写 `GPUSTACK` —— 常量/环境变量/全局 (GPUSTACK_API_BASE_URL, __GPUSTACK_*__, GPUSTACK_UI_*)(大小写敏感,不匹配)
# - 代码标识符 —— getGPUStackPlugin / GPUStackVersionAtom / GPUStackPluginManager / GPUStackLogo 等
# (FROM 紧邻字母时视为标识符的一部分,跳过)
# - HTTP 头 X-GPUStack-* —— 后端契约 (如 X-GPUStack-Model),跳过
#
# 匹配规则:FROM 前后都不是字母(独立单词),且不是 `X-` 前缀的头名。
#
# 环境变量:
# FROM 源品牌词(默认 GPUStack
# TO 目标品牌词(默认 MesaStack
# DRY_RUN 设为 1 时只预览将改动的行,不写文件
#
set -e
FROM="${FROM:-GPUStack}"
TO="${TO:-MesaStack}"
DRY_RUN="${DRY_RUN:-0}"
log() { echo -e "\033[1;34m[rebrand]\033[0m $*"; }
# 大小写敏感、单词边界、排除 X- 头前缀的 Perl 正则。
# (?<![A-Za-z]) 前面不是字母 (?<!X-) 不是 X- 头 (?![A-Za-z]) 后面不是字母
# FROM 为纯字母品牌词(无正则元字符),故直接拼接,不用 \Q\E
# —— \Q\E 在「经变量插值进正则」时不会被求值,反而会破坏匹配。
PATTERN="(?<![A-Za-z])(?<!X-)${FROM}(?![A-Za-z])"
# 行级排除:某些 FROM 出现在「与后端契约绑定的字符串」里,改了会破坏运行时逻辑,
# 即使是独立单词也必须整行跳过。命中下列任一正则的行不替换。
# 已知例外:
# - llmodels/hooks/index.ts 用 startsWith() 比对后端返回的英文兼容性消息
# "... does not exist on the GPUStack server ..."),后端仍发 GPUStack,前端不能改。
SKIP_LINE_PATTERNS=(
'does not exist on the .*server. It'"'"'s recommended'
)
skip_line() {
local line="$1" pat
for pat in "${SKIP_LINE_PATTERNS[@]}"; do
if echo "$line" | grep -qP "$pat"; then return 0; fi
done
return 1
}
# 待处理的已跟踪文本文件(排除 lockfile 与本脚本自身)。
mapfile -t files < <(
git ls-files -- \
'*.ts' '*.tsx' '*.js' '*.jsx' '*.json' '*.less' '*.html' '*.md' \
| grep -v 'pnpm-lock.yaml'
)
log "品牌替换: '${FROM}' -> '${TO}'"
log "候选文件: ${#files[@]}"
# 把行级排除合并成一个 perl 正则,供预览与替换共用(经环境变量传入,免去转义)。
SKIP_RE=""
for pat in "${SKIP_LINE_PATTERNS[@]}"; do
SKIP_RE="${SKIP_RE:+${SKIP_RE}|}(?:${pat})"
done
export REBRAND_SKIP_RE="${SKIP_RE}"
export REBRAND_PATTERN="${PATTERN}"
export REBRAND_TO="${TO}"
# 统计命中行(预览/确认用),已扣除被行级排除的行。
log "命中行预览(最多 40 行):"
grep -rnP "${PATTERN}" "${files[@]}" 2>/dev/null \
| { [[ -n "${SKIP_RE}" ]] && grep -vP "${SKIP_RE}" || cat; } \
| head -40 || true
total=$(grep -rnP "${PATTERN}" "${files[@]}" 2>/dev/null \
| { [[ -n "${SKIP_RE}" ]] && grep -vP "${SKIP_RE}" || cat; } | wc -l)
log "命中总行数: ${total}"
if [[ -n "${SKIP_RE}" ]]; then
skipped=$(grep -rnP "${PATTERN}" "${files[@]}" 2>/dev/null | grep -cP "${SKIP_RE}" || true)
log "行级排除(后端契约,保留 ${FROM}: ${skipped} 行"
fi
if [[ "${DRY_RUN}" == "1" ]]; then
log "DRY_RUN=1:未写入任何文件。"
exit 0
fi
# 实际替换(in-place)。命中行级排除的行整行跳过。
changed=0
for f in "${files[@]}"; do
if grep -qP "${PATTERN}" "$f" 2>/dev/null; then
perl -i -pe '
my $skip = $ENV{REBRAND_SKIP_RE};
next if length($skip) && /$skip/;
s/$ENV{REBRAND_PATTERN}/$ENV{REBRAND_TO}/g;
' "$f"
changed=$((changed + 1))
fi
done
log "已修改文件数: ${changed}"
log "完成。请用 'git diff' 复核改动。"
+83
View File
@@ -0,0 +1,83 @@
#!/bin/bash
#
# sync-github: 从上游 GitHub 拉取最新源码,并全量镜像同步到 192.168.0.23 私有源。
#
# 默认行为(全量镜像 / 强制):
# 1. 确保存在 upstream remote 指向 GitHub(不存在则自动添加,URL 不一致则更新)。
# 2. 从 upstream 抓取所有分支与 tag(--prune 清理已删除的远端引用)。
# 3. 将上游每个分支强制推送到私有源同名分支(force push)。
# 4. 将上游所有 tag 强制推送到私有源。
#
# 可用环境变量:
# UPSTREAM_URL 上游 GitHub 仓库地址(默认 https://github.com/gpustack/gpustack-ui.git
# ORIGIN_REMOTE 私有源 remote 名称(默认 origin
# UPSTREAM_REMOTE 上游 remote 名称(默认 upstream
# PRUNE_BRANCHES 设为 1 时,删除私有源上「上游已不存在」的分支(真·镜像,破坏性,默认关闭)
# DRY_RUN 设为 1 时,仅打印将要执行的推送动作,不实际推送
#
set -e
UPSTREAM_URL="${UPSTREAM_URL:-https://github.com/gpustack/gpustack-ui.git}"
ORIGIN_REMOTE="${ORIGIN_REMOTE:-origin}"
UPSTREAM_REMOTE="${UPSTREAM_REMOTE:-upstream}"
PRUNE_BRANCHES="${PRUNE_BRANCHES:-0}"
DRY_RUN="${DRY_RUN:-0}"
log() { echo -e "\033[1;34m[sync-github]\033[0m $*"; }
warn() { echo -e "\033[1;33m[sync-github]\033[0m $*" >&2; }
run() {
if [[ "${DRY_RUN}" == "1" ]]; then
echo " (dry-run) git $*"
else
git "$@"
fi
}
# 1. 确保 upstream remote 指向 GitHub。
if git remote get-url "${UPSTREAM_REMOTE}" >/dev/null 2>&1; then
current_url=$(git remote get-url "${UPSTREAM_REMOTE}")
if [[ "${current_url}" != "${UPSTREAM_URL}" ]]; then
log "更新 ${UPSTREAM_REMOTE} 地址: ${current_url} -> ${UPSTREAM_URL}"
git remote set-url "${UPSTREAM_REMOTE}" "${UPSTREAM_URL}"
fi
else
log "添加 upstream remote: ${UPSTREAM_REMOTE} -> ${UPSTREAM_URL}"
git remote add "${UPSTREAM_REMOTE}" "${UPSTREAM_URL}"
fi
origin_url=$(git remote get-url "${ORIGIN_REMOTE}")
log "上游 (拉取): ${UPSTREAM_URL}"
log "私有源 (推送): ${origin_url}"
# 2. 抓取上游所有分支与 tag。
log "抓取上游分支与 tag..."
git fetch --prune --tags "${UPSTREAM_REMOTE}"
# 3. 逐个分支强制推送到私有源。
log "强制同步分支到私有源..."
upstream_branches=$(git for-each-ref --format='%(refname:strip=3)' "refs/remotes/${UPSTREAM_REMOTE}/" | grep -v '^HEAD$')
for branch in ${upstream_branches}; do
log " -> ${branch}"
run push --force "${ORIGIN_REMOTE}" \
"refs/remotes/${UPSTREAM_REMOTE}/${branch}:refs/heads/${branch}"
done
# 4. 强制同步所有 tag。
log "强制同步 tag 到私有源..."
run push --force --tags "${ORIGIN_REMOTE}"
# 5. 可选:删除私有源上、上游已不存在的分支(真·镜像)。
if [[ "${PRUNE_BRANCHES}" == "1" ]]; then
warn "PRUNE_BRANCHES=1:将删除私有源上上游已不存在的分支"
origin_branches=$(git ls-remote --heads "${ORIGIN_REMOTE}" | sed 's@.*refs/heads/@@')
for branch in ${origin_branches}; do
if ! echo "${upstream_branches}" | grep -qx "${branch}"; then
warn " 删除私有源分支: ${branch}"
run push "${ORIGIN_REMOTE}" --delete "${branch}"
fi
done
fi
log "同步完成。"
+2 -25
View File
@@ -1,9 +1,7 @@
import { GPUStackVersionAtom } from '@/atoms/user'; import { GPUStackVersionAtom } from '@/atoms/user';
import { getAtomStorage } from '@/atoms/utils'; import { getAtomStorage } from '@/atoms/utils';
import VersionInfo, { modalConfig } from '@/components/version-info';
import externalLinks from '@/constants/external-links';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Button, Divider, Modal, Typography } from 'antd'; import { Divider, Typography } from 'antd';
import { createStyles } from 'antd-style'; import { createStyles } from 'antd-style';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -33,20 +31,10 @@ const useStyles = createStyles(({ token, css }) => ({
const Footer: React.FC = () => { const Footer: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const [modal, contextHolder] = Modal.useModal();
const { styles } = useStyles(); const { styles } = useStyles();
const showVersion = () => {
modal.info({
...modalConfig,
width: 460,
content: <VersionInfo intl={intl} />
});
};
return ( return (
<> <>
{contextHolder}
<div className={styles.footer}> <div className={styles.footer}>
<div className="footer-content"> <div className="footer-content">
<div className="footer-content-left"> <div className="footer-content-left">
@@ -63,18 +51,7 @@ const Footer: React.FC = () => {
</Typography.Link> </Typography.Link>
</CompanyWrapper> </CompanyWrapper>
<Divider orientation="vertical" /> <Divider orientation="vertical" />
<Button <span>{getAtomStorage(GPUStackVersionAtom)?.version}</span>
type="link"
size="small"
href={externalLinks.documentation}
target="_blank"
>
{intl.formatMessage({ id: 'common.button.help' })}
</Button>
<Divider orientation="vertical" />
<Button type="link" size="small" onClick={showVersion}>
{getAtomStorage(GPUStackVersionAtom)?.version}
</Button>
</div> </div>
</div> </div>
</div> </div>
+5 -25
View File
@@ -1,8 +1,6 @@
import { GPUStackVersionAtom, UpdateCheckAtom } from '@/atoms/user'; import { GPUStackVersionAtom, UpdateCheckAtom } from '@/atoms/user';
import PluginExtraField from '@/components/plugin-extra-fields'; import PluginExtraField from '@/components/plugin-extra-fields';
import VersionInfo, { modalConfig } from '@/components/version-info';
import externalLinks from '@/constants/external-links'; import externalLinks from '@/constants/external-links';
import useBodyScroll from '@/hooks/use-body-scroll';
import { logout } from '@/pages/login/apis'; import { logout } from '@/pages/login/apis';
import { useModel } from '@@/plugin-model'; import { useModel } from '@@/plugin-model';
import { import {
@@ -13,12 +11,11 @@ import {
} from '@ant-design/icons'; } from '@ant-design/icons';
import { DropdownActions, IconFont } from '@gpustack/core-ui'; import { DropdownActions, IconFont } from '@gpustack/core-ui';
import { history, useIntl, useNavigate } from '@umijs/max'; import { history, useIntl, useNavigate } from '@umijs/max';
import { Avatar, Button, Divider, Modal } from 'antd'; import { Avatar, Divider } from 'antd';
import { useAtom } from 'jotai'; 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;
@@ -98,11 +95,9 @@ const CustomItem = styled.div`
export const ExtraContent = (props: { isDarkTheme?: boolean }) => { export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
const { isDarkTheme } = props; const { isDarkTheme } = props;
const { saveScrollHeight, restoreScrollHeight } = useBodyScroll(); const intl = useIntl();
const [modal, contextHolder] = Modal.useModal();
const [version] = useAtom(GPUStackVersionAtom); const [version] = useAtom(GPUStackVersionAtom);
const [updateCheck] = useAtom(UpdateCheckAtom); const [updateCheck] = useAtom(UpdateCheckAtom);
const intl = useIntl();
const initialInfo = useModel('@@initialState') || { const initialInfo = useModel('@@initialState') || {
initialState: undefined, initialState: undefined,
loading: false, loading: false,
@@ -140,16 +135,6 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
return {}; return {};
}, [isDarkTheme]); }, [isDarkTheme]);
const showVersion = () => {
saveScrollHeight();
modal.info({
...modalConfig,
width: 460,
content: <VersionInfo intl={intl} />,
onCancel: restoreScrollHeight
});
};
const handleLogout = async () => { const handleLogout = async () => {
await logout(); await logout();
navigate(loginPath); navigate(loginPath);
@@ -159,7 +144,7 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
{ {
key: 'site', key: 'site',
icon: <HomeOutlined />, icon: <HomeOutlined />,
label: 'GPUStack', label: 'MesaStack',
url: externalLinks.site url: externalLinks.site
}, },
{ {
@@ -259,25 +244,20 @@ export const ExtraContent = (props: { isDarkTheme?: boolean }) => {
return ( return (
<Wrapper> <Wrapper>
{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',
alignItems: 'center' alignItems: 'center'
}} }}
> >
<Button <span
type="text"
size="small"
onClick={showVersion}
style={{ style={{
color: 'var(--ant-color-text-tertiary)' color: 'var(--ant-color-text-tertiary)'
}} }}
> >
{version.version} {version.version}
</Button> </span>
{showUpgrade && ( {showUpgrade && (
<NewLabel> <NewLabel>
<span className="text"> <span className="text">
-140
View File
@@ -1,140 +0,0 @@
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;
+128
View File
@@ -0,0 +1,128 @@
import { IconFont } from '@gpustack/core-ui';
import { Link, useLocation, useNavigate } from '@umijs/max';
import { Menu } from 'antd';
import { createStyles } from 'antd-style';
import React, { useMemo } from 'react';
interface MenuItem {
icon?: string;
selectedIcon?: string;
defaultIcon?: string;
children?: MenuItem[];
[key: string]: any;
}
interface HeaderMenuProps {
menuData: MenuItem[];
initialState?: Global.InitialStateType;
}
const useStyles = createStyles(({ css }) => {
return {
headerMenu: css`
flex: 1;
min-width: 0;
background: transparent;
border-bottom: none;
line-height: inherit;
&.ant-menu-horizontal {
border-bottom: none;
}
&.ant-menu-horizontal > .ant-menu-item::after,
&.ant-menu-horizontal > .ant-menu-submenu::after {
display: none;
}
.ant-menu-title-content {
display: inline-flex;
align-items: center;
gap: 8px;
}
.anticon {
font-size: 16px;
}
`
};
});
const isItemSelected = (item: MenuItem, pathname: string) => {
return (
pathname === item.path ||
(Array.isArray(item.subMenu) && item.subMenu.includes(pathname))
);
};
const HeaderMenu: React.FC<HeaderMenuProps> = (props) => {
const { menuData } = props;
const { styles } = useStyles();
const location = useLocation();
const navigate = useNavigate();
const buildLeaf = (item: MenuItem) => {
const selected = isItemSelected(item, location.pathname);
return {
key: item.path as string,
label: (
<Link
prefetch="intent"
to={(item.path as string).replace('/*', '')}
target={item.target}
>
<span className="flex-center gap-8">
<IconFont
type={selected ? item.selectedIcon || '' : item.defaultIcon || ''}
/>
<span>{item.name}</span>
</span>
</Link>
)
};
};
const items = useMemo(() => {
return menuData.map((item) => {
if (item.children && item.children.length > 0) {
return {
key: item.key,
label: item.name,
children: item.children.map((child) => buildLeaf(child))
};
}
return buildLeaf(item);
});
}, [menuData, location.pathname]);
const selectedKeys = useMemo(() => {
const keys: string[] = [];
for (const item of menuData) {
const leaves =
item.children && item.children.length > 0 ? item.children : [item];
for (const leaf of leaves) {
if (isItemSelected(leaf, location.pathname)) {
keys.push(leaf.path as string);
}
}
}
return keys;
}, [menuData, location.pathname]);
const handleClick = ({ key }: { key: string }) => {
if (key.startsWith('/')) {
navigate(key.replace('/*', ''));
}
};
return (
<Menu
className={styles.headerMenu}
mode="horizontal"
selectedKeys={selectedKeys}
items={items}
onClick={handleClick}
triggerSubMenuAction="hover"
/>
);
};
export default HeaderMenu;
+21 -57
View File
@@ -22,7 +22,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 { CoreUIProvider, IconFont } from '@gpustack/core-ui'; import { CoreUIProvider } from '@gpustack/core-ui';
import { import {
Access, Access,
Outlet, Outlet,
@@ -39,18 +39,18 @@ import {
useNavigate, useNavigate,
type IRoute type IRoute
} from '@umijs/max'; } from '@umijs/max';
import { Button, ConfigProvider, Modal, theme } from 'antd'; import { ConfigProvider, Modal, theme } from 'antd';
import { useAtom } from 'jotai'; import { useAtom } from 'jotai';
import 'overlayscrollbars/overlayscrollbars.css'; import 'overlayscrollbars/overlayscrollbars.css';
import { useEffect, useMemo, useRef } from 'react'; import { useEffect, useMemo, useRef } from 'react';
import { PageContainerInner } from '../pages/_components/page-box'; import { PageContainerInner } from '../pages/_components/page-box';
import Exception from './Exception'; import Exception from './Exception';
import './Layout.css'; import './Layout.css';
import { LogoIcon, SLogoIcon } from './Logo'; import { LogoIcon } from './Logo';
import ErrorBoundary from './error-boundary'; import ErrorBoundary from './error-boundary';
import { ExtraContent } from './extraRender'; import { ExtraContent } from './extraRender';
import HeaderMenu from './header-menu';
import { patchRoutes } from './runtime'; import { patchRoutes } from './runtime';
import SiderMenu from './sider-menu';
// Pages that use the page container in the page // Pages that use the page container in the page
const NO_CONTAINER_PAGES = [ const NO_CONTAINER_PAGES = [
@@ -133,7 +133,7 @@ const mapRoutes = (routes: IRoute[], role: string) => {
export default (props: any) => { export default (props: any) => {
const [, contextHolder] = Modal.useModal(); const [, contextHolder] = Modal.useModal();
const { themeData, setUserSettings, userSettings } = useUserSettings(); const { themeData, userSettings } = useUserSettings();
const [userInfo] = useAtom(userAtom); const [userInfo] = useAtom(userAtom);
const [routeCache] = useAtom(routeCacheAtom); const [routeCache] = useAtom(routeCacheAtom);
const location = useLocation(); const location = useLocation();
@@ -238,13 +238,6 @@ export default (props: any) => {
const coreUISlots = useMemo(() => ({ ExtraContent }), []); const coreUISlots = useMemo(() => ({ ExtraContent }), []);
const handleToggleCollapse = (e: any) => {
e.stopPropagation();
setUserSettings({
...userSettings,
collapsed: !userSettings.collapsed
});
};
const newRoutes = filterRoutes( const newRoutes = filterRoutes(
// @ts-ignore // @ts-ignore
clientRoutes.filter((route) => route.id === 'max-tabs'), clientRoutes.filter((route) => route.id === 'max-tabs'),
@@ -274,16 +267,19 @@ export default (props: any) => {
return NO_CONTAINER_PAGES.includes(matchedRoute?.name as string); return NO_CONTAINER_PAGES.includes(matchedRoute?.name as string);
}, [matchedRoute]); }, [matchedRoute]);
const collapsed = useMemo(() => {
return userSettings.collapsed || false;
}, [userSettings.collapsed]);
const renderMenuHeader = (logo: React.ReactNode, title: React.ReactNode) => { const renderMenuHeader = (logo: React.ReactNode, title: React.ReactNode) => {
return <>{logo}</>; return <>{logo}</>;
}; };
const menuContentRender = (menuProps: any, defaultDom: React.ReactNode) => { const headerContentRender = (
return <SiderMenu {...menuProps}></SiderMenu>; headerProps: any,
defaultDom: React.ReactNode
) => {
return <HeaderMenu {...headerProps}></HeaderMenu>;
};
const actionsRender = () => {
return <ExtraContent isDarkTheme={userSettings.isDarkTheme} />;
}; };
const onPageChange = async (route: any) => { const onPageChange = async (route: any) => {
@@ -337,17 +333,6 @@ export default (props: any) => {
navigate(pagepath); navigate(pagepath);
}; };
const onCollapse = (value: boolean) => {
// only trigger by window resize
if (!value) {
return;
}
setUserSettings({
...userSettings,
collapsed: value
});
};
return ( return (
<ConfigProvider <ConfigProvider
componentSize="large" componentSize="large"
@@ -408,48 +393,27 @@ export default (props: any) => {
<DarkMask></DarkMask> <DarkMask></DarkMask>
<ProLayout <ProLayout
fixSiderbar fixSiderbar
fixedHeader={false} fixedHeader
headerRender={false}
breadcrumbRender={false} breadcrumbRender={false}
route={route} route={route}
location={location} location={location}
title={userConfig.title} title={userConfig.title}
navTheme={userSettings.theme} navTheme={userSettings.theme}
layout="side" layout="top"
contentStyle={{ contentStyle={{
paddingBlock: 0, paddingBlock: 0,
paddingInline: 0 paddingInline: 0
}} }}
openKeys={false}
disableMobile={true} disableMobile={true}
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}
onMenuHeaderClick={onMenuHeaderClick} onMenuHeaderClick={onMenuHeaderClick}
collapsed={userSettings.collapsed}
onPageChange={onPageChange} onPageChange={onPageChange}
formatMessage={formatMessage} formatMessage={formatMessage}
menu={{ menu={{
locale: true, locale: true
type: 'group'
}} }}
splitMenus={true} logo={<LogoIcon />}
logo={userSettings.collapsed ? <SLogoIcon /> : <LogoIcon />} headerContentRender={headerContentRender}
menuContentRender={menuContentRender} actionsRender={actionsRender}
{...runtimeConfig} {...runtimeConfig}
ErrorBoundary={ErrorBoundary} ErrorBoundary={ErrorBoundary}
> >
@@ -457,7 +421,7 @@ export default (props: any) => {
style={{ style={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
height: '100vh', height: '100%',
overflow: 'hidden' overflow: 'hidden'
}} }}
> >
+1 -1
View File
@@ -82,7 +82,7 @@ export const getRightRenderContent = (opts: {
{ {
key: 'site', key: 'site',
icon: <HomeOutlined />, icon: <HomeOutlined />,
label: 'GPUStack', label: 'MesaStack',
url: externalLinks.site url: externalLinks.site
}, },
{ {
+1 -1
View File
@@ -1,7 +1,7 @@
export default { export default {
'billing.upsell.title': 'Billing is an Enterprise feature', 'billing.upsell.title': 'Billing is an Enterprise feature',
'billing.upsell.subtitle': 'billing.upsell.subtitle':
'Track spend, generate invoices, and enforce budgets across teams. Upgrade to GPUStack Enterprise to manage billing.', 'Track spend, generate invoices, and enforce budgets across teams. Upgrade to MesaStack Enterprise to manage billing.',
'billing.upsell.featuresTitle': 'What you get in Enterprise', 'billing.upsell.featuresTitle': 'What you get in Enterprise',
'billing.upsell.feature.usage': 'billing.upsell.feature.usage':
'See cost breakdowns by organization, user, and model', 'See cost breakdowns by organization, user, and model',
+8 -8
View File
@@ -85,7 +85,7 @@ export default {
'clusters.addworker.detectWorkerAddress.tips': 'clusters.addworker.detectWorkerAddress.tips':
'Defaults to Worker IP if not specified.', 'Defaults to Worker IP if not specified.',
'clusters.addworker.externalIP.tips': 'clusters.addworker.externalIP.tips':
'If running in a VPC or private network, please specify the Worker external address reachable by the GPUStack Server.', 'If running in a VPC or private network, please specify the Worker external address reachable by the MesaStack Server.',
'clusters.addworker.enterWorkerIP': 'Enter worker IP', 'clusters.addworker.enterWorkerIP': 'Enter worker IP',
'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.', 'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.',
'clusters.addworker.enterWorkerAddress': 'Enter worker external address', 'clusters.addworker.enterWorkerAddress': 'Enter worker external address',
@@ -113,20 +113,20 @@ export default {
'{count} new worker has been added to the cluster.', '{count} new worker has been added to the cluster.',
'clusters.addworker.message.success_multiple': 'clusters.addworker.message.success_multiple':
'{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': 'MesaStack Server URL',
'clusters.create.workerConfig': 'Worker Configuration', 'clusters.create.workerConfig': 'Worker Configuration',
'clusters.edit.k8sOptions.changed.tip': '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.', '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.',
'clusters.addworker.dataVolume': 'GPUStack Data Volume', 'clusters.addworker.dataVolume': 'MesaStack Data Volume',
'clusters.addworker.dataVolume.tips': 'clusters.addworker.dataVolume.tips':
'Specify a data storage path for GPUStack.', 'Specify a data storage path for MesaStack.',
'clusters.table.ip.internal': 'Internal', 'clusters.table.ip.internal': 'Internal',
'clusters.table.ip.external': 'External', 'clusters.table.ip.external': 'External',
'clusters.form.serverUrl.tips': 'clusters.form.serverUrl.tips':
'Specify an externally accessible GPUStack service URL if the worker cannot access GPUStack Server directly.', 'Specify an externally accessible MesaStack service URL if the worker cannot access MesaStack Server directly.',
'clusters.form.setDefault': 'Set as Default', 'clusters.form.setDefault': 'Set as Default',
'clusters.form.setDefault.tips': 'Default for deployment.', 'clusters.form.setDefault.tips': 'Default for deployment.',
'clusters.addworker.noClusters': 'No available Docker clusters found', 'clusters.addworker.noClusters': 'No available Docker clusters found',
@@ -144,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 require <span class="bold-text">CUDA 12.8+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">570</span> or newer.', 'The built-in inference backends in MesaStack require <span class="bold-text">CUDA 12.8+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">570</span> or newer.',
'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',
@@ -171,7 +171,7 @@ export default {
'clusters.volume.add': 'Add Volume Mount', 'clusters.volume.add': 'Add Volume Mount',
'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 MesaStack images for this cluster. Falls back to the server default when unset.',
'clusters.k8sOptions.title': 'Kubernetes 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',
@@ -183,7 +183,7 @@ export default {
'Pod nodeSelector applied to every worker DaemonSet — only nodes whose labels match are eligible to run the worker.', 'Pod nodeSelector applied to every worker DaemonSet — only nodes whose labels match are eligible to run the worker.',
'clusters.operatorImage.title': 'Operator Image', 'clusters.operatorImage.title': 'Operator Image',
'clusters.operatorImage.tip': 'clusters.operatorImage.tip':
'Override for the GPUStack Operator container image. Leave empty to use the server default.', 'Override for the MesaStack Operator container image. Leave empty to use the server default.',
'clusters.namespace.title': 'Namespace', 'clusters.namespace.title': 'Namespace',
'clusters.namespace.tip': 'clusters.namespace.tip':
'Kubernetes namespace the clusters manifests render into. Leave empty to use gpustack-system.', 'Kubernetes namespace the clusters manifests render into. Leave empty to use gpustack-system.',
+2 -2
View File
@@ -46,7 +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.enterprise.feature': 'Available in MesaStack 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',
@@ -214,7 +214,7 @@ export default {
'common.form.password': 'Password', 'common.form.password': 'Password',
'common.form.username': 'Username', 'common.form.username': 'Username',
'common.login.rember': 'Remember me', 'common.login.rember': 'Remember me',
'settings.company': 'GPUStack.ai', 'settings.company': 'MesaStack',
'common.button.help': 'Help', 'common.button.help': 'Help',
'common.button.feedback': 'Feedback', 'common.button.feedback': 'Feedback',
'common.button.docs': 'Documentation', 'common.button.docs': 'Documentation',
+4 -4
View File
@@ -14,7 +14,7 @@ export default {
'models.form.env': 'Environment Variables', 'models.form.env': 'Environment Variables',
'models.form.configurations': 'Configurations', 'models.form.configurations': 'Configurations',
'models.form.s3address': 'S3 Address', 'models.form.s3address': 'S3 Address',
'models.form.partialoffload.tips': `When CPU offloading is enabled, GPUStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`, 'models.form.partialoffload.tips': `When CPU offloading is enabled, MesaStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`,
'models.form.distribution.tips': `Allows for offloading part of the model's layers to single or multiple remote workers when the resources of a worker are insufficient.`, 'models.form.distribution.tips': `Allows for offloading part of the model's layers to single or multiple remote workers when the resources of a worker are insufficient.`,
'models.openinplayground': 'Open in Playground', 'models.openinplayground': 'Open in Playground',
'models.instances': 'instances', 'models.instances': 'instances',
@@ -24,7 +24,7 @@ export default {
'model.deploy.sort': 'Sort', 'model.deploy.sort': 'Sort',
'model.deploy.search.placeholder': 'Type <kbd>/</kbd> to search models', 'model.deploy.search.placeholder': 'Type <kbd>/</kbd> to search models',
'model.form.ollamatips': 'model.form.ollamatips':
'Tip: The following are the preconfigured Ollama models in GPUStack. Please select the model you want, or directly enter the model you wish to deploy in the 【{name}】 input box on the right.', 'Tip: The following are the preconfigured Ollama models in MesaStack. Please select the model you want, or directly enter the model you wish to deploy in the 【{name}】 input box on the right.',
'models.sort.name': 'Name', 'models.sort.name': 'Name',
'models.sort.size': 'Size', 'models.sort.size': 'Size',
'models.sort.likes': 'Likes', 'models.sort.likes': 'Likes',
@@ -87,7 +87,7 @@ export default {
'models.form.filePath': 'Model Path', 'models.form.filePath': 'Model Path',
'models.form.backendVersion': 'Backend Version', 'models.form.backendVersion': 'Backend Version',
'models.form.backendVersion.tips': 'models.form.backendVersion.tips':
'To use the desired version of {backend}{version}, the system will automatically create a virtual environment in the online environment to install the corresponding version. After a GPUStack upgrade, the backend version will remain fixed. {link}', 'To use the desired version of {backend}{version}, the system will automatically create a virtual environment in the online environment to install the corresponding version. After a MesaStack upgrade, the backend version will remain fixed. {link}',
'models.form.gpuselector': 'GPU Selector', 'models.form.gpuselector': 'GPU Selector',
'models.form.backend.llamabox': 'models.form.backend.llamabox':
'For GGUF format models, supports Linux, macOS, and Windows.', 'For GGUF format models, supports Linux, macOS, and Windows.',
@@ -277,7 +277,7 @@ export default {
'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`, 'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`,
'models.catalog.nogpus.tips': 'models.catalog.nogpus.tips':
'No compatible GPUs are available in the selected cluster for this model.', 'No compatible GPUs are available in the selected cluster for this model.',
'models.form.modelfile.notfound': `The model file path you specified does not exist on the GPUStack server. It's recommended to place the model file at the same path on both the GPUStack server and GPUStack workers. This helps GPUStack make better decisions.`, 'models.form.modelfile.notfound': `The model file path you specified does not exist on the MesaStack server. It's recommended to place the model file at the same path on both the MesaStack server and MesaStack workers. This helps MesaStack make better decisions.`,
'models.form.readyWorkers': 'workers ready', 'models.form.readyWorkers': 'workers ready',
'models.form.maxContextLength': 'Maximum Context Length', 'models.form.maxContextLength': 'Maximum Context Length',
'models.form.backend.helperText': 'models.form.backend.helperText':
+1 -1
View File
@@ -1,7 +1,7 @@
export default { export default {
'organizations.upsell.title': 'Organizations are an Enterprise feature', 'organizations.upsell.title': 'Organizations are an Enterprise feature',
'organizations.upsell.subtitle': 'organizations.upsell.subtitle':
'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to GPUStack Enterprise to manage organizations.', 'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to MesaStack Enterprise to manage organizations.',
'organizations.upsell.featuresTitle': 'What you get in Enterprise', 'organizations.upsell.featuresTitle': 'What you get in Enterprise',
'organizations.upsell.feature.orgs': 'organizations.upsell.feature.orgs':
'Create organizations to group users and isolate workloads', 'Create organizations to group users and isolate workloads',
+3 -3
View File
@@ -52,7 +52,7 @@ export default {
'resources.worker.container.supported': 'Do not support macOS or Windows.', 'resources.worker.container.supported': 'Do not support macOS or Windows.',
'resources.worker.current.version': 'Current version is {version}.', 'resources.worker.current.version': 'Current version is {version}.',
'resources.worker.driver.install': 'resources.worker.driver.install':
'Install <a href="https://docs.gpustack.ai/latest/installation/installation-requirements/" target="_blank">required drivers and libraries</a> prior to GPUStack installation.', 'Install <a href="https://docs.gpustack.ai/latest/installation/installation-requirements/" target="_blank">required drivers and libraries</a> prior to MesaStack installation.',
'resources.worker.select.command': 'resources.worker.select.command':
'Select a label to generate the command and copy it using the copy button.', 'Select a label to generate the command and copy it using the copy button.',
'resources.worker.script.install': 'Script Installation', 'resources.worker.script.install': 'Script Installation',
@@ -89,7 +89,7 @@ export default {
'Paste the <span class="bold-text">Token</span>.', 'Paste the <span class="bold-text">Token</span>.',
'resources.register.worker.step7': 'resources.register.worker.step7':
'Click <span class="bold-text">Restart</span> to apply the settings.', 'Click <span class="bold-text">Restart</span> to apply the settings.',
'resources.register.install.title': 'Install GPUStack on {os}', 'resources.register.install.title': 'Install MesaStack on {os}',
'resources.register.download': 'resources.register.download':
'Download and install the <a href={url} target="_blank">installer</a>. Only supported: {versions}.', 'Download and install the <a href={url} target="_blank">installer</a>. Only supported: {versions}.',
'resource.register.maos.support': 'Apple Silicon (M series), macOS 14+', 'resource.register.maos.support': 'Apple Silicon (M series), macOS 14+',
@@ -111,7 +111,7 @@ export default {
'No available clusters. Please create a cluster before adding a node.', 'No available clusters. Please create a cluster before adding a node.',
'resources.metrics.details': 'Monitoring', 'resources.metrics.details': 'Monitoring',
'resoureces.worker.upgrade.tips': 'resoureces.worker.upgrade.tips':
'Please upgrade to match the GPUStack Server version.', 'Please upgrade to match the MesaStack Server version.',
'resources.worker.version': 'Worker Version: {version}', 'resources.worker.version': 'Worker Version: {version}',
'resources.server.version': 'Server Version: {version}', 'resources.server.version': 'Server Version: {version}',
'resources.worker.currentVersion': 'Current Version: {version}', 'resources.worker.currentVersion': 'Current Version: {version}',
+3 -3
View File
@@ -34,12 +34,12 @@ export default {
'users.password.confirm.empty': 'Please confirm the new password.', 'users.password.confirm.empty': 'Please confirm the new password.',
'users.password.confirm.error': 'The two passwords entered do not match.', 'users.password.confirm.error': 'The two passwords entered do not match.',
'users.login.title': 'Log in to', 'users.login.title': 'Log in to',
'users.version.islatest': 'GPUStack {version} is the latest version', 'users.version.islatest': 'MesaStack {version} is the latest version',
'users.version.update': 'GPUStack {version} is available', 'users.version.update': 'MesaStack {version} is available',
'users.settings.title': 'User Settings', 'users.settings.title': 'User Settings',
'users.status.activate': 'Activate Account', 'users.status.activate': 'Activate Account',
'users.status.deactivate': 'Deactivate Account', 'users.status.deactivate': 'Deactivate Account',
'users.status.inactiveAccount': 'Inactive Account', 'users.status.inactiveAccount': 'Inactive Account',
'users.login.getInitialPassword': 'users.login.getInitialPassword':
'Run the following command on your GPUStack Server to retrieve the initial admin password.' 'Run the following command on your MesaStack Server to retrieve the initial admin password.'
}; };
+1 -1
View File
@@ -1,7 +1,7 @@
export default { export default {
'billing.upsell.title': 'Billing is an Enterprise feature', 'billing.upsell.title': 'Billing is an Enterprise feature',
'billing.upsell.subtitle': 'billing.upsell.subtitle':
'Track spend, generate invoices, and enforce budgets across teams. Upgrade to GPUStack Enterprise to manage billing.', 'Track spend, generate invoices, and enforce budgets across teams. Upgrade to MesaStack Enterprise to manage billing.',
'billing.upsell.featuresTitle': 'What you get in Enterprise', 'billing.upsell.featuresTitle': 'What you get in Enterprise',
'billing.upsell.feature.usage': 'billing.upsell.feature.usage':
'See cost breakdowns by organization, user, and model', 'See cost breakdowns by organization, user, and model',
+12 -12
View File
@@ -113,20 +113,20 @@ export default {
'{count} new worker has been added to the cluster.', '{count} new worker has been added to the cluster.',
'clusters.addworker.message.success_multiple': 'clusters.addworker.message.success_multiple':
'{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': 'MesaStack Server URL',
'clusters.create.workerConfig': 'Worker Configuration', 'clusters.create.workerConfig': 'Worker Configuration',
'clusters.edit.k8sOptions.changed.tip': 'clusters.edit.k8sOptions.changed.tip':
'Kubernetes オプションを変更しました。変更を有効にするには、対象クラスターで登録コマンドを再実行してください。', '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.',
'clusters.addworker.dataVolume': 'GPUStack Data Volume', 'clusters.addworker.dataVolume': 'MesaStack Data Volume',
'clusters.addworker.dataVolume.tips': 'clusters.addworker.dataVolume.tips':
'Specify a data storage path for GPUStack.', 'Specify a data storage path for MesaStack.',
'clusters.table.ip.internal': 'Internal', 'clusters.table.ip.internal': 'Internal',
'clusters.table.ip.external': 'External', 'clusters.table.ip.external': 'External',
'clusters.form.serverUrl.tips': 'clusters.form.serverUrl.tips':
'Specify an externally accessible GPUStack service URL if the worker cannot access GPUStack Server directly.', 'Specify an externally accessible MesaStack service URL if the worker cannot access MesaStack Server directly.',
'clusters.form.setDefault': 'Set as Default', 'clusters.form.setDefault': 'Set as Default',
'clusters.form.setDefault.tips': 'Default for deployment.', 'clusters.form.setDefault.tips': 'Default for deployment.',
'clusters.addworker.noClusters': 'No available Docker clusters found', 'clusters.addworker.noClusters': 'No available Docker clusters found',
@@ -144,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 require <span class="bold-text">CUDA 12.8+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">570</span> or newer.', 'The built-in inference backends in MesaStack require <span class="bold-text">CUDA 12.8+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">570</span> or newer.',
'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',
@@ -171,7 +171,7 @@ export default {
'clusters.volume.add': 'Add Volume Mount', 'clusters.volume.add': 'Add Volume Mount',
'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 MesaStack images for this cluster. Falls back to the server default when unset.',
'clusters.k8sOptions.title': 'Kubernetes 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',
@@ -183,7 +183,7 @@ export default {
'Pod nodeSelector applied to every worker DaemonSet — only nodes whose labels match are eligible to run the worker.', 'Pod nodeSelector applied to every worker DaemonSet — only nodes whose labels match are eligible to run the worker.',
'clusters.operatorImage.title': 'Operator Image', 'clusters.operatorImage.title': 'Operator Image',
'clusters.operatorImage.tip': 'clusters.operatorImage.tip':
'Override for the GPUStack Operator container image. Leave empty to use the server default.', 'Override for the MesaStack Operator container image. Leave empty to use the server default.',
'clusters.namespace.title': 'Namespace', 'clusters.namespace.title': 'Namespace',
'clusters.namespace.tip': 'clusters.namespace.tip':
'Kubernetes namespace the clusters manifests render into. Leave empty to use gpustack-system.', 'Kubernetes namespace the clusters manifests render into. Leave empty to use gpustack-system.',
@@ -276,15 +276,15 @@ export default {
// 73. 'clusters.addworker.cacheVolume.holder': 'e.g. /data/cache (path must start with /)', // 73. 'clusters.addworker.cacheVolume.holder': 'e.g. /data/cache (path must start with /)',
// 74. 'clusters.addworker.message.success_single': '{count} new worker has been added to the cluster.', // 74. 'clusters.addworker.message.success_single': '{count} new worker has been added to the cluster.',
// 75. 'clusters.addworker.message.success_multiple': '{count} new workers have been added to the cluster.', // 75. 'clusters.addworker.message.success_multiple': '{count} new workers have been added to the cluster.',
// 76. 'clusters.create.serverUrl': 'GPUStack Server URL', // 76. 'clusters.create.serverUrl': 'MesaStack Server URL',
// 77. 'clusters.create.workerConfig': 'Worker Configuration' // 77. 'clusters.create.workerConfig': 'Worker Configuration'
// 78. 'clusters.addworker.containerName': 'Worker Container Name', // 78. 'clusters.addworker.containerName': 'Worker Container Name',
// 79. 'clusters.addworker.containerName.tips':'Specify a name for the worker container.', // 79. 'clusters.addworker.containerName.tips':'Specify a name for the worker container.',
// 77. 'clusters.addworker.dataVolume': 'GPUStack Data Volume', // 77. 'clusters.addworker.dataVolume': 'MesaStack Data Volume',
// 78. 'clusters.addworker.dataVolume.tips': 'Specify a data storage path for GPUStack.', // 78. 'clusters.addworker.dataVolume.tips': 'Specify a data storage path for MesaStack.',
// 79. 'clusters.table.ip.internal': 'Internal', // 79. 'clusters.table.ip.internal': 'Internal',
// 80. 'clusters.table.ip.external': 'External', // 80. 'clusters.table.ip.external': 'External',
// 81. 'clusters.form.serverUrl.tips': 'Specify an externally accessible GPUStack service URL if the worker cannot access GPUStack Server directly.', // 81. 'clusters.form.serverUrl.tips': 'Specify an externally accessible MesaStack service URL if the worker cannot access MesaStack Server directly.',
// 82. 'clusters.addworker.externalIP.tips': 'Specify an external IP if the worker is in a VPC or private network.', // 82. 'clusters.addworker.externalIP.tips': 'Specify an external IP if the worker is in a VPC or private network.',
// 83. 'clusters.form.setDefault': 'Set as Default', // 83. 'clusters.form.setDefault': 'Set as Default',
// 84. 'clusters.form.setDefault.tips': 'Default for deployment', // 84. 'clusters.form.setDefault.tips': 'Default for deployment',
@@ -300,5 +300,5 @@ export default {
// 94. 'clusters.create.steps.configure': 'Configure', // 94. 'clusters.create.steps.configure': 'Configure',
// 99. 'clusters.addworker.theadNotes': 'If the <span class="bold-text>/usr/local/PPU_SDK</span> directory does not exist, please create a symbolic link pointing to the T-Head PPU SDK installed path: <span class="bold-text>ln -s /path/to/PPU_SDK /usr/local/PPU_SDK</span>', // 99. 'clusters.addworker.theadNotes': 'If the <span class="bold-text>/usr/local/PPU_SDK</span> directory does not exist, please create a symbolic link pointing to the T-Head PPU SDK installed path: <span class="bold-text>ln -s /path/to/PPU_SDK /usr/local/PPU_SDK</span>',
// 100. 'clusters.addworker.theadNotes-02': 'T-Head PPU uses the Container Device Interface (CDI) for device injection and requires the <span class="bold-text">/var/run/cdi</span> directory to be available for CDI generation.', // 100. 'clusters.addworker.theadNotes-02': 'T-Head PPU uses the Container Device Interface (CDI) for device injection and requires the <span class="bold-text">/var/run/cdi</span> directory to be available for CDI generation.',
// 101. 'clusters.addworker.nvidiaNotes': 'The built-in inference backends in 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.' // 101. 'clusters.addworker.nvidiaNotes': 'The built-in inference backends in MesaStack v2.1 require <span class="bold-text">CUDA 12.6+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">560</span> or newer.'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+2 -2
View File
@@ -46,7 +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.enterprise.feature': 'Available in MesaStack Enterprise',
'common.input.holder': '入力してください', 'common.input.holder': '入力してください',
'common.validate.value': '{name} の値は必須です', 'common.validate.value': '{name} の値は必須です',
'common.button.edit': '編集', 'common.button.edit': '編集',
@@ -213,7 +213,7 @@ export default {
'common.form.password': 'パスワード', 'common.form.password': 'パスワード',
'common.form.username': 'ユーザー名', 'common.form.username': 'ユーザー名',
'common.login.rember': 'ログイン状態を保持', 'common.login.rember': 'ログイン状態を保持',
'settings.company': 'GPUStack.ai', 'settings.company': 'MesaStack',
'common.button.help': 'ヘルプ', 'common.button.help': 'ヘルプ',
'common.button.feedback': 'フィードバック', 'common.button.feedback': 'フィードバック',
'common.button.docs': 'ドキュメント', 'common.button.docs': 'ドキュメント',
+6 -6
View File
@@ -14,7 +14,7 @@ export default {
'models.form.env': '環境変数', 'models.form.env': '環境変数',
'models.form.configurations': '設定', 'models.form.configurations': '設定',
'models.form.s3address': 'S3アドレス', 'models.form.s3address': 'S3アドレス',
'models.form.partialoffload.tips': `When CPU offloading is enabled, GPUStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`, 'models.form.partialoffload.tips': `When CPU offloading is enabled, MesaStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`,
'models.form.distribution.tips': 'models.form.distribution.tips':
'ワーカーのリソースが不足している場合、モデルの一部のレイヤーを単一または複数のリモートワーカーにオフロードすることができます。', 'ワーカーのリソースが不足している場合、モデルの一部のレイヤーを単一または複数のリモートワーカーにオフロードすることができます。',
'models.openinplayground': 'プレイグラウンドで開く', 'models.openinplayground': 'プレイグラウンドで開く',
@@ -25,7 +25,7 @@ export default {
'model.deploy.sort': '並び替え', 'model.deploy.sort': '並び替え',
'model.deploy.search.placeholder': '<kbd>/</kbd>を入力してモデルを検索', 'model.deploy.search.placeholder': '<kbd>/</kbd>を入力してモデルを検索',
'model.form.ollamatips': 'model.form.ollamatips':
'ヒント: 以下はGPUStackで事前設定されたOllamaモデルです。希望するモデルを選択するか、右側の【{name}】入力ボックスにデプロイしたいモデルを直接入力してください。', 'ヒント: 以下はMesaStackで事前設定されたOllamaモデルです。希望するモデルを選択するか、右側の【{name}】入力ボックスにデプロイしたいモデルを直接入力してください。',
'models.sort.name': '名前', 'models.sort.name': '名前',
'models.sort.size': 'サイズ', 'models.sort.size': 'サイズ',
'models.sort.likes': 'いいね', 'models.sort.likes': 'いいね',
@@ -88,7 +88,7 @@ export default {
'models.form.filePath': 'モデルパス', 'models.form.filePath': 'モデルパス',
'models.form.backendVersion': 'バックエンドバージョン', 'models.form.backendVersion': 'バックエンドバージョン',
'models.form.backendVersion.tips': 'models.form.backendVersion.tips':
'希望する{backend}{version}バージョンを使用するには、システムがオンライン環境で対応するバージョンをインストールする仮想環境を自動的に作成します。GPUStackのアップグレード後もバックエンドバージョンは固定されます。{link}', '希望する{backend}{version}バージョンを使用するには、システムがオンライン環境で対応するバージョンをインストールする仮想環境を自動的に作成します。MesaStackのアップグレード後もバックエンドバージョンは固定されます。{link}',
'models.form.gpuselector': 'GPUセレクター', 'models.form.gpuselector': 'GPUセレクター',
'models.form.backend.llamabox': 'models.form.backend.llamabox':
'GGUF形式のモデル用(Linux、macOS、Windowsをサポート)。', 'GGUF形式のモデル用(Linux、macOS、Windowsをサポート)。',
@@ -277,7 +277,7 @@ export default {
'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`, 'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`,
'models.catalog.nogpus.tips': 'models.catalog.nogpus.tips':
'No compatible GPUs are available in the selected cluster for this model.', 'No compatible GPUs are available in the selected cluster for this model.',
'models.form.modelfile.notfound': `The model file path you specified does not exist on the GPUStack server. It's recommended to place the model file at the same path on both the GPUStack server and GPUStack workers. This helps GPUStack make better decisions.`, 'models.form.modelfile.notfound': `The model file path you specified does not exist on the MesaStack server. It's recommended to place the model file at the same path on both the MesaStack server and MesaStack workers. This helps MesaStack make better decisions.`,
'models.form.readyWorkers': 'workers ready', 'models.form.readyWorkers': 'workers ready',
'models.form.maxContextLength': 'Maximum Context Length', 'models.form.maxContextLength': 'Maximum Context Length',
'models.form.backend.helperText': 'models.form.backend.helperText':
@@ -381,7 +381,7 @@ export default {
// 62. 'models.form.backend_parameters.vllm.tips': 'For more details about {backend} parameters, see <a href={link} target="_blank">here</a>.', // 62. 'models.form.backend_parameters.vllm.tips': 'For more details about {backend} parameters, see <a href={link} target="_blank">here</a>.',
// 63. 'models.button.accessSettings.tips': 'Changes to access settings take effect after one minute.', // 63. 'models.button.accessSettings.tips': 'Changes to access settings take effect after one minute.',
// 64. 'models.table.userSelection.tips': 'Admin users can access all models by default.', // 64. 'models.table.userSelection.tips': 'Admin users can access all models by default.',
// 65. 'models.form.partialoffload.tips': `When CPU offloading is enabled, GPUStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`, // 65. 'models.form.partialoffload.tips': `When CPU offloading is enabled, MesaStack will allocate CPU memory if GPU resources are insufficient. You must correctly configure the inference backend to use hybrid CPU+GPU or full CPU inference.`,
// 66. 'models.form.backend.warning': 'The selected backend does not support GGUF models. Please add a backend with GGUF support in the Inference Backend.', // 66. 'models.form.backend.warning': 'The selected backend does not support GGUF models. Please add a backend with GGUF support in the Inference Backend.',
// 67. 'models.form.backend.warning.gguf': 'Please ensure that the selected custom backend supports GGUF models.',, // 67. 'models.form.backend.warning.gguf': 'Please ensure that the selected custom backend supports GGUF models.',,
// 68. 'models.form.backendVersion.deprecated': 'Deprecated', // 68. 'models.form.backendVersion.deprecated': 'Deprecated',
@@ -390,7 +390,7 @@ export default {
// 71.'models.accessSettings.allowedUsers.tips': 'Only designated users can access the model.', // 71.'models.accessSettings.allowedUsers.tips': 'Only designated users can access the model.',
// 72. 'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`, // 72. 'models.form.backendVersions.tips': `To use more versions, go to the {link} page and edit the backend to add versions.`,
// 73. 'models.catalog.nogpus.tips': 'No compatible GPUs are available in the selected cluster for this model.', // 73. 'models.catalog.nogpus.tips': 'No compatible GPUs are available in the selected cluster for this model.',
// 74. 'models.form.modelfile.notfound': `The model file path you specified does not exist on the GPUStack server. It's recommended to place the model file at the same path on both the GPUStack server and GPUStack workers. This helps GPUStack make better decisions.`, // 74. 'models.form.modelfile.notfound': `The model file path you specified does not exist on the MesaStack server. It's recommended to place the model file at the same path on both the MesaStack server and MesaStack workers. This helps MesaStack make better decisions.`,
// 75. 'models.form.readyWorkers': 'workers ready', // 75. 'models.form.readyWorkers': 'workers ready',
// 76. 'models.form.maxContextLength': 'Maximum Context Length', // 76. 'models.form.maxContextLength': 'Maximum Context Length',
// 77. 'models.form.backend.helperText': 'Not enabled yet. Will be enabled after deployment. ', // 77. 'models.form.backend.helperText': 'Not enabled yet. Will be enabled after deployment. ',
+1 -1
View File
@@ -1,7 +1,7 @@
export default { export default {
'organizations.upsell.title': 'Organizations are an Enterprise feature', 'organizations.upsell.title': 'Organizations are an Enterprise feature',
'organizations.upsell.subtitle': 'organizations.upsell.subtitle':
'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to GPUStack Enterprise to manage organizations.', 'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to MesaStack Enterprise to manage organizations.',
'organizations.upsell.featuresTitle': 'What you get in Enterprise', 'organizations.upsell.featuresTitle': 'What you get in Enterprise',
'organizations.upsell.feature.orgs': 'organizations.upsell.feature.orgs':
'Create organizations to group users and isolate workloads', 'Create organizations to group users and isolate workloads',
+5 -5
View File
@@ -52,7 +52,7 @@ export default {
'MacOSまたはWindowsはサポートされていません。', 'MacOSまたはWindowsはサポートされていません。',
'resources.worker.current.version': '現在のバージョンは {version} です。', 'resources.worker.current.version': '現在のバージョンは {version} です。',
'resources.worker.driver.install': 'resources.worker.driver.install':
'<a href="https://docs.gpustack.ai/latest/installation/installation-requirements/" target="_blank">必要なドライバとライブラリ</a> をGPUStackのインストール前にインストールしてください。', '<a href="https://docs.gpustack.ai/latest/installation/installation-requirements/" target="_blank">必要なドライバとライブラリ</a> をMesaStackのインストール前にインストールしてください。',
'resources.worker.select.command': 'resources.worker.select.command':
'ラベルを選択してコマンドを生成し、コピーを使用してコマンドをコピーします。', 'ラベルを選択してコマンドを生成し、コピーを使用してコマンドをコピーします。',
'resources.worker.script.install': 'スクリプトインストール', 'resources.worker.script.install': 'スクリプトインストール',
@@ -89,7 +89,7 @@ export default {
'Paste the <span class="bold-text">Token</span>.', 'Paste the <span class="bold-text">Token</span>.',
'resources.register.worker.step7': 'resources.register.worker.step7':
'Click <span class="bold-text">Restart</span> to apply the settings.', 'Click <span class="bold-text">Restart</span> to apply the settings.',
'resources.register.install.title': 'Install GPUStack on {os}', 'resources.register.install.title': 'Install MesaStack on {os}',
'resources.register.download': 'resources.register.download':
'Download and install the <a href={url} target="_blank">installer</a>. Only supported: {versions}.', 'Download and install the <a href={url} target="_blank">installer</a>. Only supported: {versions}.',
'resource.register.maos.support': 'Apple Silicon (M series), macOS 14+', 'resource.register.maos.support': 'Apple Silicon (M series), macOS 14+',
@@ -112,7 +112,7 @@ export default {
'No available clusters. Please create a cluster before adding a node.', 'No available clusters. Please create a cluster before adding a node.',
'resources.metrics.details': 'Monitoring', 'resources.metrics.details': 'Monitoring',
'resoureces.worker.upgrade.tips': 'resoureces.worker.upgrade.tips':
'Please upgrade to match the GPUStack Server version.', 'Please upgrade to match the MesaStack Server version.',
'resources.worker.version': 'Worker Version: {version}', 'resources.worker.version': 'Worker Version: {version}',
'resources.server.version': 'Server Version: {version}', 'resources.server.version': 'Server Version: {version}',
'resources.worker.currentVersion': 'Current Version: {version}', 'resources.worker.currentVersion': 'Current Version: {version}',
@@ -128,7 +128,7 @@ export default {
// 5. 'resources.register.worker.step5': 'Enter the <span class="bold-text">Server URL</span>: {url}.', // 5. 'resources.register.worker.step5': 'Enter the <span class="bold-text">Server URL</span>: {url}.',
// 6. 'resources.register.worker.step6': 'Paste the <span class="bold-text">Token</span>.', // 6. 'resources.register.worker.step6': 'Paste the <span class="bold-text">Token</span>.',
// 7. 'resources.register.worker.step7': 'Click <span class="bold-text">Restart</span> to apply the settings.', // 7. 'resources.register.worker.step7': 'Click <span class="bold-text">Restart</span> to apply the settings.',
// 8. 'resources.register.install.title': 'Install GPUStack on {os}', // 8. 'resources.register.install.title': 'Install MesaStack on {os}',
// 9. 'resources.register.download':'Download and install the <a>installer</a>. Only supported: {versions}.', // 9. 'resources.register.download':'Download and install the <a>installer</a>. Only supported: {versions}.',
// 10. 'resource.register.maos.support': 'Apple Silicon (M series), macOS 14+', // 10. 'resource.register.maos.support': 'Apple Silicon (M series), macOS 14+',
// 11. 'resource.register.windows.support': 'win 10, win 11', // 11. 'resource.register.windows.support': 'win 10, win 11',
@@ -145,5 +145,5 @@ export default {
// 22. 'resources.worker.maintenance.remark.rules': 'Please enter maintenance remarks', // 22. 'resources.worker.maintenance.remark.rules': 'Please enter maintenance remarks',
// 23. 'resources.worker.maintenance.tips': 'When maintenance mode is enabled, the node will stop scheduling new model deployment tasks. Running instances will not be affected.', // 23. 'resources.worker.maintenance.tips': 'When maintenance mode is enabled, the node will stop scheduling new model deployment tasks. Running instances will not be affected.',
// 24. 'resources.worker.noCluster.tips': 'No available clusters. Please create a cluster before adding a node.' // 24. 'resources.worker.noCluster.tips': 'No available clusters. Please create a cluster before adding a node.'
// 25. 'resoureces.worker.upgrade.tips': 'Please upgrade to match the GPUStack Server version.' // 25. 'resoureces.worker.upgrade.tips': 'Please upgrade to match the MesaStack Server version.'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+4 -4
View File
@@ -35,14 +35,14 @@ export default {
'users.password.confirm.empty': '新しいパスワードを確認してください。', 'users.password.confirm.empty': '新しいパスワードを確認してください。',
'users.password.confirm.error': '入力された2つのパスワードが一致しません。', 'users.password.confirm.error': '入力された2つのパスワードが一致しません。',
'users.login.title': 'ログイン', 'users.login.title': 'ログイン',
'users.version.islatest': 'GPUStack {version} は最新バージョンです', 'users.version.islatest': 'MesaStack {version} は最新バージョンです',
'users.version.update': 'GPUStack {version} が利用可能です', 'users.version.update': 'MesaStack {version} が利用可能です',
'users.settings.title': 'User Settings', 'users.settings.title': 'User Settings',
'users.status.activate': 'Activate Account', 'users.status.activate': 'Activate Account',
'users.status.deactivate': 'Deactivate Account', 'users.status.deactivate': 'Deactivate Account',
'users.status.inactiveAccount': 'Inactive Account', 'users.status.inactiveAccount': 'Inactive Account',
'users.login.getInitialPassword': 'users.login.getInitialPassword':
'Run the following command on your GPUStack Server to retrieve the initial admin password.' 'Run the following command on your MesaStack Server to retrieve the initial admin password.'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -50,5 +50,5 @@ export default {
// 2. 'users.status.activate': 'Activate Account', // 2. 'users.status.activate': 'Activate Account',
// 3. 'users.status.deactivate': 'Deactivate Account', // 3. 'users.status.deactivate': 'Deactivate Account',
// 4. 'users.status.inactiveAccount': 'Inactive Account', // 4. 'users.status.inactiveAccount': 'Inactive Account',
// 5. 'users.login.getInitialPassword': 'Run the following command on your GPUStack Server to retrieve the initial admin password.' // 5. 'users.login.getInitialPassword': 'Run the following command on your MesaStack Server to retrieve the initial admin password.'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+1 -1
View File
@@ -1,7 +1,7 @@
export default { export default {
'billing.upsell.title': 'Billing is an Enterprise feature', 'billing.upsell.title': 'Billing is an Enterprise feature',
'billing.upsell.subtitle': 'billing.upsell.subtitle':
'Track spend, generate invoices, and enforce budgets across teams. Upgrade to GPUStack Enterprise to manage billing.', 'Track spend, generate invoices, and enforce budgets across teams. Upgrade to MesaStack Enterprise to manage billing.',
'billing.upsell.featuresTitle': 'What you get in Enterprise', 'billing.upsell.featuresTitle': 'What you get in Enterprise',
'billing.upsell.feature.usage': 'billing.upsell.feature.usage':
'See cost breakdowns by organization, user, and model', 'See cost breakdowns by organization, user, and model',
+8 -8
View File
@@ -113,20 +113,20 @@ export default {
'{count} новый воркер был добавлен в кластер.', '{count} новый воркер был добавлен в кластер.',
'clusters.addworker.message.success_multiple': 'clusters.addworker.message.success_multiple':
'{count} новых воркеров были добавлены в кластер.', '{count} новых воркеров были добавлены в кластер.',
'clusters.create.serverUrl': 'URL сервера GPUStack', 'clusters.create.serverUrl': 'URL сервера MesaStack',
'clusters.create.workerConfig': 'Конфигурация воркера', 'clusters.create.workerConfig': 'Конфигурация воркера',
'clusters.edit.k8sOptions.changed.tip': 'clusters.edit.k8sOptions.changed.tip':
'Вы изменили параметры Kubernetes. Чтобы изменения вступили в силу, повторно выполните команду регистрации в целевом кластере.', 'Вы изменили параметры Kubernetes. Чтобы изменения вступили в силу, повторно выполните команду регистрации в целевом кластере.',
'clusters.addworker.containerName': 'Имя контейнера воркера', 'clusters.addworker.containerName': 'Имя контейнера воркера',
'clusters.addworker.containerName.tips': 'clusters.addworker.containerName.tips':
'Укажите имя для контейнера воркера.', 'Укажите имя для контейнера воркера.',
'clusters.addworker.dataVolume': 'Том данных GPUStack', 'clusters.addworker.dataVolume': 'Том данных MesaStack',
'clusters.addworker.dataVolume.tips': 'clusters.addworker.dataVolume.tips':
'Укажите путь для хранения данных GPUStack.', 'Укажите путь для хранения данных MesaStack.',
'clusters.table.ip.internal': 'Внутренний', 'clusters.table.ip.internal': 'Внутренний',
'clusters.table.ip.external': 'Внешний', 'clusters.table.ip.external': 'Внешний',
'clusters.form.serverUrl.tips': 'clusters.form.serverUrl.tips':
'Если рабочий узел не может напрямую получить доступ к GPUStack Server, укажите внешний URL службы GPUStack Server.', 'Если рабочий узел не может напрямую получить доступ к MesaStack Server, укажите внешний URL службы MesaStack Server.',
'clusters.form.setDefault': 'Установить по умолчанию', 'clusters.form.setDefault': 'Установить по умолчанию',
'clusters.form.setDefault.tips': 'clusters.form.setDefault.tips':
'Использовать по умолчанию для развертывания.', 'Использовать по умолчанию для развертывания.',
@@ -145,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 require <span class="bold-text">CUDA 12.8+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">570</span> or newer.', 'The built-in inference backends in MesaStack require <span class="bold-text">CUDA 12.8+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">570</span> or newer.',
'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',
@@ -172,7 +172,7 @@ export default {
'clusters.volume.add': 'Add Volume Mount', 'clusters.volume.add': 'Add Volume Mount',
'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 MesaStack images for this cluster. Falls back to the server default when unset.',
'clusters.k8sOptions.title': 'Kubernetes 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',
@@ -184,7 +184,7 @@ export default {
'Pod nodeSelector applied to every worker DaemonSet — only nodes whose labels match are eligible to run the worker.', 'Pod nodeSelector applied to every worker DaemonSet — only nodes whose labels match are eligible to run the worker.',
'clusters.operatorImage.title': 'Operator Image', 'clusters.operatorImage.title': 'Operator Image',
'clusters.operatorImage.tip': 'clusters.operatorImage.tip':
'Override for the GPUStack Operator container image. Leave empty to use the server default.', 'Override for the MesaStack Operator container image. Leave empty to use the server default.',
'clusters.namespace.title': 'Namespace', 'clusters.namespace.title': 'Namespace',
'clusters.namespace.tip': 'clusters.namespace.tip':
'Kubernetes namespace the clusters manifests render into. Leave empty to use gpustack-system.', 'Kubernetes namespace the clusters manifests render into. Leave empty to use gpustack-system.',
@@ -213,5 +213,5 @@ export default {
// 10. 'clusters.addworker.theadNotes': 'If the <span class="bold-text>/usr/local/PPU_SDK</span> directory does not exist, please create a symbolic link pointing to the T-Head PPU SDK installed path: <span class="bold-text>ln -s /path/to/PPU_SDK /usr/local/PPU_SDK</span>', // 10. 'clusters.addworker.theadNotes': 'If the <span class="bold-text>/usr/local/PPU_SDK</span> directory does not exist, please create a symbolic link pointing to the T-Head PPU SDK installed path: <span class="bold-text>ln -s /path/to/PPU_SDK /usr/local/PPU_SDK</span>',
// 11. 'clusters.addworker.theadNotes-02': 'T-Head PPU uses the Container Device Interface (CDI) for device injection and requires the <span class="bold-text">/var/run/cdi</span> directory to be available for CDI generation.' // 11. 'clusters.addworker.theadNotes-02': 'T-Head PPU uses the Container Device Interface (CDI) for device injection and requires the <span class="bold-text">/var/run/cdi</span> directory to be available for CDI generation.'
// 12. 'clusters.addworker.metaxNotes': `If the <span class="bold-text">/opt/mxdriver</span> or <span class="bold-text">/opt/maca</span> directory does not exist, create a symbolic link to the MetaX driver and SDK installation path: <span class="desc-fill">ln -s /path/to/mxdriver /opt/mxdriver</span><span class="desc-fill">ln -s /path/to/maca /opt/maca</span>.`, // 12. 'clusters.addworker.metaxNotes': `If the <span class="bold-text">/opt/mxdriver</span> or <span class="bold-text">/opt/maca</span> directory does not exist, create a symbolic link to the MetaX driver and SDK installation path: <span class="desc-fill">ln -s /path/to/mxdriver /opt/mxdriver</span><span class="desc-fill">ln -s /path/to/maca /opt/maca</span>.`,
// 13. 'clusters.addworker.nvidiaNotes': 'The built-in inference backends in 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.' // 13. 'clusters.addworker.nvidiaNotes': 'The built-in inference backends in MesaStack v2.1 require <span class="bold-text">CUDA 12.6+</span>. Please ensure your NVIDIA driver version is <span class="bold-text">560</span> or newer.'
// ================================================================ // ================================================================
+2 -2
View File
@@ -46,7 +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.enterprise.feature': 'Available in MesaStack Enterprise',
'common.input.holder': 'Введите значение', 'common.input.holder': 'Введите значение',
'common.validate.value': 'Поле {name} обязательно', 'common.validate.value': 'Поле {name} обязательно',
'common.button.edit': 'Редактировать', 'common.button.edit': 'Редактировать',
@@ -211,7 +211,7 @@ export default {
'common.form.password': 'Пароль', 'common.form.password': 'Пароль',
'common.form.username': 'Имя пользователя', 'common.form.username': 'Имя пользователя',
'common.login.rember': 'Запомнить меня', 'common.login.rember': 'Запомнить меня',
'settings.company': 'GPUStack.ai', 'settings.company': 'MesaStack',
'common.button.help': 'Помощь', 'common.button.help': 'Помощь',
'common.button.feedback': 'Обратная связь', 'common.button.feedback': 'Обратная связь',
'common.button.docs': 'Документация', 'common.button.docs': 'Документация',
+4 -4
View File
@@ -15,7 +15,7 @@ export default {
'models.form.configurations': 'Конфигурации', 'models.form.configurations': 'Конфигурации',
'models.form.s3address': 'S3-адрес', 'models.form.s3address': 'S3-адрес',
'models.form.partialoffload.tips': 'models.form.partialoffload.tips':
'При включении CPU оффлоудинга GPUStack будет выделять оперативную память, если ресурсов GPU недостаточно. Вы должны правильно настроить бэкенд вывода для использования гибридного CPU+GPU или полного CPU вывода.', 'При включении CPU оффлоудинга MesaStack будет выделять оперативную память, если ресурсов GPU недостаточно. Вы должны правильно настроить бэкенд вывода для использования гибридного CPU+GPU или полного CPU вывода.',
'models.form.distribution.tips': 'models.form.distribution.tips':
'Позволяет переносить часть слоёв модели на один или несколько удалённых воркеров, когда ресурсов текущего воркера недостаточно.', 'Позволяет переносить часть слоёв модели на один или несколько удалённых воркеров, когда ресурсов текущего воркера недостаточно.',
'models.openinplayground': 'Открыть в Песочнице', 'models.openinplayground': 'Открыть в Песочнице',
@@ -26,7 +26,7 @@ export default {
'model.deploy.sort': 'Сортировка', 'model.deploy.sort': 'Сортировка',
'model.deploy.search.placeholder': 'Введите <kbd>/</kbd> для поиска моделей', 'model.deploy.search.placeholder': 'Введите <kbd>/</kbd> для поиска моделей',
'model.form.ollamatips': 'model.form.ollamatips':
'Подсказка: ниже представлены предустановленные модели Ollama в GPUStack. Выберите нужную или введите модель для развертывания в поле 【{name}】 справа.', 'Подсказка: ниже представлены предустановленные модели Ollama в MesaStack. Выберите нужную или введите модель для развертывания в поле 【{name}】 справа.',
'models.sort.name': 'По имени', 'models.sort.name': 'По имени',
'models.sort.size': 'По размеру', 'models.sort.size': 'По размеру',
'models.sort.likes': 'По лайкам', 'models.sort.likes': 'По лайкам',
@@ -89,7 +89,7 @@ export default {
'models.form.filePath': 'Путь к модели', 'models.form.filePath': 'Путь к модели',
'models.form.backendVersion': 'Версия бэкенда', 'models.form.backendVersion': 'Версия бэкенда',
'models.form.backendVersion.tips': 'models.form.backendVersion.tips':
'Чтобы использовать желаемую версию {backend} {version}, система автоматически создаст виртуальную среду в онлайн-окружении для установки соответствующей версии. После обновления GPUStack версия бэкенда останется зафиксированной. {link}', 'Чтобы использовать желаемую версию {backend} {version}, система автоматически создаст виртуальную среду в онлайн-окружении для установки соответствующей версии. После обновления MesaStack версия бэкенда останется зафиксированной. {link}',
'models.form.gpuselector': 'Селектор GPU', 'models.form.gpuselector': 'Селектор GPU',
'models.form.backend.llamabox': 'models.form.backend.llamabox':
'Для моделей формата GGUF. Поддержка Linux, macOS и Windows.', 'Для моделей формата GGUF. Поддержка Linux, macOS и Windows.',
@@ -281,7 +281,7 @@ export default {
'models.form.backendVersions.tips': `Чтобы использовать больше версий, перейдите на страницу {link} и отредактируйте бэкенд для добавления версий.`, 'models.form.backendVersions.tips': `Чтобы использовать больше версий, перейдите на страницу {link} и отредактируйте бэкенд для добавления версий.`,
'models.catalog.nogpus.tips': 'models.catalog.nogpus.tips':
'В выбранном кластере нет доступных GPU, совместимых с этой моделью.', 'В выбранном кластере нет доступных GPU, совместимых с этой моделью.',
'models.form.modelfile.notfound': `Указанный путь к файлу модели не существует на сервере GPUStack. Рекомендуется размещать файл модели по одному и тому же пути как на сервере GPUStack, так и на воркерах GPUStack. Это поможет системе принимать лучшие решения по распределению ресурсов.`, 'models.form.modelfile.notfound': `Указанный путь к файлу модели не существует на сервере MesaStack. Рекомендуется размещать файл модели по одному и тому же пути как на сервере MesaStack, так и на воркерах MesaStack. Это поможет системе принимать лучшие решения по распределению ресурсов.`,
'models.form.readyWorkers': 'воркеров готово', 'models.form.readyWorkers': 'воркеров готово',
'models.form.maxContextLength': 'Maximum Context Length', 'models.form.maxContextLength': 'Maximum Context Length',
'models.form.backend.helperText': 'models.form.backend.helperText':
+1 -1
View File
@@ -1,7 +1,7 @@
export default { export default {
'organizations.upsell.title': 'Organizations are an Enterprise feature', 'organizations.upsell.title': 'Organizations are an Enterprise feature',
'organizations.upsell.subtitle': 'organizations.upsell.subtitle':
'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to GPUStack Enterprise to manage organizations.', 'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to MesaStack Enterprise to manage organizations.',
'organizations.upsell.featuresTitle': 'What you get in Enterprise', 'organizations.upsell.featuresTitle': 'What you get in Enterprise',
'organizations.upsell.feature.orgs': 'organizations.upsell.feature.orgs':
'Create organizations to group users and isolate workloads', 'Create organizations to group users and isolate workloads',
+4 -4
View File
@@ -51,7 +51,7 @@ export default {
'resources.worker.container.supported': 'Только для Linux.', 'resources.worker.container.supported': 'Только для Linux.',
'resources.worker.current.version': 'Текущая версия: {version}', 'resources.worker.current.version': 'Текущая версия: {version}',
'resources.worker.driver.install': 'resources.worker.driver.install':
'Установите <a href="https://docs.gpustack.ai/latest/installation/installation-requirements/" target="_blank">необходимые драйверы и библиотеки</a> перед установкой GPUStack.', // Translated 'Установите <a href="https://docs.gpustack.ai/latest/installation/installation-requirements/" target="_blank">необходимые драйверы и библиотеки</a> перед установкой MesaStack.', // Translated
'resources.worker.select.command': 'resources.worker.select.command':
'Выберите метку для генерации команды и скопируйте её.', 'Выберите метку для генерации команды и скопируйте её.',
'resources.worker.script.install': 'Установка скриптом', 'resources.worker.script.install': 'Установка скриптом',
@@ -87,7 +87,7 @@ export default {
'Вставьте <span class="bold-text">Токен</span>.', 'Вставьте <span class="bold-text">Токен</span>.',
'resources.register.worker.step7': 'resources.register.worker.step7':
'Нажмите <span class="bold-text">Перезапуск</span> для применения настроек.', 'Нажмите <span class="bold-text">Перезапуск</span> для применения настроек.',
'resources.register.install.title': 'Установка GPUStack на {os}', 'resources.register.install.title': 'Установка MesaStack на {os}',
'resources.register.download': 'resources.register.download':
'Скачайте и установите <a href={url} target="_blank">инсталлятор</a>. Поддерживаемые версии: {versions}.', 'Скачайте и установите <a href={url} target="_blank">инсталлятор</a>. Поддерживаемые версии: {versions}.',
'resource.register.maos.support': 'Apple Silicon (серия M), macOS 14+', 'resource.register.maos.support': 'Apple Silicon (серия M), macOS 14+',
@@ -110,7 +110,7 @@ export default {
'No available clusters. Please create a cluster before adding a node.', 'No available clusters. Please create a cluster before adding a node.',
'resources.metrics.details': 'Monitoring', 'resources.metrics.details': 'Monitoring',
'resoureces.worker.upgrade.tips': 'resoureces.worker.upgrade.tips':
'Please upgrade to match the GPUStack Server version.', 'Please upgrade to match the MesaStack Server version.',
'resources.worker.version': 'Worker Version: {version}', 'resources.worker.version': 'Worker Version: {version}',
'resources.server.version': 'Server Version: {version}', 'resources.server.version': 'Server Version: {version}',
'resources.worker.currentVersion': 'Current Version: {version}', 'resources.worker.currentVersion': 'Current Version: {version}',
@@ -127,5 +127,5 @@ export default {
// 7. 'resources.worker.maintenance.tips': 'When maintenance mode is enabled, the node will stop scheduling new model deployment tasks. Running instances will not be affected.', // 7. 'resources.worker.maintenance.tips': 'When maintenance mode is enabled, the node will stop scheduling new model deployment tasks. Running instances will not be affected.',
// 8. 'resources.worker.noCluster.tips': 'No available clusters. Please create a cluster before adding a node.', // 8. 'resources.worker.noCluster.tips': 'No available clusters. Please create a cluster before adding a node.',
// 9. 'resources.metrics.details': 'Monitoring', // 9. 'resources.metrics.details': 'Monitoring',
// 10. 'resoureces.worker.upgrade.tips': 'Please upgrade to match the GPUStack Server version.' // 10. 'resoureces.worker.upgrade.tips': 'Please upgrade to match the MesaStack Server version.'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+2 -2
View File
@@ -35,8 +35,8 @@ export default {
'users.password.confirm.empty': 'Подтвердите новый пароль', 'users.password.confirm.empty': 'Подтвердите новый пароль',
'users.password.confirm.error': 'Пароли не совпадают', 'users.password.confirm.error': 'Пароли не совпадают',
'users.login.title': 'Вход в', 'users.login.title': 'Вход в',
'users.version.islatest': 'GPUStack {version} — последняя версия', 'users.version.islatest': 'MesaStack {version} — последняя версия',
'users.version.update': 'Доступно обновление GPUStack {version}', 'users.version.update': 'Доступно обновление MesaStack {version}',
'users.settings.title': 'Настройки пользователя', 'users.settings.title': 'Настройки пользователя',
'users.status.activate': 'Активировать аккаунт', 'users.status.activate': 'Активировать аккаунт',
'users.status.deactivate': 'Деактивировать аккаунт', 'users.status.deactivate': 'Деактивировать аккаунт',
+1 -1
View File
@@ -1,7 +1,7 @@
export default { export default {
'billing.upsell.title': 'Billing is an Enterprise feature', 'billing.upsell.title': 'Billing is an Enterprise feature',
'billing.upsell.subtitle': 'billing.upsell.subtitle':
'Track spend, generate invoices, and enforce budgets across teams. Upgrade to GPUStack Enterprise to manage billing.', 'Track spend, generate invoices, and enforce budgets across teams. Upgrade to MesaStack Enterprise to manage billing.',
'billing.upsell.featuresTitle': 'What you get in Enterprise', 'billing.upsell.featuresTitle': 'What you get in Enterprise',
'billing.upsell.feature.usage': 'billing.upsell.feature.usage':
'See cost breakdowns by organization, user, and model', 'See cost breakdowns by organization, user, and model',
+8 -8
View File
@@ -85,7 +85,7 @@ export default {
'clusters.addworker.detectWorkerAddress.tips': 'clusters.addworker.detectWorkerAddress.tips':
"Belirtilmezse İşçi Düğüm IP'si varsayılır.", "Belirtilmezse İşçi Düğüm IP'si varsayılır.",
'clusters.addworker.externalIP.tips': 'clusters.addworker.externalIP.tips':
'VPC veya özel ağda çalıştırılıyorsa, lütfen GPUStack Sunucusuna erişilebilir İşçi Düğüm harici adresini belirtin.', 'VPC veya özel ağda çalıştırılıyorsa, lütfen MesaStack Sunucusuna erişilebilir İşçi Düğüm harici adresini belirtin.',
'clusters.addworker.enterWorkerIP': "İşçi düğüm IP'sini girin", 'clusters.addworker.enterWorkerIP': "İşçi düğüm IP'sini girin",
'clusters.addworker.enterWorkerIP.error': "Lütfen işçi düğüm IP'sini girin.", 'clusters.addworker.enterWorkerIP.error': "Lütfen işçi düğüm IP'sini girin.",
'clusters.addworker.enterWorkerAddress': 'İşçi düğüm harici adresini girin', 'clusters.addworker.enterWorkerAddress': 'İşçi düğüm harici adresini girin',
@@ -113,20 +113,20 @@ export default {
'{count} yeni işçi düğüm kümeye eklendi.', '{count} yeni işçi düğüm kümeye eklendi.',
'clusters.addworker.message.success_multiple': 'clusters.addworker.message.success_multiple':
'{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': "MesaStack 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': '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.', '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.',
'clusters.addworker.dataVolume': 'GPUStack Veri Birimi', 'clusters.addworker.dataVolume': 'MesaStack Veri Birimi',
'clusters.addworker.dataVolume.tips': 'clusters.addworker.dataVolume.tips':
'GPUStack için veri depolama yolu belirtin.', 'MesaStack için veri depolama yolu belirtin.',
'clusters.table.ip.internal': 'Dahili', 'clusters.table.ip.internal': 'Dahili',
'clusters.table.ip.external': 'Harici', 'clusters.table.ip.external': 'Harici',
'clusters.form.serverUrl.tips': 'clusters.form.serverUrl.tips':
"İşçi düğüm GPUStack Sunucusuna doğrudan erişemiyorsa, harici olarak erişilebilir bir GPUStack hizmet URL'si belirtin.", "İşçi düğüm MesaStack Sunucusuna doğrudan erişemiyorsa, harici olarak erişilebilir bir MesaStack hizmet URL'si belirtin.",
'clusters.form.setDefault': 'Varsayılan Olarak Ayarla', 'clusters.form.setDefault': 'Varsayılan Olarak Ayarla',
'clusters.form.setDefault.tips': 'Dağıtım için varsayılan.', 'clusters.form.setDefault.tips': 'Dağıtım için varsayılan.',
'clusters.addworker.noClusters': 'Kullanılabilir Docker kümesi bulunamadı', 'clusters.addworker.noClusters': 'Kullanılabilir Docker kümesi bulunamadı',
@@ -145,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\'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.', 'MesaStack\'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',
@@ -172,7 +172,7 @@ export default {
'clusters.volume.add': 'Add Volume Mount', 'clusters.volume.add': 'Add Volume Mount',
'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 MesaStack images for this cluster. Falls back to the server default when unset.',
'clusters.k8sOptions.title': 'Kubernetes 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',
@@ -184,7 +184,7 @@ export default {
'Pod nodeSelector applied to every worker DaemonSet — only nodes whose labels match are eligible to run the worker.', 'Pod nodeSelector applied to every worker DaemonSet — only nodes whose labels match are eligible to run the worker.',
'clusters.operatorImage.title': 'Operator Image', 'clusters.operatorImage.title': 'Operator Image',
'clusters.operatorImage.tip': 'clusters.operatorImage.tip':
'Override for the GPUStack Operator container image. Leave empty to use the server default.', 'Override for the MesaStack Operator container image. Leave empty to use the server default.',
'clusters.namespace.title': 'Namespace', 'clusters.namespace.title': 'Namespace',
'clusters.namespace.tip': 'clusters.namespace.tip':
'Kubernetes namespace the clusters manifests render into. Leave empty to use gpustack-system.', 'Kubernetes namespace the clusters manifests render into. Leave empty to use gpustack-system.',
+2 -2
View File
@@ -46,7 +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.enterprise.feature': 'Available in MesaStack 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',
@@ -216,7 +216,7 @@ export default {
'common.form.password': 'Şifre', 'common.form.password': 'Şifre',
'common.form.username': 'Kullanıcı adı', 'common.form.username': 'Kullanıcı adı',
'common.login.rember': 'Beni hatırla', 'common.login.rember': 'Beni hatırla',
'settings.company': 'GPUStack.ai', 'settings.company': 'MesaStack',
'common.button.help': 'Yardım', 'common.button.help': 'Yardım',
'common.button.feedback': 'Geri Bildirim', 'common.button.feedback': 'Geri Bildirim',
'common.button.docs': 'Dokümantasyon', 'common.button.docs': 'Dokümantasyon',
+4 -4
View File
@@ -14,7 +14,7 @@ export default {
'models.form.env': 'Ortam Değişkenleri', 'models.form.env': 'Ortam Değişkenleri',
'models.form.configurations': 'Yapılandırmalar', 'models.form.configurations': 'Yapılandırmalar',
'models.form.s3address': 'S3 Adresi', 'models.form.s3address': 'S3 Adresi',
'models.form.partialoffload.tips': `CPU aktarımı etkinleştirildiğinde, GPU kaynakları yetersiz olduğunda GPUStack CPU belleği ayırır. Hibrit CPU+GPU veya tam CPU çıkarımı kullanmak için çıkarım altyapısını doğru şekilde yapılandırmanız gerekir.`, 'models.form.partialoffload.tips': `CPU aktarımı etkinleştirildiğinde, GPU kaynakları yetersiz olduğunda MesaStack CPU belleği ayırır. Hibrit CPU+GPU veya tam CPU çıkarımı kullanmak için çıkarım altyapısını doğru şekilde yapılandırmanız gerekir.`,
'models.form.distribution.tips': `Bir işçi düğümün kaynakları yetersiz olduğunda, modelin katmanlarının bir kısmının tekli veya çoklu uzak işçi düğümlere aktarılmasına olanak tanır.`, 'models.form.distribution.tips': `Bir işçi düğümün kaynakları yetersiz olduğunda, modelin katmanlarının bir kısmının tekli veya çoklu uzak işçi düğümlere aktarılmasına olanak tanır.`,
'models.openinplayground': 'Deneme Alanında Aç', 'models.openinplayground': 'Deneme Alanında Aç',
'models.instances': 'örnekler', 'models.instances': 'örnekler',
@@ -24,7 +24,7 @@ export default {
'model.deploy.sort': 'Sırala', 'model.deploy.sort': 'Sırala',
'model.deploy.search.placeholder': 'Modelleri aramak için <kbd>/</kbd> yazın', 'model.deploy.search.placeholder': 'Modelleri aramak için <kbd>/</kbd> yazın',
'model.form.ollamatips': 'model.form.ollamatips':
"İpucu: Aşağıdakiler GPUStack'te önceden yapılandırılmış Ollama modelleridir. İstediğiniz modeli seçin veya dağıtmak istediğiniz modeli doğrudan sağdaki 【{name}】 giriş kutusuna yazın.", "İpucu: Aşağıdakiler MesaStack'te önceden yapılandırılmış Ollama modelleridir. İstediğiniz modeli seçin veya dağıtmak istediğiniz modeli doğrudan sağdaki 【{name}】 giriş kutusuna yazın.",
'models.sort.name': 'Ad', 'models.sort.name': 'Ad',
'models.sort.size': 'Boyut', 'models.sort.size': 'Boyut',
'models.sort.likes': 'Beğeniler', 'models.sort.likes': 'Beğeniler',
@@ -87,7 +87,7 @@ export default {
'models.form.filePath': 'Model Yolu', 'models.form.filePath': 'Model Yolu',
'models.form.backendVersion': 'Altyapı Sürümü', 'models.form.backendVersion': 'Altyapı Sürümü',
'models.form.backendVersion.tips': 'models.form.backendVersion.tips':
'{backend}{version} sürümünü kullanmak için sistem, ilgili sürümü yüklemek üzere çevrimiçi ortamda otomatik olarak sanal ortam oluşturur. GPUStack yükseltmesinden sonra altyapı sürümü sabit kalır. {link}', '{backend}{version} sürümünü kullanmak için sistem, ilgili sürümü yüklemek üzere çevrimiçi ortamda otomatik olarak sanal ortam oluşturur. MesaStack yükseltmesinden sonra altyapı sürümü sabit kalır. {link}',
'models.form.gpuselector': 'GPU Seçici', 'models.form.gpuselector': 'GPU Seçici',
'models.form.backend.llamabox': 'models.form.backend.llamabox':
'GGUF format modeller için, Linux, macOS ve Windows destekler.', 'GGUF format modeller için, Linux, macOS ve Windows destekler.',
@@ -277,7 +277,7 @@ export default {
'models.form.backendVersions.tips': `Daha fazla sürüm kullanmak için {link} sayfasına gidin ve sürüm eklemek üzere altyapıyı düzenleyin.`, 'models.form.backendVersions.tips': `Daha fazla sürüm kullanmak için {link} sayfasına gidin ve sürüm eklemek üzere altyapıyı düzenleyin.`,
'models.catalog.nogpus.tips': 'models.catalog.nogpus.tips':
'Seçili kümede bu model için uyumlu GPU bulunmuyor.', 'Seçili kümede bu model için uyumlu GPU bulunmuyor.',
'models.form.modelfile.notfound': `Belirttiğiniz model dosyası yolu GPUStack sunucusunda mevcut değil. Model dosyasını hem GPUStack sunucusunda hem de GPUStack işçi düğümlerinde aynı yola yerleştirmeniz önerilir. Bu, GPUStack'in daha iyi kararlar almasına yardımcı olur.`, 'models.form.modelfile.notfound': `Belirttiğiniz model dosyası yolu MesaStack sunucusunda mevcut değil. Model dosyasını hem MesaStack sunucusunda hem de MesaStack işçi düğümlerinde aynı yola yerleştirmeniz önerilir. Bu, MesaStack'in daha iyi kararlar almasına yardımcı olur.`,
'models.form.readyWorkers': 'hazır işçi düğüm', 'models.form.readyWorkers': 'hazır işçi düğüm',
'models.form.maxContextLength': 'Maksimum Bağlam Uzunluğu', 'models.form.maxContextLength': 'Maksimum Bağlam Uzunluğu',
'models.form.backend.helperText': 'models.form.backend.helperText':
+1 -1
View File
@@ -1,7 +1,7 @@
export default { export default {
'organizations.upsell.title': 'Organizations are an Enterprise feature', 'organizations.upsell.title': 'Organizations are an Enterprise feature',
'organizations.upsell.subtitle': 'organizations.upsell.subtitle':
'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to GPUStack Enterprise to manage organizations.', 'Multi-tenancy lets you isolate users, resources, and quotas across teams. Upgrade to MesaStack Enterprise to manage organizations.',
'organizations.upsell.featuresTitle': 'What you get in Enterprise', 'organizations.upsell.featuresTitle': 'What you get in Enterprise',
'organizations.upsell.feature.orgs': 'organizations.upsell.feature.orgs':
'Create organizations to group users and isolate workloads', 'Create organizations to group users and isolate workloads',
+4 -4
View File
@@ -52,7 +52,7 @@ export default {
'resources.worker.container.supported': 'macOS veya Windows desteklenmez.', 'resources.worker.container.supported': 'macOS veya Windows desteklenmez.',
'resources.worker.current.version': 'Mevcut sürüm: {version}.', 'resources.worker.current.version': 'Mevcut sürüm: {version}.',
'resources.worker.driver.install': 'resources.worker.driver.install':
'GPUStack kurulumundan önce <a href="https://docs.gpustack.ai/latest/installation/installation-requirements/" target="_blank">gerekli sürücüleri ve kütüphaneleri</a> yükleyin.', 'MesaStack kurulumundan önce <a href="https://docs.gpustack.ai/latest/installation/installation-requirements/" target="_blank">gerekli sürücüleri ve kütüphaneleri</a> yükleyin.',
'resources.worker.select.command': 'resources.worker.select.command':
'Komutu oluşturmak için bir etiket seçin ve kopyala düğmesiyle kopyalayın.', 'Komutu oluşturmak için bir etiket seçin ve kopyala düğmesiyle kopyalayın.',
'resources.worker.script.install': 'Betik Kurulumu', 'resources.worker.script.install': 'Betik Kurulumu',
@@ -89,7 +89,7 @@ export default {
'<span class="bold-text">Token</span>\'ı yapıştırın.', '<span class="bold-text">Token</span>\'ı yapıştırın.',
'resources.register.worker.step7': 'resources.register.worker.step7':
'Ayarları uygulamak için <span class="bold-text">Yeniden Başlat</span>\'a tıklayın.', 'Ayarları uygulamak için <span class="bold-text">Yeniden Başlat</span>\'a tıklayın.',
'resources.register.install.title': '{os} üzerine GPUStack kur', 'resources.register.install.title': '{os} üzerine MesaStack kur',
'resources.register.download': 'resources.register.download':
'<a href={url} target="_blank">Yükleyiciyi</a> indirip kurun. Yalnızca desteklenen: {versions}.', '<a href={url} target="_blank">Yükleyiciyi</a> indirip kurun. Yalnızca desteklenen: {versions}.',
'resource.register.maos.support': 'Apple Silicon (M serisi), macOS 14+', 'resource.register.maos.support': 'Apple Silicon (M serisi), macOS 14+',
@@ -110,7 +110,7 @@ export default {
'Kullanılabilir küme yok. Lütfen düğüm eklemeden önce bir küme oluşturun.', 'Kullanılabilir küme yok. Lütfen düğüm eklemeden önce bir küme oluşturun.',
'resources.metrics.details': 'İzleme', 'resources.metrics.details': 'İzleme',
'resoureces.worker.upgrade.tips': 'resoureces.worker.upgrade.tips':
'Please upgrade to match the GPUStack Server version.', 'Please upgrade to match the MesaStack Server version.',
'resources.worker.version': 'Worker Version: {version}', 'resources.worker.version': 'Worker Version: {version}',
'resources.server.version': 'Server Version: {version}', 'resources.server.version': 'Server Version: {version}',
'resources.worker.currentVersion': 'Current Version: {version}', 'resources.worker.currentVersion': 'Current Version: {version}',
@@ -119,5 +119,5 @@ export default {
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'resoureces.worker.upgrade.tips': 'Please upgrade to match the GPUStack Server version.' // 1. 'resoureces.worker.upgrade.tips': 'Please upgrade to match the MesaStack Server version.'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+3 -3
View File
@@ -35,12 +35,12 @@ export default {
'users.password.confirm.empty': 'Lütfen yeni şifreyi tekrar girin.', 'users.password.confirm.empty': 'Lütfen yeni şifreyi tekrar girin.',
'users.password.confirm.error': 'Girilen iki şifre eşleşmiyor.', 'users.password.confirm.error': 'Girilen iki şifre eşleşmiyor.',
'users.login.title': 'Giriş yap:', 'users.login.title': 'Giriş yap:',
'users.version.islatest': 'GPUStack {version} en güncel sürümdür', 'users.version.islatest': 'MesaStack {version} en güncel sürümdür',
'users.version.update': 'GPUStack {version} kullanılabilir', 'users.version.update': 'MesaStack {version} kullanılabilir',
'users.settings.title': 'Kullanıcı Ayarları', 'users.settings.title': 'Kullanıcı Ayarları',
'users.status.activate': 'Hesabı Etkinleştir', 'users.status.activate': 'Hesabı Etkinleştir',
'users.status.deactivate': 'Hesabı Devre Dışı Bırak', 'users.status.deactivate': 'Hesabı Devre Dışı Bırak',
'users.status.inactiveAccount': 'Pasif Hesap', 'users.status.inactiveAccount': 'Pasif Hesap',
'users.login.getInitialPassword': 'users.login.getInitialPassword':
'Başlangıç yönetici şifresini almak için GPUStack Sunucunuzda aşağıdaki komutu çalıştırın.' 'Başlangıç yönetici şifresini almak için MesaStack Sunucunuzda aşağıdaki komutu çalıştırın.'
}; };
+1 -1
View File
@@ -1,7 +1,7 @@
export default { export default {
'billing.upsell.title': '计费是企业版功能', 'billing.upsell.title': '计费是企业版功能',
'billing.upsell.subtitle': 'billing.upsell.subtitle':
'在团队间跟踪花费、生成账单并执行预算。升级到 GPUStack 企业版即可管理计费。', '在团队间跟踪花费、生成账单并执行预算。升级到 MesaStack 企业版即可管理计费。',
'billing.upsell.featuresTitle': '企业版包含的能力', 'billing.upsell.featuresTitle': '企业版包含的能力',
'billing.upsell.feature.usage': '按组织、用户与模型查看成本明细', 'billing.upsell.feature.usage': '按组织、用户与模型查看成本明细',
'billing.upsell.feature.invoices': '生成账单并导出计费报表', 'billing.upsell.feature.invoices': '生成账单并导出计费报表',
+8 -8
View File
@@ -83,7 +83,7 @@ export default {
'clusters.addworker.detectWorkerAddress.tips': 'clusters.addworker.detectWorkerAddress.tips':
'如果未指定,则默认为节点 IP。', '如果未指定,则默认为节点 IP。',
'clusters.addworker.externalIP.tips': 'clusters.addworker.externalIP.tips':
'如运行在 VPC 或私有网络时,请指定 GPUStack Server 可达的节点外部地址。', '如运行在 VPC 或私有网络时,请指定 MesaStack Server 可达的节点外部地址。',
'clusters.addworker.enterWorkerIP': '输入节点 IP', 'clusters.addworker.enterWorkerIP': '输入节点 IP',
'clusters.addworker.enterWorkerIP.error': '请输入节点 IP', 'clusters.addworker.enterWorkerIP.error': '请输入节点 IP',
'clusters.addworker.enterWorkerAddress': '输入节点外部地址', 'clusters.addworker.enterWorkerAddress': '输入节点外部地址',
@@ -111,18 +111,18 @@ export default {
'已将 {count} 个新节点添加到集群中。', '已将 {count} 个新节点添加到集群中。',
'clusters.addworker.message.success_multiple': 'clusters.addworker.message.success_multiple':
'已将 {count} 个新节点添加到集群中。', '已将 {count} 个新节点添加到集群中。',
'clusters.create.serverUrl': 'GPUStack Server 节点地址', 'clusters.create.serverUrl': 'MesaStack Server 节点地址',
'clusters.create.workerConfig': '节点配置', 'clusters.create.workerConfig': '节点配置',
'clusters.edit.k8sOptions.changed.tip': 'clusters.edit.k8sOptions.changed.tip':
'您已修改 Kubernetes 选项,需要在目标集群上重新运行注册命令才会生效。', '您已修改 Kubernetes 选项,需要在目标集群上重新运行注册命令才会生效。',
'clusters.addworker.containerName': '节点容器名称', 'clusters.addworker.containerName': '节点容器名称',
'clusters.addworker.containerName.tips': '为节点容器指定一个名称。', 'clusters.addworker.containerName.tips': '为节点容器指定一个名称。',
'clusters.addworker.dataVolume': 'GPUStack 数据卷', 'clusters.addworker.dataVolume': 'MesaStack 数据卷',
'clusters.addworker.dataVolume.tips': '为 GPUStack 指定数据存储路径。', 'clusters.addworker.dataVolume.tips': '为 MesaStack 指定数据存储路径。',
'clusters.table.ip.internal': '内', 'clusters.table.ip.internal': '内',
'clusters.table.ip.external': '外', 'clusters.table.ip.external': '外',
'clusters.form.serverUrl.tips': 'clusters.form.serverUrl.tips':
'如果节点无法直接访问 GPUStack Server,则指定一个可访问的外部 GPUStack Server 地址。', '如果节点无法直接访问 MesaStack Server,则指定一个可访问的外部 MesaStack Server 地址。',
'clusters.form.setDefault': '设为默认', 'clusters.form.setDefault': '设为默认',
'clusters.form.setDefault.tips': '部署时的默认集群。', 'clusters.form.setDefault.tips': '部署时的默认集群。',
'clusters.addworker.noClusters': '无可用的 Docker 集群', 'clusters.addworker.noClusters': '无可用的 Docker 集群',
@@ -138,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 内置推理后端依赖 <span class="bold-text">CUDA 12.8</span> 及以上版本,请确保 NVIDIA 驱动版本为 <span class="bold-text">570</span> 或以上。', 'MesaStack 内置推理后端依赖 <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': '容器内路径',
@@ -164,7 +164,7 @@ export default {
'clusters.volume.add': '添加卷挂载', 'clusters.volume.add': '添加卷挂载',
'clusters.systemDefaultContainerRegistry.title': '默认容器镜像仓库', 'clusters.systemDefaultContainerRegistry.title': '默认容器镜像仓库',
'clusters.systemDefaultContainerRegistry.tip': 'clusters.systemDefaultContainerRegistry.tip':
'用于解析该集群 GPUStack 镜像的默认镜像仓库。未设置时回退到服务端默认值。', '用于解析该集群 MesaStack 镜像的默认镜像仓库。未设置时回退到服务端默认值。',
'clusters.k8sOptions.title': 'Kubernetes 部署选项', 'clusters.k8sOptions.title': 'Kubernetes 部署选项',
'clusters.imageCredentials.title': '镜像仓库凭证', 'clusters.imageCredentials.title': '镜像仓库凭证',
'clusters.imageCredentials.add': '添加凭证', 'clusters.imageCredentials.add': '添加凭证',
@@ -176,7 +176,7 @@ export default {
'应用到每个 worker DaemonSet 的 Pod nodeSelector,只有标签匹配的节点才会被调度运行 worker。', '应用到每个 worker DaemonSet 的 Pod nodeSelector,只有标签匹配的节点才会被调度运行 worker。',
'clusters.operatorImage.title': 'Operator 镜像', 'clusters.operatorImage.title': 'Operator 镜像',
'clusters.operatorImage.tip': 'clusters.operatorImage.tip':
'GPUStack Operator 容器镜像的覆盖值。留空则使用服务端默认值。', 'MesaStack Operator 容器镜像的覆盖值。留空则使用服务端默认值。',
'clusters.namespace.title': '命名空间', 'clusters.namespace.title': '命名空间',
'clusters.namespace.tip': 'clusters.namespace.tip':
'集群清单渲染所使用的 Kubernetes 命名空间。留空则使用 gpustack-system。', '集群清单渲染所使用的 Kubernetes 命名空间。留空则使用 gpustack-system。',
+2 -2
View File
@@ -44,7 +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.enterprise.feature': 'MesaStack 企业版可用',
'common.input.holder': '请输入', 'common.input.holder': '请输入',
'common.holder.search': '搜索', 'common.holder.search': '搜索',
'common.button.edit': '编辑', 'common.button.edit': '编辑',
@@ -204,7 +204,7 @@ export default {
'common.form.password': '密码', 'common.form.password': '密码',
'common.form.username': '用户名', 'common.form.username': '用户名',
'common.login.rember': '记住我', 'common.login.rember': '记住我',
'settings.company': '数澈软件', 'settings.company': 'MesaStack',
'common.button.help': '帮助', 'common.button.help': '帮助',
'common.button.feedback': '反馈', 'common.button.feedback': '反馈',
'common.button.docs': '文档', 'common.button.docs': '文档',
+4 -4
View File
@@ -15,7 +15,7 @@ export default {
'models.form.configurations': '配置', 'models.form.configurations': '配置',
'models.form.s3address': 'S3 地址', 'models.form.s3address': 'S3 地址',
'models.form.partialoffload.tips': 'models.form.partialoffload.tips':
'启用 CPU 卸载后,GPU 不足时 GPUStack 会自动使用 CPU 内存。请确保推理后端已正确配置为混合 CPU+GPU 或纯 CPU 推理。', '启用 CPU 卸载后,GPU 不足时 MesaStack 会自动使用 CPU 内存。请确保推理后端已正确配置为混合 CPU+GPU 或纯 CPU 推理。',
'models.form.distribution.tips': 'models.form.distribution.tips':
'允许在单个节点资源不足时,将部分计算卸载到一个或多个远程节点。', '允许在单个节点资源不足时,将部分计算卸载到一个或多个远程节点。',
'models.openinplayground': '在 Playground 中打开', 'models.openinplayground': '在 Playground 中打开',
@@ -26,7 +26,7 @@ export default {
'model.deploy.sort': '排序', 'model.deploy.sort': '排序',
'model.deploy.search.placeholder': '按 <kbd>/</kbd> 开始搜索模型', 'model.deploy.search.placeholder': '按 <kbd>/</kbd> 开始搜索模型',
'model.form.ollamatips': 'model.form.ollamatips':
'提示:以下为 GPUStack 预设的 Ollama 模型,请选择你想要的模型或者直接在右侧表单 【{name}】 输入框中输入你要部署的模型。', '提示:以下为 MesaStack 预设的 Ollama 模型,请选择你想要的模型或者直接在右侧表单 【{name}】 输入框中输入你要部署的模型。',
'models.sort.name': '名称', 'models.sort.name': '名称',
'models.sort.size': '大小', 'models.sort.size': '大小',
'models.sort.likes': '点赞量', 'models.sort.likes': '点赞量',
@@ -86,7 +86,7 @@ export default {
'models.form.filePath': '模型路径', 'models.form.filePath': '模型路径',
'models.form.backendVersion': '后端版本', 'models.form.backendVersion': '后端版本',
'models.form.backendVersion.tips': 'models.form.backendVersion.tips':
'固定以使用期望的 {backend} 版本 {version},在线环境会自动创建虚拟环境安装对应版本的 {backend}。在 GPUStack 升级后也将保持固定的后端版本。{link}', '固定以使用期望的 {backend} 版本 {version},在线环境会自动创建虚拟环境安装对应版本的 {backend}。在 MesaStack 升级后也将保持固定的后端版本。{link}',
'models.form.gpuselector': 'GPU 选择器', 'models.form.gpuselector': 'GPU 选择器',
'models.form.backend.llamabox': 'models.form.backend.llamabox':
'用于 GGUF 格式模型,支持 Linux, macOS 和 Windows。', '用于 GGUF 格式模型,支持 Linux, macOS 和 Windows。',
@@ -262,7 +262,7 @@ export default {
'models.form.backendVersions.tips': `如需使用更多版本,请前往{link}页面并编辑对应的后端以添加版本。`, 'models.form.backendVersions.tips': `如需使用更多版本,请前往{link}页面并编辑对应的后端以添加版本。`,
'models.catalog.nogpus.tips': '所选集群中没有兼容该模型的 GPU。', 'models.catalog.nogpus.tips': '所选集群中没有兼容该模型的 GPU。',
'models.form.modelfile.notfound': 'models.form.modelfile.notfound':
'你指定的模型文件路径在 GPUStack Server 节点上不存在。建议在 GPUStack Server 节点和 GPUStack 节点上使用相同的模型文件路径,这有助于 GPUStack 做出更优的调度与决策。', '你指定的模型文件路径在 MesaStack Server 节点上不存在。建议在 MesaStack Server 节点和 MesaStack 节点上使用相同的模型文件路径,这有助于 MesaStack 做出更优的调度与决策。',
'models.form.readyWorkers': '节点就绪', 'models.form.readyWorkers': '节点就绪',
'models.form.maxContextLength': '最大上下文长度', 'models.form.maxContextLength': '最大上下文长度',
'models.form.backend.helperText': '该社区后端暂未启用,部署后将自动启用', 'models.form.backend.helperText': '该社区后端暂未启用,部署后将自动启用',
+1 -1
View File
@@ -1,7 +1,7 @@
export default { export default {
'organizations.upsell.title': '组织是企业版功能', 'organizations.upsell.title': '组织是企业版功能',
'organizations.upsell.subtitle': 'organizations.upsell.subtitle':
'多租户可在团队间隔离用户、资源与配额。升级到 GPUStack 企业版即可管理组织。', '多租户可在团队间隔离用户、资源与配额。升级到 MesaStack 企业版即可管理组织。',
'organizations.upsell.featuresTitle': '企业版包含的能力', 'organizations.upsell.featuresTitle': '企业版包含的能力',
'organizations.upsell.feature.orgs': '创建组织来分组用户并隔离工作负载', 'organizations.upsell.feature.orgs': '创建组织来分组用户并隔离工作负载',
'organizations.upsell.feature.members': '为每个组织管理成员与角色', 'organizations.upsell.feature.members': '为每个组织管理成员与角色',
+3 -3
View File
@@ -52,7 +52,7 @@ export default {
'resources.worker.current.version': '当前版本为 {version}', 'resources.worker.current.version': '当前版本为 {version}',
'resources.worker.select.command': '选择一个标签生成命令并使用复制按钮复制', 'resources.worker.select.command': '选择一个标签生成命令并使用复制按钮复制',
'resources.worker.driver.install': 'resources.worker.driver.install':
'在安装 GPUStack 之前,请安装<a href="https://docs.gpustack.ai/latest/installation/installation-requirements/" target="_blank">所需的驱动程序和库</a>。', '在安装 MesaStack 之前,请安装<a href="https://docs.gpustack.ai/latest/installation/installation-requirements/" target="_blank">所需的驱动程序和库</a>。',
'resources.worker.script.install': '脚本安装', 'resources.worker.script.install': '脚本安装',
'resources.worker.container.install': '容器安装(仅支持 Linux)', 'resources.worker.container.install': '容器安装(仅支持 Linux)',
'resources.worker.cann.tips': 'resources.worker.cann.tips':
@@ -87,7 +87,7 @@ export default {
'粘贴 <span class="bold-text">Token</span>。', '粘贴 <span class="bold-text">Token</span>。',
'resources.register.worker.step7': 'resources.register.worker.step7':
'点击<span class="bold-text">重启</span>,应用设置。', '点击<span class="bold-text">重启</span>,应用设置。',
'resources.register.install.title': '在 {os} 上安装 GPUStack', 'resources.register.install.title': '在 {os} 上安装 MesaStack',
'resources.register.download': 'resources.register.download':
'下载并安装<a href={url} target="_blank">安装包</a>,仅支持 {versions}。', '下载并安装<a href={url} target="_blank">安装包</a>,仅支持 {versions}。',
'resource.register.maos.support': 'M 芯片,macOS 14+', 'resource.register.maos.support': 'M 芯片,macOS 14+',
@@ -106,7 +106,7 @@ export default {
'进入维护模式后,节点将停止调度新的模型实例部署任务,正在运行的实例不会受到影响。', '进入维护模式后,节点将停止调度新的模型实例部署任务,正在运行的实例不会受到影响。',
'resources.worker.noCluster.tips': '当前无可用集群,请先创建集群再添加节点。', 'resources.worker.noCluster.tips': '当前无可用集群,请先创建集群再添加节点。',
'resources.metrics.details': '监控', 'resources.metrics.details': '监控',
'resoureces.worker.upgrade.tips': '请升级到与 GPUStack Server 版本一致。', 'resoureces.worker.upgrade.tips': '请升级到与 MesaStack Server 版本一致。',
'resources.worker.version': '节点版本:{version}', 'resources.worker.version': '节点版本:{version}',
'resources.server.version': 'Server 版本:{version}', 'resources.server.version': 'Server 版本:{version}',
'resources.worker.currentVersion': '当前版本:{version}', 'resources.worker.currentVersion': '当前版本:{version}',
+3 -3
View File
@@ -32,12 +32,12 @@ export default {
'users.password.confirm.empty': '请确认新密码', 'users.password.confirm.empty': '请确认新密码',
'users.password.confirm.error': '两次输入的密码不一致', 'users.password.confirm.error': '两次输入的密码不一致',
'users.login.title': '登录', 'users.login.title': '登录',
'users.version.islatest': 'GPUStack {version} 已是最新版本', 'users.version.islatest': 'MesaStack {version} 已是最新版本',
'users.version.update': 'GPUStack {version} 版本可供更新', 'users.version.update': 'MesaStack {version} 版本可供更新',
'users.settings.title': '用户设置', 'users.settings.title': '用户设置',
'users.status.activate': '启用账户', 'users.status.activate': '启用账户',
'users.status.deactivate': '停用账户', 'users.status.deactivate': '停用账户',
'users.status.inactiveAccount': '停用账户', 'users.status.inactiveAccount': '停用账户',
'users.login.getInitialPassword': 'users.login.getInitialPassword':
'在 GPUStack Server 节点运行以下命令以获取初始管理员密码。' '在 MesaStack Server 节点运行以下命令以获取初始管理员密码。'
}; };
+1 -9
View File
@@ -1,11 +1,9 @@
import { ExtraContent } from '@/layouts/extraRender';
import { import {
PageContainer, PageContainer,
RouteContext, RouteContext,
type PageContainerProps type PageContainerProps
} from '@ant-design/pro-components'; } from '@ant-design/pro-components';
import { useOverlayScroller } from '@gpustack/core-ui'; import { useOverlayScroller } from '@gpustack/core-ui';
import { Divider } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import { useContext, useEffect, useRef } from 'react'; import { useContext, useEffect, useRef } from 'react';
import pageBoxCss from './styles/page-box.less'; import pageBoxCss from './styles/page-box.less';
@@ -53,13 +51,7 @@ export const PageContainerInner: React.FC<
{leftContent || pageContext.title} {leftContent || pageContext.title}
</div> </div>
<div className={pageBoxCss.right}> <div className={pageBoxCss.right}>
{rightContent && ( {rightContent && <div>{rightContent}</div>}
<div>
{rightContent}
<Divider orientation="vertical" style={{ margin: '0 16px' }} />
</div>
)}
<ExtraContent />
</div> </div>
</div> </div>
<div <div
@@ -48,7 +48,7 @@ export const GPUStackFailedStatuses = [
export const InstanceStatusLabelMap: Record<string, string> = { export const InstanceStatusLabelMap: Record<string, string> = {
// === K8s Statuses === // === K8s Statuses ===
...Object.fromEntries(K8SStatuses.map((status) => [status, status])), ...Object.fromEntries(K8SStatuses.map((status) => [status, status])),
// === GPUStack Statuses no logs and events=== // === MesaStack Statuses no logs and events===
[InstanceStatusValueMap.Deleting]: 'Deleting', [InstanceStatusValueMap.Deleting]: 'Deleting',
[InstanceStatusValueMap.Stopping]: 'Stopping', [InstanceStatusValueMap.Stopping]: 'Stopping',
[InstanceStatusValueMap.Stopped]: 'Stopped', [InstanceStatusValueMap.Stopped]: 'Stopped',
+9
View File
@@ -96,3 +96,12 @@ export interface UsageMeta {
} }
export type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>; export type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>;
// The full breakdown filter set (route / user / api_key). Every breakdown
// table sends all active dimensions — matching the trend chart — so e.g. a
// user filter narrows the Models table too, not only the Users table.
export type BreakdownFilters = {
routes?: FilterOptionType[];
users?: FilterOptionType[];
api_keys?: FilterOptionType[];
};
+13 -5
View File
@@ -1,6 +1,7 @@
import { exportJsonToExcel } from '@gpustack/core-ui/excel'; import { exportJsonToExcel } from '@gpustack/core-ui/excel';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { useEffect, useMemo, useState } from 'react'; import _ from 'lodash';
import { useEffect, useRef, useState } from 'react';
import { GroupOption } from '../config'; import { GroupOption } from '../config';
import { BreakdownItem, UsageFilterItem } from '../config/types'; import { BreakdownItem, UsageFilterItem } from '../config/types';
import useQueryTimeSeriesData from '../services/use-query-timeseries-data'; import useQueryTimeSeriesData from '../services/use-query-timeseries-data';
@@ -181,10 +182,17 @@ export const useUsageFilters = ({
return filters; return filters;
}; };
const filters = useMemo( // Keep a stable reference while the content is unchanged. ``buildFilters``
() => buildFilters(commonFilters), // returns a fresh object every render — and again when the meta options
[commonFilters, routeOptions, userOptions, apiKeyOptions] // resolve after mount — which would otherwise retrigger every breakdown
); // table's fetch effect a second time on first load. Only a real selection
// change (or options resolving a previously-selected id) should swap it.
const filtersRef = useRef<ReturnType<typeof buildFilters>>({});
const nextFilters = buildFilters(commonFilters);
if (!_.isEqual(nextFilters, filtersRef.current)) {
filtersRef.current = nextFilters;
}
const filters = filtersRef.current;
const fetchData = ( const fetchData = (
currentSelectedFilters = commonFilters, currentSelectedFilters = commonFilters,
+55 -3
View File
@@ -31,7 +31,9 @@ import { Col, Row } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import ResourceFilterBar from '../components/resource-filter-bar'; import ResourceFilterBar from '../components/resource-filter-bar';
import useResourceMeta from '../hooks/use-resource-meta'; import { FilterOptionType } from '../config/types';
import useResourceMeta, { SelectOption } from '../hooks/use-resource-meta';
import useQueryUsageMetaData from '../services/use-query-meta-data';
import { import {
bucketKey, bucketKey,
generateBucketRange, generateBucketRange,
@@ -231,7 +233,38 @@ const SummaryTab: React.FC = () => {
selectedUsers: [] selectedUsers: []
}); });
const { start, end, selectedUsers } = queryParams; const { start, end, selectedUsers } = queryParams;
const { creators: userOptions } = useResourceMeta(scope); const { creators: resourceUsers } = useResourceMeta(scope);
const { detailData: tokenMeta, fetchData: fetchTokenMeta } =
useQueryUsageMetaData();
// The user filter unions two sources: resource creators (GPU / storage
// usage) and the token-usage users (/usage/meta) — a user may appear in only
// one. Deduped by user id. The token meta also carries the per-user identity
// the token-series endpoint filters on (see ``tokenUserById``).
const userOptions = useMemo<SelectOption[]>(() => {
const map = new Map<number, SelectOption>();
resourceUsers.forEach((u) =>
map.set(u.value, { value: u.value, label: u.label, deleted: u.deleted })
);
(tokenMeta?.users || []).forEach((u) => {
const id = u.identity.current?.user_id;
if (id != null && !map.has(id)) {
map.set(id, { value: id, label: u.label });
}
});
return Array.from(map.values());
}, [resourceUsers, tokenMeta]);
// user id → the identity object the token series filters by. Built from the
// token meta so the trend's ``users`` filter carries the real identity.
const tokenUserById = useMemo(() => {
const map = new Map<number, FilterOptionType>();
(tokenMeta?.users || []).forEach((u) => {
const id = u.identity.current?.user_id;
if (id != null) map.set(id, { identity: u.identity });
});
return map;
}, [tokenMeta]);
const { const {
detailData: summary, detailData: summary,
@@ -285,6 +318,24 @@ const SummaryTab: React.FC = () => {
? { creator_ids: currentParams.selectedUsers } ? { creator_ids: currentParams.selectedUsers }
: undefined; : undefined;
// The token series hits /usage/breakdown, which filters users by identity
// rather than the creator_ids the resource endpoints take — so the token
// trend honors the user filter like the totals do. Resolve each id to its
// token-meta identity, falling back to a minimal current.user_id object for
// users present only in the resource meta.
const tokenUserFilter: { users?: FilterOptionType[] } = currentParams
.selectedUsers.length
? {
users: currentParams.selectedUsers.map(
(id) =>
tokenUserById.get(id) ??
({
identity: { current: { user_id: id } }
} as unknown as FilterOptionType)
)
}
: {};
await Promise.all([ await Promise.all([
fetchSummary({ fetchSummary({
...commonParams, ...commonParams,
@@ -308,7 +359,7 @@ const SummaryTab: React.FC = () => {
group_by: ['date'], group_by: ['date'],
granularity, granularity,
page: -1, page: -1,
filters: {} filters: tokenUserFilter
}), }),
fetchComputeBreakdown({ fetchComputeBreakdown({
@@ -418,6 +469,7 @@ const SummaryTab: React.FC = () => {
}; };
useEffect(() => { useEffect(() => {
fetchTokenMeta();
fetchAll(); fetchAll();
}, []); }, []);
@@ -1,14 +1,11 @@
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Tabs } from 'antd'; import { Tabs } from 'antd';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { UsageFilterItem } from '../../config/types'; import { BreakdownFilters } from '../../config/types';
import ApiKeysTable from '../tables/apikeys-table'; import ApiKeysTable from '../tables/apikeys-table';
import ModelsTable from '../tables/models-table'; import ModelsTable from '../tables/models-table';
import UsersTable from '../tables/users-table'; import UsersTable from '../tables/users-table';
type FilterOptionType = Omit<UsageFilterItem, 'label' | 'deleted'>;
const EMPTY_FILTERS: FilterOptionType[] = [];
const BreakdownTabs: React.FC<{ const BreakdownTabs: React.FC<{
dateRange: { dateRange: {
start_date: string; start_date: string;
@@ -17,16 +14,9 @@ const BreakdownTabs: React.FC<{
scope: string; scope: string;
pageResetKey?: number; pageResetKey?: number;
refreshKey?: number; refreshKey?: number;
filters: { filters: BreakdownFilters;
routes?: FilterOptionType[];
users?: FilterOptionType[];
api_keys?: FilterOptionType[];
};
}> = ({ filters, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => { }> = ({ filters, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => {
const intl = useIntl(); const intl = useIntl();
const routes = filters.routes || EMPTY_FILTERS;
const users = filters.users || EMPTY_FILTERS;
const apiKeys = filters.api_keys || EMPTY_FILTERS;
const items = useMemo(() => { const items = useMemo(() => {
return [ return [
@@ -37,7 +27,7 @@ const BreakdownTabs: React.FC<{
children: ( children: (
<ModelsTable <ModelsTable
key="models" key="models"
routes={routes} filters={filters}
dateRange={dateRange} dateRange={dateRange}
scope={scope} scope={scope}
pageResetKey={pageResetKey} pageResetKey={pageResetKey}
@@ -52,7 +42,7 @@ const BreakdownTabs: React.FC<{
children: ( children: (
<UsersTable <UsersTable
key="users" key="users"
users={users} filters={filters}
dateRange={dateRange} dateRange={dateRange}
scope={scope} scope={scope}
pageResetKey={pageResetKey} pageResetKey={pageResetKey}
@@ -67,7 +57,7 @@ const BreakdownTabs: React.FC<{
children: ( children: (
<ApiKeysTable <ApiKeysTable
key="api_keys" key="api_keys"
apiKeys={apiKeys} filters={filters}
dateRange={dateRange} dateRange={dateRange}
scope={scope} scope={scope}
pageResetKey={pageResetKey} pageResetKey={pageResetKey}
@@ -81,7 +71,7 @@ const BreakdownTabs: React.FC<{
} }
return true; return true;
}); });
}, [apiKeys, dateRange, routes, pageResetKey, refreshKey, scope, users]); }, [filters, dateRange, pageResetKey, refreshKey, scope]);
return ( return (
<div style={{ marginTop: 16 }}> <div style={{ marginTop: 16 }}>
@@ -3,18 +3,18 @@ import PageBox from '@/pages/_components/page-box';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Table } from 'antd'; import { Table } from 'antd';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { FilterOptionType } from '../../config/types'; import { BreakdownFilters } from '../../config/types';
import useAPIKeys from '../../hooks/use-apikeys-columns'; import useAPIKeys from '../../hooks/use-apikeys-columns';
import useQueryBreakdownList from '../../services/use-query-breakdown-list'; import useQueryBreakdownList from '../../services/use-query-breakdown-list';
import getBreakdownRowKey from '../../utils/get-breakdown-row-key'; import getBreakdownRowKey from '../../utils/get-breakdown-row-key';
const APIKeys: React.FC<{ const APIKeys: React.FC<{
apiKeys: FilterOptionType[]; filters: BreakdownFilters;
dateRange: { start_date: string; end_date: string }; dateRange: { start_date: string; end_date: string };
scope: string; scope: string;
pageResetKey?: number; pageResetKey?: number;
refreshKey?: number; refreshKey?: number;
}> = ({ apiKeys, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => { }> = ({ filters, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => {
const intl = useIntl(); const intl = useIntl();
const { loading, dataSource, fetchData } = useQueryBreakdownList({ const { loading, dataSource, fetchData } = useQueryBreakdownList({
@@ -69,16 +69,16 @@ const APIKeys: React.FC<{
fetchData({ fetchData({
...queryParams, ...queryParams,
group_by: ['api_key'], group_by: ['api_key'],
filters: { // Send the full filter set (route / user / api_key), not just the
api_keys: apiKeys // table's own dimension, so the breakdown matches the trend chart.
}, filters,
scope: scope, scope: scope,
...dateRange ...dateRange
}); });
}, [ }, [
apiKeys,
dateRange.end_date, dateRange.end_date,
dateRange.start_date, dateRange.start_date,
filters,
queryParams.page, queryParams.page,
queryParams.perPage, queryParams.perPage,
queryParams.sort_by, queryParams.sort_by,
@@ -4,18 +4,18 @@ import PageBox from '@/pages/_components/page-box';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Table } from 'antd'; import { Table } from 'antd';
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { BreakdownItem, FilterOptionType } from '../../config/types'; import { BreakdownFilters, BreakdownItem } from '../../config/types';
import useModelsColumns from '../../hooks/use-models-columns'; import useModelsColumns from '../../hooks/use-models-columns';
import useQueryBreakdownList from '../../services/use-query-breakdown-list'; import useQueryBreakdownList from '../../services/use-query-breakdown-list';
import getBreakdownRowKey from '../../utils/get-breakdown-row-key'; import getBreakdownRowKey from '../../utils/get-breakdown-row-key';
const Models: React.FC<{ const Models: React.FC<{
routes: FilterOptionType[]; filters: BreakdownFilters;
dateRange: { start_date: string; end_date: string }; dateRange: { start_date: string; end_date: string };
scope: string; scope: string;
pageResetKey?: number; pageResetKey?: number;
refreshKey?: number; refreshKey?: number;
}> = ({ routes, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => { }> = ({ filters, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => {
const intl = useIntl(); const intl = useIntl();
const { loading, dataSource, fetchData } = useQueryBreakdownList({ const { loading, dataSource, fetchData } = useQueryBreakdownList({
@@ -33,7 +33,6 @@ const Models: React.FC<{
const pendingPageResetRef = useRef(false); const pendingPageResetRef = useRef(false);
const handleTableChange = (pagination: any, filters: any, sorter: any) => { const handleTableChange = (pagination: any, filters: any, sorter: any) => {
console.log('pagination, filters, sorter: ', pagination, filters, sorter);
const sort_by = const sort_by =
sorter.order === 'descend' ? `-${sorter.field}` : sorter.field; sorter.order === 'descend' ? `-${sorter.field}` : sorter.field;
setQueryParams((prev) => ({ setQueryParams((prev) => ({
@@ -72,16 +71,16 @@ const Models: React.FC<{
fetchData({ fetchData({
...queryParams, ...queryParams,
group_by: ['route'], group_by: ['route'],
filters: { // Send the full filter set (route / user / api_key), not just the
routes // table's own dimension, so the breakdown matches the trend chart.
}, filters,
scope: scope, scope: scope,
...dateRange ...dateRange
}); });
}, [ }, [
dateRange.end_date, dateRange.end_date,
dateRange.start_date, dateRange.start_date,
routes, filters,
queryParams.page, queryParams.page,
queryParams.perPage, queryParams.perPage,
queryParams.sort_by, queryParams.sort_by,
@@ -3,18 +3,18 @@ import PageBox from '@/pages/_components/page-box';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Table } from 'antd'; import { Table } from 'antd';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { FilterOptionType } from '../../config/types'; import { BreakdownFilters } from '../../config/types';
import useUsersColumns from '../../hooks/use-users-columns'; import useUsersColumns from '../../hooks/use-users-columns';
import useQueryBreakdownList from '../../services/use-query-breakdown-list'; import useQueryBreakdownList from '../../services/use-query-breakdown-list';
import getBreakdownRowKey from '../../utils/get-breakdown-row-key'; import getBreakdownRowKey from '../../utils/get-breakdown-row-key';
const Users: React.FC<{ const Users: React.FC<{
users: FilterOptionType[]; filters: BreakdownFilters;
dateRange: { start_date: string; end_date: string }; dateRange: { start_date: string; end_date: string };
scope: string; scope: string;
pageResetKey?: number; pageResetKey?: number;
refreshKey?: number; refreshKey?: number;
}> = ({ users, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => { }> = ({ filters, dateRange, scope, pageResetKey = 0, refreshKey = 0 }) => {
const intl = useIntl(); const intl = useIntl();
const { loading, dataSource, fetchData } = useQueryBreakdownList({ const { loading, dataSource, fetchData } = useQueryBreakdownList({
@@ -71,9 +71,9 @@ const Users: React.FC<{
fetchData({ fetchData({
...queryParams, ...queryParams,
group_by: ['user'], group_by: ['user'],
filters: { // Send the full filter set (route / user / api_key), not just the
users // table's own dimension, so the breakdown matches the trend chart.
}, filters,
scope: scope, scope: scope,
...dateRange ...dateRange
}); });
@@ -81,12 +81,12 @@ const Users: React.FC<{
}, [ }, [
dateRange.end_date, dateRange.end_date,
dateRange.start_date, dateRange.start_date,
filters,
queryParams.page, queryParams.page,
queryParams.perPage, queryParams.perPage,
queryParams.sort_by, queryParams.sort_by,
refreshKey, refreshKey,
scope, scope
users
]); ]);
return ( return (