diff --git a/src/locales/en-US/gpuservice.ts b/src/locales/en-US/gpuservice.ts index 9d71709f..50781bbd 100644 --- a/src/locales/en-US/gpuservice.ts +++ b/src/locales/en-US/gpuservice.ts @@ -31,5 +31,47 @@ export default { 'gpuservice.template.card.ports': 'Ports', 'gpuservice.publicKey.label': 'SSH Public Key', 'gpuservice.publicKey.placeholder': - 'Begin with ssh-rsa or ssh-ed25519. One Public Key per line.\n\nView Public Key:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub' + 'Begin with ssh-rsa or ssh-ed25519. One Public Key per line.\n\nView Public Key:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub', + 'gpuservice.instance': 'GPU Instance', + 'gpuservice.instance.add': 'Add GPU Instance', + 'gpuservice.instance.edit': 'Edit GPU Instance', + 'gpuservice.instance.filter.cluster': 'Filter by cluster', + 'gpuservice.instance.name': 'Instance Name', + 'gpuservice.instance.name.required': 'Please enter the instance name', + 'gpuservice.instance.section.basic': 'Basic Info', + 'gpuservice.instance.section.type': 'Instance Type', + 'gpuservice.instance.section.template': 'Instance Template', + 'gpuservice.instance.section.storage': 'Storage Volume', + 'gpuservice.instance.type.required': 'Please select an instance type', + 'gpuservice.instance.gpuCount': 'GPU Count', + 'gpuservice.instance.gpuCount.required': 'Please enter the GPU count', + 'gpuservice.instance.gpuCount.max': + 'The current instance type supports at most {count} GPU(s)', + 'gpuservice.instance.stock': 'Stock', + 'gpuservice.instance.memory': 'VRAM', + 'gpuservice.instance.ram': 'Memory', + 'gpuservice.instance.search.type.placeholder': + 'Search by name, VRAM, memory or vCPU', + 'gpuservice.instance.search.template.placeholder': + 'Search by template name, image or mount path', + 'gpuservice.instance.template.image': 'Image', + 'gpuservice.instance.template.mount': 'Mount', + 'gpuservice.storage': 'Storage', + 'gpuservice.storage.add': 'Add Storage', + 'gpuservice.storage.edit': 'Edit Storage', + 'gpuservice.storage.filter.cluster': 'Filter by cluster', + 'gpuservice.storage.type': 'Storage Type', + 'gpuservice.storage.type.local': 'Local Storage', + 'gpuservice.storage.type.shared': 'Shared Storage', + 'gpuservice.storage.type.object': 'Object Storage', + 'gpuservice.storage.capacity': 'Capacity', + 'gpuservice.storage.accessMode': 'Access Mode', + 'gpuservice.storage.persistent': 'Persistent Storage', + 'gpuservice.storage.temporary': 'Temporary Storage', + 'gpuservice.storage.persistentVolume': 'Persistent Volume', + 'gpuservice.storage.persistentVolume.required': + 'Please select a persistent volume', + 'gpuservice.storage.tempCapacity': 'Storage Capacity (GB)', + 'gpuservice.storage.tempCapacity.required': + 'Please enter the local temporary storage capacity' }; diff --git a/src/locales/en-US/no-result.ts b/src/locales/en-US/no-result.ts index ee2d155f..6e80a8f6 100644 --- a/src/locales/en-US/no-result.ts +++ b/src/locales/en-US/no-result.ts @@ -57,5 +57,13 @@ export default { 'noresult.gpuservice.template.subTitle': 'Once you create an instance template, it will appear here.', 'noresult.gpuservice.template.nofound': - 'No matching instance templates found.' + 'No matching instance templates found.', + 'noresult.gpuservice.instance.title': 'No GPU Instances', + 'noresult.gpuservice.instance.subTitle': + 'Once you create a GPU instance, it will appear here.', + 'noresult.gpuservice.instance.nofound': 'No matching GPU instances found.', + 'noresult.gpuservice.storage.title': 'No Storage', + 'noresult.gpuservice.storage.subTitle': + 'Once you create a storage, it will appear here.', + 'noresult.gpuservice.storage.nofound': 'No matching storage found.' }; diff --git a/src/locales/ja-JP/gpuservice.ts b/src/locales/ja-JP/gpuservice.ts index 8a9578da..a86d8a1f 100644 --- a/src/locales/ja-JP/gpuservice.ts +++ b/src/locales/ja-JP/gpuservice.ts @@ -29,5 +29,47 @@ export default { 'gpuservice.template.card.ports': 'ポート', 'gpuservice.publicKey.label': 'SSH 公開鍵', 'gpuservice.publicKey.placeholder': - 'ssh-rsa または ssh-ed25519 で始まり、各公開鍵は1行ずつ記述します\n\n公開鍵を確認:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub' + 'ssh-rsa または ssh-ed25519 で始まり、各公開鍵は1行ずつ記述します\n\n公開鍵を確認:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub', + 'gpuservice.instance': 'GPU インスタンス', + 'gpuservice.instance.add': 'GPU インスタンスを追加', + 'gpuservice.instance.edit': 'GPU インスタンスを編集', + 'gpuservice.instance.filter.cluster': 'クラスターでフィルター', + 'gpuservice.instance.name': 'インスタンス名', + 'gpuservice.instance.name.required': 'インスタンス名を入力してください', + 'gpuservice.instance.section.basic': '基本情報', + 'gpuservice.instance.section.type': 'インスタンスタイプ', + 'gpuservice.instance.section.template': 'インスタンステンプレート', + 'gpuservice.instance.section.storage': 'ストレージボリューム', + 'gpuservice.instance.type.required': 'インスタンスタイプを選択してください', + 'gpuservice.instance.gpuCount': 'GPU 数', + 'gpuservice.instance.gpuCount.required': 'GPU 数を入力してください', + 'gpuservice.instance.gpuCount.max': + '現在のインスタンスタイプは最大 {count} 個の GPU をサポートします', + 'gpuservice.instance.stock': '在庫', + 'gpuservice.instance.memory': 'VRAM', + 'gpuservice.instance.ram': 'メモリ', + 'gpuservice.instance.search.type.placeholder': + '名前、VRAM、メモリまたは vCPU で検索', + 'gpuservice.instance.search.template.placeholder': + 'テンプレート名、イメージまたはマウントパスで検索', + 'gpuservice.instance.template.image': 'イメージ', + 'gpuservice.instance.template.mount': 'マウント', + 'gpuservice.storage': 'ストレージ', + 'gpuservice.storage.add': 'ストレージを追加', + 'gpuservice.storage.edit': 'ストレージを編集', + 'gpuservice.storage.filter.cluster': 'クラスターでフィルター', + 'gpuservice.storage.type': 'ストレージタイプ', + 'gpuservice.storage.type.local': 'ローカルストレージ', + 'gpuservice.storage.type.shared': '共有ストレージ', + 'gpuservice.storage.type.object': 'オブジェクトストレージ', + 'gpuservice.storage.capacity': '容量', + 'gpuservice.storage.accessMode': 'アクセスモード', + 'gpuservice.storage.persistent': '永続ストレージ', + 'gpuservice.storage.temporary': '一時ストレージ', + 'gpuservice.storage.persistentVolume': '永続ボリューム', + 'gpuservice.storage.persistentVolume.required': + '永続ボリュームを選択してください', + 'gpuservice.storage.tempCapacity': 'ストレージ容量 (GB)', + 'gpuservice.storage.tempCapacity.required': + 'ローカルの一時ストレージ容量を入力してください' }; diff --git a/src/locales/ja-JP/no-result.ts b/src/locales/ja-JP/no-result.ts index a7b1bf32..8848ef97 100644 --- a/src/locales/ja-JP/no-result.ts +++ b/src/locales/ja-JP/no-result.ts @@ -57,5 +57,14 @@ export default { 'noresult.gpuservice.template.subTitle': 'インスタンステンプレートを作成すると、ここに表示されます。', 'noresult.gpuservice.template.nofound': - '一致するインスタンステンプレートが見つかりません。' + '一致するインスタンステンプレートが見つかりません。', + 'noresult.gpuservice.instance.title': 'GPU インスタンスなし', + 'noresult.gpuservice.instance.subTitle': + 'GPU インスタンスを作成すると、ここに表示されます。', + 'noresult.gpuservice.instance.nofound': + '一致する GPU インスタンスが見つかりません。', + 'noresult.gpuservice.storage.title': 'ストレージなし', + 'noresult.gpuservice.storage.subTitle': + 'ストレージを作成すると、ここに表示されます。', + 'noresult.gpuservice.storage.nofound': '一致するストレージが見つかりません。' }; diff --git a/src/locales/ru-RU/gpuservice.ts b/src/locales/ru-RU/gpuservice.ts index 22c03ba9..39a0bb6b 100644 --- a/src/locales/ru-RU/gpuservice.ts +++ b/src/locales/ru-RU/gpuservice.ts @@ -31,5 +31,46 @@ export default { 'gpuservice.template.card.ports': 'Порты', 'gpuservice.publicKey.label': 'Открытый ключ SSH', 'gpuservice.publicKey.placeholder': - 'Начинается с ssh-rsa или ssh-ed25519, по одному открытому ключу на строку\n\nПросмотр открытого ключа:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub' + 'Начинается с ssh-rsa или ssh-ed25519, по одному открытому ключу на строку\n\nПросмотр открытого ключа:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub', + 'gpuservice.instance': 'Экземпляр GPU', + 'gpuservice.instance.add': 'Добавить экземпляр GPU', + 'gpuservice.instance.edit': 'Редактировать экземпляр GPU', + 'gpuservice.instance.filter.cluster': 'Фильтр по кластеру', + 'gpuservice.instance.name': 'Имя экземпляра', + 'gpuservice.instance.name.required': 'Введите имя экземпляра', + 'gpuservice.instance.section.basic': 'Основная информация', + 'gpuservice.instance.section.type': 'Тип экземпляра', + 'gpuservice.instance.section.template': 'Шаблон экземпляра', + 'gpuservice.instance.section.storage': 'Том хранилища', + 'gpuservice.instance.type.required': 'Выберите тип экземпляра', + 'gpuservice.instance.gpuCount': 'Количество GPU', + 'gpuservice.instance.gpuCount.required': 'Введите количество GPU', + 'gpuservice.instance.gpuCount.max': + 'Текущий тип экземпляра поддерживает максимум {count} GPU', + 'gpuservice.instance.stock': 'Остаток', + 'gpuservice.instance.memory': 'VRAM', + 'gpuservice.instance.ram': 'Память', + 'gpuservice.instance.search.type.placeholder': + 'Поиск по имени, VRAM, памяти или vCPU', + 'gpuservice.instance.search.template.placeholder': + 'Поиск по имени шаблона, образу или пути монтирования', + 'gpuservice.instance.template.image': 'Образ', + 'gpuservice.instance.template.mount': 'Монтирование', + 'gpuservice.storage': 'Хранилище', + 'gpuservice.storage.add': 'Добавить хранилище', + 'gpuservice.storage.edit': 'Редактировать хранилище', + 'gpuservice.storage.filter.cluster': 'Фильтр по кластеру', + 'gpuservice.storage.type': 'Тип хранилища', + 'gpuservice.storage.type.local': 'Локальное хранилище', + 'gpuservice.storage.type.shared': 'Общее хранилище', + 'gpuservice.storage.type.object': 'Объектное хранилище', + 'gpuservice.storage.capacity': 'Объём', + 'gpuservice.storage.accessMode': 'Режим доступа', + 'gpuservice.storage.persistent': 'Постоянное хранилище', + 'gpuservice.storage.temporary': 'Временное хранилище', + 'gpuservice.storage.persistentVolume': 'Постоянный том', + 'gpuservice.storage.persistentVolume.required': 'Выберите постоянный том', + 'gpuservice.storage.tempCapacity': 'Объём хранилища (ГБ)', + 'gpuservice.storage.tempCapacity.required': + 'Введите объём локального временного хранилища' }; diff --git a/src/locales/ru-RU/no-result.ts b/src/locales/ru-RU/no-result.ts index 7b1a6b74..6d561742 100644 --- a/src/locales/ru-RU/no-result.ts +++ b/src/locales/ru-RU/no-result.ts @@ -58,7 +58,16 @@ export default { 'noresult.gpuservice.template.subTitle': 'После создания шаблона экземпляра он появится здесь.', 'noresult.gpuservice.template.nofound': - 'Подходящие шаблоны экземпляров не найдены.' + 'Подходящие шаблоны экземпляров не найдены.', + 'noresult.gpuservice.instance.title': 'Нет экземпляров GPU', + 'noresult.gpuservice.instance.subTitle': + 'После создания экземпляра GPU он появится здесь.', + 'noresult.gpuservice.instance.nofound': + 'Подходящие экземпляры GPU не найдены.', + 'noresult.gpuservice.storage.title': 'Нет хранилищ', + 'noresult.gpuservice.storage.subTitle': + 'После создания хранилища оно появится здесь.', + 'noresult.gpuservice.storage.nofound': 'Подходящие хранилища не найдены.' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== diff --git a/src/locales/tr-TR/gpuservice.ts b/src/locales/tr-TR/gpuservice.ts index 43000327..ba8cee88 100644 --- a/src/locales/tr-TR/gpuservice.ts +++ b/src/locales/tr-TR/gpuservice.ts @@ -30,5 +30,47 @@ export default { 'gpuservice.template.card.ports': 'Bağlantı Noktaları', 'gpuservice.publicKey.label': 'SSH Açık Anahtarı', 'gpuservice.publicKey.placeholder': - 'ssh-rsa veya ssh-ed25519 ile başlar, her açık anahtar ayrı bir satırda\n\nAçık anahtarı görüntüle:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub' + 'ssh-rsa veya ssh-ed25519 ile başlar, her açık anahtar ayrı bir satırda\n\nAçık anahtarı görüntüle:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub', + 'gpuservice.instance': 'GPU Örneği', + 'gpuservice.instance.add': 'GPU Örneği Ekle', + 'gpuservice.instance.edit': 'GPU Örneğini Düzenle', + 'gpuservice.instance.filter.cluster': 'Kümeye göre filtrele', + 'gpuservice.instance.name': 'Örnek Adı', + 'gpuservice.instance.name.required': 'Lütfen örnek adını girin', + 'gpuservice.instance.section.basic': 'Temel Bilgiler', + 'gpuservice.instance.section.type': 'Örnek Türü', + 'gpuservice.instance.section.template': 'Örnek Şablonu', + 'gpuservice.instance.section.storage': 'Depolama Hacmi', + 'gpuservice.instance.type.required': 'Lütfen bir örnek türü seçin', + 'gpuservice.instance.gpuCount': 'GPU Sayısı', + 'gpuservice.instance.gpuCount.required': 'Lütfen GPU sayısını girin', + 'gpuservice.instance.gpuCount.max': + 'Mevcut örnek türü en fazla {count} GPU destekler', + 'gpuservice.instance.stock': 'Stok', + 'gpuservice.instance.memory': 'VRAM', + 'gpuservice.instance.ram': 'Bellek', + 'gpuservice.instance.search.type.placeholder': + "Ada, VRAM, belleğe veya vCPU'ya göre ara", + 'gpuservice.instance.search.template.placeholder': + 'Şablon adına, imaja veya bağlama yoluna göre ara', + 'gpuservice.instance.template.image': 'İmaj', + 'gpuservice.instance.template.mount': 'Bağlama', + 'gpuservice.storage': 'Depolama', + 'gpuservice.storage.add': 'Depolama Ekle', + 'gpuservice.storage.edit': 'Depolamayı Düzenle', + 'gpuservice.storage.filter.cluster': 'Kümeye göre filtrele', + 'gpuservice.storage.type': 'Depolama Türü', + 'gpuservice.storage.type.local': 'Yerel Depolama', + 'gpuservice.storage.type.shared': 'Paylaşımlı Depolama', + 'gpuservice.storage.type.object': 'Nesne Depolama', + 'gpuservice.storage.capacity': 'Kapasite', + 'gpuservice.storage.accessMode': 'Erişim Modu', + 'gpuservice.storage.persistent': 'Kalıcı Depolama', + 'gpuservice.storage.temporary': 'Geçici Depolama', + 'gpuservice.storage.persistentVolume': 'Kalıcı Hacim', + 'gpuservice.storage.persistentVolume.required': + 'Lütfen bir kalıcı hacim seçin', + 'gpuservice.storage.tempCapacity': 'Depolama Kapasitesi (GB)', + 'gpuservice.storage.tempCapacity.required': + 'Lütfen yerel geçici depolama kapasitesini girin' }; diff --git a/src/locales/tr-TR/no-result.ts b/src/locales/tr-TR/no-result.ts index 317cf6ca..f8cb07fb 100644 --- a/src/locales/tr-TR/no-result.ts +++ b/src/locales/tr-TR/no-result.ts @@ -58,7 +58,15 @@ export default { 'noresult.gpuservice.template.title': 'Örnek Şablonu Yok', 'noresult.gpuservice.template.subTitle': 'Bir örnek şablonu oluşturduğunuzda burada görünecek.', - 'noresult.gpuservice.template.nofound': 'Eşleşen örnek şablonu bulunamadı.' + 'noresult.gpuservice.template.nofound': 'Eşleşen örnek şablonu bulunamadı.', + 'noresult.gpuservice.instance.title': 'GPU Örneği Yok', + 'noresult.gpuservice.instance.subTitle': + 'Bir GPU örneği oluşturduğunuzda burada görünecek.', + 'noresult.gpuservice.instance.nofound': 'Eşleşen GPU örneği bulunamadı.', + 'noresult.gpuservice.storage.title': 'Depolama Yok', + 'noresult.gpuservice.storage.subTitle': + 'Bir depolama oluşturduğunuzda burada görünecek.', + 'noresult.gpuservice.storage.nofound': 'Eşleşen depolama bulunamadı.' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== diff --git a/src/locales/zh-CN/gpuservice.ts b/src/locales/zh-CN/gpuservice.ts index be2d7c9c..9bf6c09b 100644 --- a/src/locales/zh-CN/gpuservice.ts +++ b/src/locales/zh-CN/gpuservice.ts @@ -29,5 +29,43 @@ export default { 'gpuservice.template.card.ports': '端口', 'gpuservice.publicKey.label': 'SSH 公钥', 'gpuservice.publicKey.placeholder': - '以 ssh-rsa 或 ssh-ed25519 开头,每个 Public Key 单独一行\n\n查看 Public Key:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub' + '以 ssh-rsa 或 ssh-ed25519 开头,每个 Public Key 单独一行\n\n查看 Public Key:\n- RSA\ncat ~/.ssh/id_rsa.pub\n- Ed25519\ncat ~/.ssh/id_ed25519.pub', + 'gpuservice.instance': 'GPU 实例', + 'gpuservice.instance.add': '添加 GPU 实例', + 'gpuservice.instance.edit': '编辑 GPU 实例', + 'gpuservice.instance.filter.cluster': '按集群过滤', + 'gpuservice.instance.name': '实例名称', + 'gpuservice.instance.name.required': '请输入实例名称', + 'gpuservice.instance.section.basic': '基础信息', + 'gpuservice.instance.section.type': '实例类型', + 'gpuservice.instance.section.template': '实例模板', + 'gpuservice.instance.section.storage': '存储卷', + 'gpuservice.instance.type.required': '请选择实例类型', + 'gpuservice.instance.gpuCount': 'GPU 数量', + 'gpuservice.instance.gpuCount.required': '请输入 GPU 数量', + 'gpuservice.instance.gpuCount.max': '当前实例类型最多支持 {count} 个 GPU', + 'gpuservice.instance.stock': '库存', + 'gpuservice.instance.memory': '显存', + 'gpuservice.instance.ram': '内存', + 'gpuservice.instance.search.type.placeholder': '搜索名称、显存、内存或 vCPU', + 'gpuservice.instance.search.template.placeholder': + '搜索模板名称、镜像或挂载路径', + 'gpuservice.instance.template.image': '镜像', + 'gpuservice.instance.template.mount': '挂载', + 'gpuservice.storage': '存储', + 'gpuservice.storage.add': '添加存储', + 'gpuservice.storage.edit': '编辑存储', + 'gpuservice.storage.filter.cluster': '按集群过滤', + 'gpuservice.storage.type': '存储类型', + 'gpuservice.storage.type.local': '本地存储', + 'gpuservice.storage.type.shared': '共享存储', + 'gpuservice.storage.type.object': '对象存储', + 'gpuservice.storage.capacity': '容量', + 'gpuservice.storage.accessMode': '访问模式', + 'gpuservice.storage.persistent': '持久存储', + 'gpuservice.storage.temporary': '临时存储', + 'gpuservice.storage.persistentVolume': '持久卷', + 'gpuservice.storage.persistentVolume.required': '请选择持久卷', + 'gpuservice.storage.tempCapacity': '存储容量 (GB)', + 'gpuservice.storage.tempCapacity.required': '请输入本地临时存储容量' }; diff --git a/src/locales/zh-CN/no-result.ts b/src/locales/zh-CN/no-result.ts index fa02f45b..271574f2 100644 --- a/src/locales/zh-CN/no-result.ts +++ b/src/locales/zh-CN/no-result.ts @@ -53,5 +53,11 @@ export default { 'noresult.instances.nofound': '未找到匹配的实例', 'noresult.gpuservice.template.title': '暂无实例模板', 'noresult.gpuservice.template.subTitle': '创建一个实例模板后会显示在这里', - 'noresult.gpuservice.template.nofound': '未找到匹配的实例模板' + 'noresult.gpuservice.template.nofound': '未找到匹配的实例模板', + 'noresult.gpuservice.instance.title': '暂无 GPU 实例', + 'noresult.gpuservice.instance.subTitle': '创建一个 GPU 实例后会显示在这里', + 'noresult.gpuservice.instance.nofound': '未找到匹配的 GPU 实例', + 'noresult.gpuservice.storage.title': '暂无存储', + 'noresult.gpuservice.storage.subTitle': '创建一个存储后会显示在这里', + 'noresult.gpuservice.storage.nofound': '未找到匹配的存储' }; diff --git a/src/pages/gpu-service/instances/components/add-modal.tsx b/src/pages/gpu-service/instances/components/add-modal.tsx index c8338030..88ae2655 100644 --- a/src/pages/gpu-service/instances/components/add-modal.tsx +++ b/src/pages/gpu-service/instances/components/add-modal.tsx @@ -2,6 +2,7 @@ import { PageActionType } from '@/config/types'; import Separator from '@/pages/llmodels/components/separator'; import { SearchOutlined } from '@ant-design/icons'; import { ColumnWrapper, GSDrawer, ModalFooter } from '@gpustack/core-ui'; +import { useIntl } from '@umijs/max'; import { Empty, Input, Typography } from 'antd'; import { useEffect, useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; @@ -58,8 +59,10 @@ const AddModal: React.FC = ({ data, onCancel }) => { + const intl = useIntl(); const form = useRef(null); const [selectedInstanceType, setSelectedInstanceType] = useState(); + const [selectedManufacturer, setSelectedManufacturer] = useState(); const [templateId, setTemplateId] = useState(); const [instanceKeyword, setInstanceKeyword] = useState(''); const [templateKeyword, setTemplateKeyword] = useState(''); @@ -95,17 +98,26 @@ const AddModal: React.FC = ({ }); }, [instanceKeyword, instanceTypeList]); + const manufacturerMatchedTemplates = useMemo(() => { + if (!selectedManufacturer) { + return templateList; + } + return templateList.filter( + (item) => item.manufacturer === selectedManufacturer + ); + }, [selectedManufacturer, templateList]); + const filteredTemplates = useMemo(() => { const keyword = templateKeyword.trim().toLowerCase(); if (!keyword) { - return templateList; + return manufacturerMatchedTemplates; } - return templateList.filter((item) => + return manufacturerMatchedTemplates.filter((item) => [item.name, item.spec?.image, item.spec?.volumeMount].some((text) => (text || '').toLowerCase().includes(keyword) ) ); - }, [templateKeyword, templateList]); + }, [templateKeyword, manufacturerMatchedTemplates]); const handleSubmit = () => { form.current?.submit(); @@ -124,13 +136,47 @@ const AddModal: React.FC = ({ const handleInstanceTypeChange = (item: InstanceTypeItem) => { const name = item.metadata?.name; + const manufacturer = item.spec?.manufacturer; setSelectedInstanceType(name); - form.current?.setFieldsValue({ - spec: { - type: name, - resources: { accelerator: '1' } - } - }); + setSelectedManufacturer(manufacturer); + + const currentSpec = form.current?.getFieldsValue()?.spec || {}; + const updatedSpec = { + ...currentSpec, + type: name, + resources: { ...(currentSpec.resources || {}), accelerator: '1' } + }; + + const currentTemplate = templateList.find((t) => t.id === templateId); + const isMatched = + !manufacturer || + (!!currentTemplate && currentTemplate.manufacturer === manufacturer); + + if (isMatched) { + form.current?.setFieldsValue({ spec: updatedSpec }); + return; + } + + const firstTemplate = templateList.find( + (t) => t.manufacturer === manufacturer + ); + + if (firstTemplate) { + setTemplateId(firstTemplate.id); + form.current?.setFieldsValue({ + manufacturer: firstTemplate.manufacturer, + spec: { + ...updatedSpec, + ...firstTemplate.spec + } + }); + } else { + setTemplateId(undefined); + form.current?.setFieldsValue({ + manufacturer: undefined, + spec: updatedSpec + }); + } }; const handleTemplateChange = (id: number, item: TemplateItem) => { @@ -176,7 +222,9 @@ const AddModal: React.FC = ({ } - placeholder="搜索名称、显存、内存或 vCPU" + placeholder={intl.formatMessage({ + id: 'gpuservice.instance.search.type.placeholder' + })} value={instanceKeyword} onChange={(e) => setInstanceKeyword(e.target.value)} /> @@ -208,7 +256,9 @@ const AddModal: React.FC = ({ } - placeholder="搜索模板名称、镜像或挂载路径" + placeholder={intl.formatMessage({ + id: 'gpuservice.instance.search.template.placeholder' + })} value={templateKeyword} onChange={(e) => setTemplateKeyword(e.target.value)} /> diff --git a/src/pages/gpu-service/instances/components/instance-type-list.tsx b/src/pages/gpu-service/instances/components/instance-type-list.tsx index 203d6c03..46c32ae3 100644 --- a/src/pages/gpu-service/instances/components/instance-type-list.tsx +++ b/src/pages/gpu-service/instances/components/instance-type-list.tsx @@ -1,4 +1,5 @@ import { AutoTooltip, StatusTag, TemplateCard } from '@gpustack/core-ui'; +import { useIntl } from '@umijs/max'; import { Flex, Tag } from 'antd'; import styled from 'styled-components'; import { @@ -55,6 +56,7 @@ const InstanceTypeList: React.FC = ({ onChange, dataList = [] }) => { + const intl = useIntl(); const handleSelect = (item: InstanceTypeItem) => { if (!isAvailable(item)) return; onChange?.(item); @@ -104,7 +106,8 @@ const InstanceTypeList: React.FC = ({ }} > - 库存 {item.status?.accelerator?.remaining ?? '-'} + {intl.formatMessage({ id: 'gpuservice.instance.stock' })}{' '} + {item.status?.accelerator?.remaining ?? '-'} )} @@ -114,12 +117,16 @@ const InstanceTypeList: React.FC = ({ {item.spec?.acceleratable && ( - 显存 {item.spec?.memory ?? '-'} + {intl.formatMessage({ id: 'gpuservice.instance.memory' })}{' '} + {item.spec?.memory ?? '-'} )} - 内存 {item.status?.ram?.capacity ?? '-'} + + {intl.formatMessage({ id: 'gpuservice.instance.ram' })}{' '} + {item.status?.ram?.capacity ?? '-'} + vCPU {item.status?.cpu?.capacity ?? '-'} diff --git a/src/pages/gpu-service/instances/config/index.ts b/src/pages/gpu-service/instances/config/index.ts index 4d3179e0..4d900662 100644 --- a/src/pages/gpu-service/instances/config/index.ts +++ b/src/pages/gpu-service/instances/config/index.ts @@ -46,13 +46,15 @@ export const status: Record = { export const rowActionList = [ { - label: '编辑', + label: 'common.button.edit', key: 'edit', + locale: true, icon: icons.EditOutlined }, { - label: '删除', + label: 'common.button.delete', key: 'delete', + locale: true, icon: icons.DeleteOutlined, props: { danger: true diff --git a/src/pages/gpu-service/instances/config/types.ts b/src/pages/gpu-service/instances/config/types.ts index c9fd4df4..506545a4 100644 --- a/src/pages/gpu-service/instances/config/types.ts +++ b/src/pages/gpu-service/instances/config/types.ts @@ -1,31 +1,5 @@ export type ImagePullPolicy = 'Always' | 'IfNotPresent' | 'Never'; -export interface InstanceEnvVar { - name: string; - value?: string; -} - -export interface InstancePort { - port: number; - protocol?: string; -} - -export interface InstanceResources { - cpu?: string; - ram?: string; - localStorage?: string; - accelerator?: string; -} - -export interface InstanceVolume { - ephemeral?: { - capacity?: string; - }; - persistent?: { - name: string; - }; -} - // instance form data export interface FormData { metadata: { @@ -38,63 +12,74 @@ export interface FormData { imagePullPolicy: ImagePullPolicy; displayName: string; command: string[]; - ports: InstancePort[]; - env: InstanceEnvVar[]; + ports: { + port: number; + protocol?: string; + }[]; + env: { + name: string; + value?: string; + }[]; volumeMount: string; - resources: InstanceResources; + resources: { + cpu?: string; + ram?: string; + localStorage?: string; + accelerator?: string; + }; description: string; - volume: InstanceVolume; + volume: { + ephemeral?: { + capacity?: string; + }; + persistent?: { + name: string; + }; + }; sshPublicKey: { name: string; }; }; } -export interface ManagedField { - manager: string; - operation: string; - apiVersion: string; - time: string; - fieldsType: string; - fieldsV1: Record; -} +export type InstanceServicePortProtocol = 'TCP' | 'UDP'; -export interface Metadata { - name: string; - namespace?: string; - uid: string; - resourceVersion: string; - creationTimestamp: string; - annotations?: Record; - managedFields?: ManagedField[]; +export interface InstanceStatus { + hostIPs?: { + ip: string; + }[]; + phase?: string; + phaseMessage?: string; + podIPs?: { + ip: string; + }[]; + ports?: { + port: number; + nodePort?: number; + protocol?: InstanceServicePortProtocol; + }[]; } // instance list item export interface ListItem extends Omit { id: number; - status?: string; - metadata: Metadata; -} - -export interface InstanceItem { - id: number; - name: string; - vram: number; // GiB - ram: number; // GiB - vCPU: number; // cores - gpu_count: number; - status: string; // available, unavailable -} - -export interface InstanceTypeSpec { - acceleratable: boolean; - computeCapability?: string; - family?: string; - group: string; - manufacturer?: string; - memory?: string; - product?: string; - sliced?: number; + status?: InstanceStatus; + metadata: { + name: string; + namespace?: string; + uid: string; + resourceVersion: string; + creationTimestamp: string; + annotations?: Record; + managedFields?: { + manager: string; + operation: string; + apiVersion: string; + time: string; + fieldsType: string; + fieldsV1: Record; + }[]; + }; } type Quality = `${number}Ki` | `${number}Gi`; @@ -105,15 +90,6 @@ export interface InstanceTypeResource { remaining?: Quality; } -export interface InstanceTypeStatus { - accelerator: InstanceTypeResource; - cpu: InstanceTypeResource; - localStorage: InstanceTypeResource; - phase: string; - phaseMessage?: string; - ram: InstanceTypeResource; -} - export interface InstanceTypeItem { apiVersion: string; kind: string; @@ -122,6 +98,22 @@ export interface InstanceTypeItem { namespace: string; }; id: number; - spec: InstanceTypeSpec; - status: InstanceTypeStatus; + spec: { + acceleratable: boolean; + computeCapability?: string; + family?: string; + group: string; + manufacturer?: string; + memory?: string; + product?: string; + sliced?: number; + }; + status: { + accelerator: InstanceTypeResource; + cpu: InstanceTypeResource; + localStorage: InstanceTypeResource; + phase: string; + phaseMessage?: string; + ram: InstanceTypeResource; + }; } diff --git a/src/pages/gpu-service/instances/forms/basic.tsx b/src/pages/gpu-service/instances/forms/basic.tsx index 2b913a91..0f8d580e 100644 --- a/src/pages/gpu-service/instances/forms/basic.tsx +++ b/src/pages/gpu-service/instances/forms/basic.tsx @@ -1,8 +1,10 @@ import { Input as CInput } from '@gpustack/core-ui'; +import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import { FormData } from '../config/types'; const Basic = () => { + const intl = useIntl(); return ( <> @@ -11,14 +13,22 @@ const Basic = () => { rules={[ { required: true, - message: '请输入实例名称' + message: intl.formatMessage({ + id: 'gpuservice.instance.name.required' + }) } ]} > - + name={['spec', 'description']}> - + ); diff --git a/src/pages/gpu-service/instances/forms/index.tsx b/src/pages/gpu-service/instances/forms/index.tsx index 74e9d52c..02725318 100644 --- a/src/pages/gpu-service/instances/forms/index.tsx +++ b/src/pages/gpu-service/instances/forms/index.tsx @@ -9,6 +9,7 @@ import { useScrollActiveChange, useWrapperContext } from '@gpustack/core-ui'; +import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import { forwardRef, @@ -77,6 +78,7 @@ const GPUServiceInstanceForm: React.FC = forwardRef( namespace = 'default', onFinish } = props; + const intl = useIntl(); const [form] = Form.useForm(); const scrollTabsRef = useRef(null); const { detailData, fetchData } = useGetSshkey(); @@ -98,9 +100,7 @@ const GPUServiceInstanceForm: React.FC = forwardRef( useEffect(() => { if (open) { - fetchData({}).then((res) => { - form?.setFieldValue(['spec', 'sshPublicKey', 'name'], res.name); - }); + fetchData({}); } }, [open]); @@ -108,30 +108,36 @@ const GPUServiceInstanceForm: React.FC = forwardRef( () => [ { value: TABKeysMap.BASIC, - label: '基础信息', + label: intl.formatMessage({ + id: 'gpuservice.instance.section.basic' + }), icon: , field: 'name' }, { value: TABKeysMap.INSTANCE_TYPE, - label: '实例类型', + label: intl.formatMessage({ id: 'gpuservice.instance.section.type' }), icon: , field: 'instanceType' }, { value: TABKeysMap.TEMPLATE, - label: '实例模板', + label: intl.formatMessage({ + id: 'gpuservice.instance.section.template' + }), icon: , field: 'template' }, { value: TABKeysMap.STORAGE, - label: '存储卷', + label: intl.formatMessage({ + id: 'gpuservice.instance.section.storage' + }), icon: , field: 'storage' } ], - [] + [intl] ); const ports = (Form.useWatch(['spec', 'ports'], form) || @@ -211,7 +217,7 @@ const GPUServiceInstanceForm: React.FC = forwardRef( const handleFinish = async (values: InstanceFormValues) => { const payload = buildPayload(values); - await onFinish(payload); + await onFinish(values); }; useImperativeHandle(ref, () => ({ @@ -275,19 +281,25 @@ const GPUServiceInstanceForm: React.FC = forwardRef( items={[ { key: TABKeysMap.INSTANCE_TYPE, - label: '实例类型', + label: intl.formatMessage({ + id: 'gpuservice.instance.section.type' + }), forceRender: true, children: }, { key: TABKeysMap.TEMPLATE, - label: '实例模板', + label: intl.formatMessage({ + id: 'gpuservice.instance.section.template' + }), forceRender: true, children: }, { key: TABKeysMap.STORAGE, - label: '存储卷', + label: intl.formatMessage({ + id: 'gpuservice.instance.section.storage' + }), forceRender: true, children: } diff --git a/src/pages/gpu-service/instances/forms/instance-type.tsx b/src/pages/gpu-service/instances/forms/instance-type.tsx index 43c6784d..8c57e8c7 100644 --- a/src/pages/gpu-service/instances/forms/instance-type.tsx +++ b/src/pages/gpu-service/instances/forms/instance-type.tsx @@ -1,4 +1,5 @@ import { AutoTooltip, InputNumber as CInputNumber } from '@gpustack/core-ui'; +import { useIntl } from '@umijs/max'; import { Flex, Form, Tag } from 'antd'; import { useEffect, useMemo } from 'react'; import styled from 'styled-components'; @@ -47,6 +48,7 @@ const InstanceTypePicker: React.FC = ({ value, dataList }) => { + const intl = useIntl(); const selected = useMemo(() => { return dataList.find((item) => (item.metadata?.name || '') === value); }, [value, dataList]); @@ -67,17 +69,24 @@ const InstanceTypePicker: React.FC = ({ color: 'var(--ant-color-text-tertiary)' }} > - 库存 {selected?.status?.accelerator?.remaining ?? '-'} + {intl.formatMessage({ id: 'gpuservice.instance.stock' })}{' '} + {selected?.status?.accelerator?.remaining ?? '-'} )} {selected?.spec?.acceleratable && ( - 显存 {selected?.spec?.memory ?? '-'} + + {intl.formatMessage({ id: 'gpuservice.instance.memory' })}{' '} + {selected?.spec?.memory ?? '-'} + )} - 内存 {selected?.status?.ram?.capacity ?? '-'} + + {intl.formatMessage({ id: 'gpuservice.instance.ram' })}{' '} + {selected?.status?.ram?.capacity ?? '-'} + vCPU {selected?.status?.cpu?.capacity ?? '-'} @@ -88,6 +97,7 @@ const InstanceTypePicker: React.FC = ({ }; const InstanceTypeFormItem = () => { + const intl = useIntl(); const form = Form.useFormInstance(); const typeName = Form.useWatch(['spec', 'type'], form) as string | undefined; @@ -132,14 +142,16 @@ const InstanceTypeFormItem = () => { }, [form, typeName, maxGpuCount, dataList]); return ( - <> - +
+ @@ -152,13 +164,20 @@ const InstanceTypeFormItem = () => { rules={[ { required: true, - message: '请输入 GPU 数量' + message: intl.formatMessage({ + id: 'gpuservice.instance.gpuCount.required' + }) }, { validator: (_, value) => { if (value > maxGpuCount) { return Promise.reject( - new Error(`当前实例类型最多支持 ${maxGpuCount} 个 GPU`) + new Error( + intl.formatMessage( + { id: 'gpuservice.instance.gpuCount.max' }, + { count: maxGpuCount } + ) + ) ); } return Promise.resolve(); @@ -170,12 +189,12 @@ const InstanceTypeFormItem = () => { min={1} max={maxGpuCount} precision={0} - label="GPU 数量" + label={intl.formatMessage({ id: 'gpuservice.instance.gpuCount' })} required /> )} - +
); }; diff --git a/src/pages/gpu-service/instances/forms/storage-overlay.tsx b/src/pages/gpu-service/instances/forms/storage-overlay.tsx index 35e70ded..6f07e6d4 100644 --- a/src/pages/gpu-service/instances/forms/storage-overlay.tsx +++ b/src/pages/gpu-service/instances/forms/storage-overlay.tsx @@ -1,6 +1,7 @@ import { PageAction } from '@/config'; import FormOverlayView from '@/pages/_components/form-overlay-view'; import { ModalFooter } from '@gpustack/core-ui'; +import { useIntl } from '@umijs/max'; import { useCallback, useRef } from 'react'; import { FormData as StorageFormData } from '../../storage/config/types'; import GPUServiceStorageForm from '../../storage/forms'; @@ -18,6 +19,7 @@ const StorageOverlay: React.FC = ({ onCancel, onSubmit }) => { + const intl = useIntl(); const formRef = useRef(null); const handleSubmit = () => { @@ -43,7 +45,7 @@ const StorageOverlay: React.FC = ({ return ( { + const intl = useIntl(); const { initialState } = useModel('@@initialState'); const { fetchData: createStorage } = useCreateStorage(); const { detailData: storageData, fetchData: fetchStorage } = @@ -92,8 +94,16 @@ const StorageVolume = () => { value={storageMode} onChange={(e) => handleModeChange(e.target.value)} options={[ - { label: '持久存储', value: StorageModeValueMap.Existing }, - { label: '临时存储', value: StorageModeValueMap.Temporary } + { + label: intl.formatMessage({ + id: 'gpuservice.storage.persistent' + }), + value: StorageModeValueMap.Existing + }, + { + label: intl.formatMessage({ id: 'gpuservice.storage.temporary' }), + value: StorageModeValueMap.Temporary + } ]} /> @@ -112,11 +122,19 @@ const StorageVolume = () => { rules={[ { required: true, - message: '请选择持久卷' + message: intl.formatMessage({ + id: 'gpuservice.storage.persistentVolume.required' + }) } ]} > - )} @@ -132,11 +150,20 @@ const StorageVolume = () => { rules={[ { required: true, - message: '请输入本地临时存储容量' + message: intl.formatMessage({ + id: 'gpuservice.storage.tempCapacity.required' + }) } ]} > - + )} diff --git a/src/pages/gpu-service/instances/forms/template-selector.tsx b/src/pages/gpu-service/instances/forms/template-selector.tsx index 28823f2b..6d3e5a1b 100644 --- a/src/pages/gpu-service/instances/forms/template-selector.tsx +++ b/src/pages/gpu-service/instances/forms/template-selector.tsx @@ -1,4 +1,5 @@ import { AutoTooltip, IconFont, TemplateCard } from '@gpustack/core-ui'; +import { useIntl } from '@umijs/max'; import styled from 'styled-components'; import { ListItem as TemplateItem } from '../../templates/config/types'; @@ -51,6 +52,7 @@ const TemplateSelector: React.FC = ({ onChange, dataList = [] }) => { + const intl = useIntl(); return ( {dataList.map((item: TemplateItem) => ( @@ -71,7 +73,11 @@ const TemplateSelector: React.FC = ({
- 镜像: + {' '} + {intl.formatMessage({ + id: 'gpuservice.instance.template.image' + })} + : {item.spec?.image || '-'} @@ -79,7 +85,11 @@ const TemplateSelector: React.FC = ({
- 挂载: + {' '} + {intl.formatMessage({ + id: 'gpuservice.instance.template.mount' + })} + : {item.spec?.volumeMount || '-'}
diff --git a/src/pages/gpu-service/instances/hooks/use-instances-columns.tsx b/src/pages/gpu-service/instances/hooks/use-instances-columns.tsx index 31393984..d07172c2 100644 --- a/src/pages/gpu-service/instances/hooks/use-instances-columns.tsx +++ b/src/pages/gpu-service/instances/hooks/use-instances-columns.tsx @@ -1,5 +1,6 @@ import { tableSorter } from '@/config/settings'; import { AutoTooltip, DropdownButtons, StatusTag } from '@gpustack/core-ui'; +import { useIntl } from '@umijs/max'; import type { ColumnsType } from 'antd/lib/table'; import dayjs from 'dayjs'; import { useMemo } from 'react'; @@ -15,10 +16,11 @@ const useInstancesColumns = ({ handleSelect, sortOrder }: ColumnsHookProps): ColumnsType => { + const intl = useIntl(); return useMemo(() => { return [ { - title: '名称', + title: intl.formatMessage({ id: 'common.table.name' }), dataIndex: ['metadata', 'name'], key: 'name', sorter: tableSorter(1), @@ -32,7 +34,7 @@ const useInstancesColumns = ({ ) }, { - title: '状态', + title: intl.formatMessage({ id: 'common.table.status' }), dataIndex: ['status', 'phase'], key: 'status', sorter: tableSorter(2), @@ -48,7 +50,7 @@ const useInstancesColumns = ({ } }, { - title: '镜像', + title: intl.formatMessage({ id: 'gpuservice.template.image' }), dataIndex: ['spec', 'image'], key: 'image', sorter: tableSorter(3), @@ -60,7 +62,7 @@ const useInstancesColumns = ({ ) }, { - title: '创建时间', + title: intl.formatMessage({ id: 'common.table.createTime' }), dataIndex: ['metadata', 'creationTimestamp'], key: 'creationTimestamp', sorter: tableSorter(5), @@ -74,7 +76,7 @@ const useInstancesColumns = ({ ) }, { - title: '操作', + title: intl.formatMessage({ id: 'common.table.operation' }), key: 'operation', dataIndex: 'operation', render: (_text, record) => ( @@ -85,7 +87,7 @@ const useInstancesColumns = ({ ) } ]; - }, [handleSelect, sortOrder]); + }, [handleSelect, sortOrder, intl]); }; export default useInstancesColumns; diff --git a/src/pages/gpu-service/instances/index.tsx b/src/pages/gpu-service/instances/index.tsx index 1b7b6c11..575e4c95 100644 --- a/src/pages/gpu-service/instances/index.tsx +++ b/src/pages/gpu-service/instances/index.tsx @@ -32,6 +32,7 @@ import useCreateInstance from './services/use-create-instance'; import useUpdateInstance from './services/use-update-instance'; const GPUService: React.FC = () => { + const intl = useIntl(); const { initialState } = useModel('@@initialState'); const namespace = initialState?.currentUser?.org_name || 'default'; const [currentCluster, setCurrentCluster] = useAtom(currentClusterAtom); @@ -97,7 +98,7 @@ const GPUService: React.FC = () => { deleteAPI: deleteInstance, watch: true, API: GPU_SERVICE_INSTANCES_API({ namespace, clusterID }), - contentForDelete: 'GPU 实例' + contentForDelete: intl.formatMessage({ id: 'gpuservice.instance' }) }); const { fetchData: createInstance } = useCreateInstance(); @@ -107,7 +108,6 @@ const GPUService: React.FC = () => { cancelRequest: cancelClusterRequest, clusterList } = useQueryClusterList(); - const intl = useIntl(); const k8sClusterList = useMemo( () => @@ -155,7 +155,7 @@ const GPUService: React.FC = () => { const handleAddInstance = () => { setOpenAddModalStatus({ action: PageAction.CREATE, - title: '添加 GPU 实例', + title: intl.formatMessage({ id: 'gpuservice.instance.add' }), open: true, currentData: null }); @@ -164,7 +164,7 @@ const GPUService: React.FC = () => { const handleEditInstance = (row: ListItem) => { setOpenAddModalStatus({ action: PageAction.EDIT, - title: '编辑 GPU 实例', + title: intl.formatMessage({ id: 'gpuservice.instance.edit' }), open: true, currentData: row }); @@ -192,9 +192,9 @@ const GPUService: React.FC = () => { fetchData(); closeModal(); - message.success('操作成功'); + message.success(intl.formatMessage({ id: 'common.message.success' })); } catch (error) { - message.error('操作失败'); + message.error(intl.formatMessage({ id: 'common.message.fail' })); } }; @@ -224,13 +224,19 @@ const GPUService: React.FC = () => { loading={dataSource.loading} loadend={dataSource.loadend} dataSource={dataSource.dataList} - image={} + image={} filters={_.omit(queryParams, ['sort_by'])} - noFoundText="未找到匹配的 GPU 实例" - title="暂无 GPU 实例" - subTitle="创建一个 GPU 实例后会显示在这里" + noFoundText={intl.formatMessage({ + id: 'noresult.gpuservice.instance.nofound' + })} + title={intl.formatMessage({ + id: 'noresult.gpuservice.instance.title' + })} + subTitle={intl.formatMessage({ + id: 'noresult.gpuservice.instance.subTitle' + })} onClick={handleAddInstance} - buttonText="立即添加" + buttonText={intl.formatMessage({ id: 'noresult.button.add' })} /> ); }; @@ -267,8 +273,10 @@ const GPUService: React.FC = () => { showSelect={false} selectOptions={k8sClusterList} select={{ showSearch: true }} - selectHolder="按集群过滤" - buttonText="添加 GPU 实例" + selectHolder={intl.formatMessage({ + id: 'gpuservice.instance.filter.cluster' + })} + buttonText={intl.formatMessage({ id: 'gpuservice.instance.add' })} handleSearch={handleSearch} handleSelectChange={handleClusterChange} handleDeleteByBatch={handleDeleteBatch} diff --git a/src/pages/gpu-service/storage/config/index.ts b/src/pages/gpu-service/storage/config/index.ts index 5082e732..86c89120 100644 --- a/src/pages/gpu-service/storage/config/index.ts +++ b/src/pages/gpu-service/storage/config/index.ts @@ -7,23 +7,26 @@ export const StorageTypeValueMap = { }; export const StorageTypeLabelMap: Record = { - [StorageTypeValueMap.Local]: '本地存储', - [StorageTypeValueMap.Shared]: '共享存储', - [StorageTypeValueMap.Object]: '对象存储' + [StorageTypeValueMap.Local]: 'gpuservice.storage.type.local', + [StorageTypeValueMap.Shared]: 'gpuservice.storage.type.shared', + [StorageTypeValueMap.Object]: 'gpuservice.storage.type.object' }; export const StorageTypeOptions = [ { label: StorageTypeLabelMap[StorageTypeValueMap.Local], - value: StorageTypeValueMap.Local + value: StorageTypeValueMap.Local, + locale: true }, { label: StorageTypeLabelMap[StorageTypeValueMap.Shared], - value: StorageTypeValueMap.Shared + value: StorageTypeValueMap.Shared, + locale: true }, { label: StorageTypeLabelMap[StorageTypeValueMap.Object], - value: StorageTypeValueMap.Object + value: StorageTypeValueMap.Object, + locale: true } ]; @@ -76,13 +79,15 @@ export const ReClaimPolicyOptions = [ export const rowActionList = [ { - label: '编辑', + label: 'common.button.edit', key: 'edit', + locale: true, icon: icons.EditOutlined }, { - label: '删除', + label: 'common.button.delete', key: 'delete', + locale: true, icon: icons.DeleteOutlined, props: { danger: true diff --git a/src/pages/gpu-service/storage/forms/basic.tsx b/src/pages/gpu-service/storage/forms/basic.tsx index c86ac40d..af8cf42f 100644 --- a/src/pages/gpu-service/storage/forms/basic.tsx +++ b/src/pages/gpu-service/storage/forms/basic.tsx @@ -4,12 +4,14 @@ import { Select as SealSelect, useAppUtils } from '@gpustack/core-ui'; +import { useIntl } from '@umijs/max'; import { Flex, Form } from 'antd'; import { useEffect } from 'react'; import { FormData } from '../config/types'; import useQueryStorageClass from '../services/use-query-storage-class'; const Basic = ({ open }: { open: boolean }) => { + const intl = useIntl(); const { getRuleMessage } = useAppUtils(); const { storageClassList, fetchData, loading } = useQueryStorageClass(); @@ -26,11 +28,17 @@ const Basic = ({ open }: { open: boolean }) => { rules={[ { required: true, - message: getRuleMessage('input', '名称') + message: getRuleMessage( + 'input', + intl.formatMessage({ id: 'common.table.name' }) + ) } ]} > - +
@@ -39,12 +47,15 @@ const Basic = ({ open }: { open: boolean }) => { rules={[ { required: true, - message: getRuleMessage('select', '存储类型') + message: getRuleMessage( + 'select', + intl.formatMessage({ id: 'gpuservice.storage.type' }) + ) } ]} > { rules={[ { required: true, - message: getRuleMessage('input', '容量') + message: getRuleMessage( + 'input', + intl.formatMessage({ id: 'gpuservice.storage.capacity' }) + ) } ]} > - +
diff --git a/src/pages/gpu-service/storage/hooks/use-storage-columns.tsx b/src/pages/gpu-service/storage/hooks/use-storage-columns.tsx index 53dd17d7..4707e90d 100644 --- a/src/pages/gpu-service/storage/hooks/use-storage-columns.tsx +++ b/src/pages/gpu-service/storage/hooks/use-storage-columns.tsx @@ -1,5 +1,6 @@ import { tableSorter } from '@/config/settings'; import { AutoTooltip, DropdownButtons } from '@gpustack/core-ui'; +import { useIntl } from '@umijs/max'; import type { ColumnsType } from 'antd/lib/table'; import dayjs from 'dayjs'; import { useMemo } from 'react'; @@ -15,10 +16,11 @@ const useStorageColumns = ({ handleSelect, sortOrder }: ColumnsHookProps): ColumnsType => { + const intl = useIntl(); return useMemo(() => { return [ { - title: '名称', + title: intl.formatMessage({ id: 'common.table.name' }), dataIndex: ['metadata', 'name'], key: 'name', sorter: tableSorter(1), @@ -32,28 +34,31 @@ const useStorageColumns = ({ ) }, { - title: '类型', + title: intl.formatMessage({ id: 'common.table.type' }), dataIndex: ['spec', 'type'], key: 'type', sorter: tableSorter(2), - render: (value: string) => StorageTypeLabelMap[value] || value || '-' + render: (value: string) => + StorageTypeLabelMap[value] + ? intl.formatMessage({ id: StorageTypeLabelMap[value] }) + : value || '-' }, { - title: '容量', + title: intl.formatMessage({ id: 'gpuservice.storage.capacity' }), dataIndex: ['spec', 'capacity'], key: 'capacity', sorter: tableSorter(3), render: (value: string) => value ?? '-' }, { - title: '访问模式', + title: intl.formatMessage({ id: 'gpuservice.storage.accessMode' }), dataIndex: ['spec', 'accessMode'], key: 'accessMode', sorter: tableSorter(4), render: (value: string) => value || '-' }, { - title: '创建时间', + title: intl.formatMessage({ id: 'common.table.createTime' }), dataIndex: ['metadata', 'creationTimestamp'], key: 'creationTimestamp', sorter: tableSorter(5), @@ -67,7 +72,7 @@ const useStorageColumns = ({ ) }, { - title: '操作', + title: intl.formatMessage({ id: 'common.table.operation' }), key: 'operation', dataIndex: 'operation', render: (_text, record) => ( @@ -78,7 +83,7 @@ const useStorageColumns = ({ ) } ]; - }, [handleSelect, sortOrder]); + }, [handleSelect, sortOrder, intl]); }; export default useStorageColumns; diff --git a/src/pages/gpu-service/storage/index.tsx b/src/pages/gpu-service/storage/index.tsx index ba2f7f00..9cb261b4 100644 --- a/src/pages/gpu-service/storage/index.tsx +++ b/src/pages/gpu-service/storage/index.tsx @@ -33,6 +33,7 @@ import useCreateStorage from './services/use-create-storage'; import useUpdateStorage from './services/use-update-storage'; const GPUServiceStorage: React.FC = () => { + const intl = useIntl(); const { initialState } = useModel('@@initialState'); const namespace = initialState?.currentUser?.org_name || 'default'; const [currentCluster, setCurrentCluster] = useAtom(currentClusterAtom); @@ -98,7 +99,7 @@ const GPUServiceStorage: React.FC = () => { deleteAPI: deleteStorage, watch: false, API: GPU_SERVICE_STORAGE_API({ namespace, clusterID }), - contentForDelete: '存储' + contentForDelete: intl.formatMessage({ id: 'gpuservice.storage' }) }); const { fetchData: createStorage } = useCreateStorage(); @@ -108,7 +109,6 @@ const GPUServiceStorage: React.FC = () => { cancelRequest: cancelClusterRequest, clusterList } = useQueryClusterList(); - const intl = useIntl(); const k8sClusterList = useMemo( () => @@ -156,7 +156,7 @@ const GPUServiceStorage: React.FC = () => { const handleAddStorage = () => { setOpenAddModalStatus({ action: PageAction.CREATE, - title: '添加存储', + title: intl.formatMessage({ id: 'gpuservice.storage.add' }), open: true, currentData: null }); @@ -165,7 +165,7 @@ const GPUServiceStorage: React.FC = () => { const handleEditStorage = (row: ListItem) => { setOpenAddModalStatus({ action: PageAction.EDIT, - title: '编辑存储', + title: intl.formatMessage({ id: 'gpuservice.storage.edit' }), open: true, currentData: row }); @@ -193,9 +193,9 @@ const GPUServiceStorage: React.FC = () => { fetchData(); closeModal(); - message.success('操作成功'); + message.success(intl.formatMessage({ id: 'common.message.success' })); } catch (error) { - message.error('操作失败'); + message.error(intl.formatMessage({ id: 'common.message.fail' })); } }; @@ -227,11 +227,15 @@ const GPUServiceStorage: React.FC = () => { dataSource={dataSource.dataList} image={} filters={_.omit(queryParams, ['sort_by'])} - noFoundText="未找到匹配的存储" - title="暂无存储" - subTitle="创建一个存储后会显示在这里" + noFoundText={intl.formatMessage({ + id: 'noresult.gpuservice.storage.nofound' + })} + title={intl.formatMessage({ id: 'noresult.gpuservice.storage.title' })} + subTitle={intl.formatMessage({ + id: 'noresult.gpuservice.storage.subTitle' + })} onClick={handleAddStorage} - buttonText="立即添加" + buttonText={intl.formatMessage({ id: 'noresult.button.add' })} /> ); }; @@ -269,8 +273,10 @@ const GPUServiceStorage: React.FC = () => { showSelect={false} selectOptions={k8sClusterList} select={{ showSearch: true }} - selectHolder="按集群过滤" - buttonText="添加存储" + selectHolder={intl.formatMessage({ + id: 'gpuservice.storage.filter.cluster' + })} + buttonText={intl.formatMessage({ id: 'gpuservice.storage.add' })} handleSearch={handleSearch} handleSelectChange={handleClusterChange} handleDeleteByBatch={handleDeleteBatch} diff --git a/src/pages/gpu-service/templates/forms/basic.tsx b/src/pages/gpu-service/templates/forms/basic.tsx index 3aac82fa..b5aa8a0f 100644 --- a/src/pages/gpu-service/templates/forms/basic.tsx +++ b/src/pages/gpu-service/templates/forms/basic.tsx @@ -34,6 +34,7 @@ const Basic: React.FC = ({ page = 'template' }) => { return ( <> +
{page === 'template' && ( <>