fix: display templates by manufacturer
This commit is contained in:
@@ -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'
|
||||
};
|
||||
|
||||
@@ -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.'
|
||||
};
|
||||
|
||||
@@ -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':
|
||||
'ローカルの一時ストレージ容量を入力してください'
|
||||
};
|
||||
|
||||
@@ -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': '一致するストレージが見つかりません。'
|
||||
};
|
||||
|
||||
@@ -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':
|
||||
'Введите объём локального временного хранилища'
|
||||
};
|
||||
|
||||
@@ -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) ==========
|
||||
|
||||
@@ -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'
|
||||
};
|
||||
|
||||
@@ -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) ==========
|
||||
|
||||
@@ -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': '请输入本地临时存储容量'
|
||||
};
|
||||
|
||||
@@ -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': '未找到匹配的存储'
|
||||
};
|
||||
|
||||
@@ -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<AddModalProps> = ({
|
||||
data,
|
||||
onCancel
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const form = useRef<any>(null);
|
||||
const [selectedInstanceType, setSelectedInstanceType] = useState<string>();
|
||||
const [selectedManufacturer, setSelectedManufacturer] = useState<string>();
|
||||
const [templateId, setTemplateId] = useState<number>();
|
||||
const [instanceKeyword, setInstanceKeyword] = useState('');
|
||||
const [templateKeyword, setTemplateKeyword] = useState('');
|
||||
@@ -95,17 +98,26 @@ const AddModal: React.FC<AddModalProps> = ({
|
||||
});
|
||||
}, [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<AddModalProps> = ({
|
||||
|
||||
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<AddModalProps> = ({
|
||||
<Input
|
||||
allowClear
|
||||
prefix={<SearchOutlined className="text-tertiary" />}
|
||||
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<AddModalProps> = ({
|
||||
<Input
|
||||
allowClear
|
||||
prefix={<SearchOutlined className="text-tertiary" />}
|
||||
placeholder="搜索模板名称、镜像或挂载路径"
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'gpuservice.instance.search.template.placeholder'
|
||||
})}
|
||||
value={templateKeyword}
|
||||
onChange={(e) => setTemplateKeyword(e.target.value)}
|
||||
/>
|
||||
|
||||
@@ -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<InstanceTypeListProps> = ({
|
||||
onChange,
|
||||
dataList = []
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const handleSelect = (item: InstanceTypeItem) => {
|
||||
if (!isAvailable(item)) return;
|
||||
onChange?.(item);
|
||||
@@ -104,7 +106,8 @@ const InstanceTypeList: React.FC<InstanceTypeListProps> = ({
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
库存 {item.status?.accelerator?.remaining ?? '-'}
|
||||
{intl.formatMessage({ id: 'gpuservice.instance.stock' })}{' '}
|
||||
{item.status?.accelerator?.remaining ?? '-'}
|
||||
</span>
|
||||
</Tag>
|
||||
)}
|
||||
@@ -114,12 +117,16 @@ const InstanceTypeList: React.FC<InstanceTypeListProps> = ({
|
||||
<span style={{ display: 'flex', height: 15 }}>
|
||||
{item.spec?.acceleratable && (
|
||||
<span className="meta-row">
|
||||
显存 {item.spec?.memory ?? '-'}
|
||||
{intl.formatMessage({ id: 'gpuservice.instance.memory' })}{' '}
|
||||
{item.spec?.memory ?? '-'}
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className="meta-row gap-16">
|
||||
<span>内存 {item.status?.ram?.capacity ?? '-'}</span>
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'gpuservice.instance.ram' })}{' '}
|
||||
{item.status?.ram?.capacity ?? '-'}
|
||||
</span>
|
||||
<span>vCPU {item.status?.cpu?.capacity ?? '-'}</span>
|
||||
</span>
|
||||
</TypeMeta>
|
||||
|
||||
@@ -46,13 +46,15 @@ export const status: Record<string, StatusType> = {
|
||||
|
||||
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
|
||||
|
||||
@@ -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<string, any>;
|
||||
}
|
||||
export type InstanceServicePortProtocol = 'TCP' | 'UDP';
|
||||
|
||||
export interface Metadata {
|
||||
name: string;
|
||||
namespace?: string;
|
||||
uid: string;
|
||||
resourceVersion: string;
|
||||
creationTimestamp: string;
|
||||
annotations?: Record<string, string>;
|
||||
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<FormData, 'metadata'> {
|
||||
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<string, string>;
|
||||
managedFields?: {
|
||||
manager: string;
|
||||
operation: string;
|
||||
apiVersion: string;
|
||||
time: string;
|
||||
fieldsType: string;
|
||||
fieldsV1: Record<string, any>;
|
||||
}[];
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<Form.Item<FormData>
|
||||
@@ -11,14 +13,22 @@ const Basic = () => {
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入实例名称'
|
||||
message: intl.formatMessage({
|
||||
id: 'gpuservice.instance.name.required'
|
||||
})
|
||||
}
|
||||
]}
|
||||
>
|
||||
<CInput.Input label="实例名称" required />
|
||||
<CInput.Input
|
||||
label={intl.formatMessage({ id: 'gpuservice.instance.name' })}
|
||||
required
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item<FormData> name={['spec', 'description']}>
|
||||
<CInput.TextArea label="描述" scaleSize />
|
||||
<CInput.TextArea
|
||||
label={intl.formatMessage({ id: 'common.table.description' })}
|
||||
scaleSize
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -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<InstanceFormProps> = forwardRef(
|
||||
namespace = 'default',
|
||||
onFinish
|
||||
} = props;
|
||||
const intl = useIntl();
|
||||
const [form] = Form.useForm<InstanceFormValues>();
|
||||
const scrollTabsRef = useRef<any>(null);
|
||||
const { detailData, fetchData } = useGetSshkey();
|
||||
@@ -98,9 +100,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
fetchData({}).then((res) => {
|
||||
form?.setFieldValue(['spec', 'sshPublicKey', 'name'], res.name);
|
||||
});
|
||||
fetchData({});
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
@@ -108,30 +108,36 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
||||
() => [
|
||||
{
|
||||
value: TABKeysMap.BASIC,
|
||||
label: '基础信息',
|
||||
label: intl.formatMessage({
|
||||
id: 'gpuservice.instance.section.basic'
|
||||
}),
|
||||
icon: <IconFont type="icon-basic" />,
|
||||
field: 'name'
|
||||
},
|
||||
{
|
||||
value: TABKeysMap.INSTANCE_TYPE,
|
||||
label: '实例类型',
|
||||
label: intl.formatMessage({ id: 'gpuservice.instance.section.type' }),
|
||||
icon: <IconFont type="icon-gpu1" />,
|
||||
field: 'instanceType'
|
||||
},
|
||||
{
|
||||
value: TABKeysMap.TEMPLATE,
|
||||
label: '实例模板',
|
||||
label: intl.formatMessage({
|
||||
id: 'gpuservice.instance.section.template'
|
||||
}),
|
||||
icon: <IconFont type="icon-model" />,
|
||||
field: 'template'
|
||||
},
|
||||
{
|
||||
value: TABKeysMap.STORAGE,
|
||||
label: '存储卷',
|
||||
label: intl.formatMessage({
|
||||
id: 'gpuservice.instance.section.storage'
|
||||
}),
|
||||
icon: <IconFont type="icon-storage-outlined" />,
|
||||
field: 'storage'
|
||||
}
|
||||
],
|
||||
[]
|
||||
[intl]
|
||||
);
|
||||
|
||||
const ports = (Form.useWatch(['spec', 'ports'], form) ||
|
||||
@@ -211,7 +217,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = 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<InstanceFormProps> = forwardRef(
|
||||
items={[
|
||||
{
|
||||
key: TABKeysMap.INSTANCE_TYPE,
|
||||
label: '实例类型',
|
||||
label: intl.formatMessage({
|
||||
id: 'gpuservice.instance.section.type'
|
||||
}),
|
||||
forceRender: true,
|
||||
children: <InstanceTypeFormItem />
|
||||
},
|
||||
{
|
||||
key: TABKeysMap.TEMPLATE,
|
||||
label: '实例模板',
|
||||
label: intl.formatMessage({
|
||||
id: 'gpuservice.instance.section.template'
|
||||
}),
|
||||
forceRender: true,
|
||||
children: <TemplateBasicForm page="instance" />
|
||||
},
|
||||
{
|
||||
key: TABKeysMap.STORAGE,
|
||||
label: '存储卷',
|
||||
label: intl.formatMessage({
|
||||
id: 'gpuservice.instance.section.storage'
|
||||
}),
|
||||
forceRender: true,
|
||||
children: <StorageVolume />
|
||||
}
|
||||
|
||||
@@ -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<InstanceTypePickerProps> = ({
|
||||
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<InstanceTypePickerProps> = ({
|
||||
color: 'var(--ant-color-text-tertiary)'
|
||||
}}
|
||||
>
|
||||
库存 {selected?.status?.accelerator?.remaining ?? '-'}
|
||||
{intl.formatMessage({ id: 'gpuservice.instance.stock' })}{' '}
|
||||
{selected?.status?.accelerator?.remaining ?? '-'}
|
||||
</Tag>
|
||||
)}
|
||||
</Flex>
|
||||
</SummaryTitle>
|
||||
<SummaryMeta>
|
||||
{selected?.spec?.acceleratable && (
|
||||
<span>显存 {selected?.spec?.memory ?? '-'}</span>
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'gpuservice.instance.memory' })}{' '}
|
||||
{selected?.spec?.memory ?? '-'}
|
||||
</span>
|
||||
)}
|
||||
<Flex gap={16}>
|
||||
<span>内存 {selected?.status?.ram?.capacity ?? '-'}</span>
|
||||
<span>
|
||||
{intl.formatMessage({ id: 'gpuservice.instance.ram' })}{' '}
|
||||
{selected?.status?.ram?.capacity ?? '-'}
|
||||
</span>
|
||||
<span>vCPU {selected?.status?.cpu?.capacity ?? '-'}</span>
|
||||
</Flex>
|
||||
</SummaryMeta>
|
||||
@@ -88,6 +97,7 @@ const InstanceTypePicker: React.FC<InstanceTypePickerProps> = ({
|
||||
};
|
||||
|
||||
const InstanceTypeFormItem = () => {
|
||||
const intl = useIntl();
|
||||
const form = Form.useFormInstance<FormData>();
|
||||
const typeName = Form.useWatch(['spec', 'type'], form) as string | undefined;
|
||||
|
||||
@@ -132,14 +142,16 @@ const InstanceTypeFormItem = () => {
|
||||
}, [form, typeName, maxGpuCount, dataList]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FieldBlock data-field="instanceType">
|
||||
<div data-field="instanceType">
|
||||
<FieldBlock>
|
||||
<Form.Item
|
||||
name={['spec', 'type']}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择实例类型'
|
||||
message: intl.formatMessage({
|
||||
id: 'gpuservice.instance.type.required'
|
||||
})
|
||||
}
|
||||
]}
|
||||
>
|
||||
@@ -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
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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<StorageOverlayProps> = ({
|
||||
onCancel,
|
||||
onSubmit
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const formRef = useRef<any>(null);
|
||||
|
||||
const handleSubmit = () => {
|
||||
@@ -43,7 +45,7 @@ const StorageOverlay: React.FC<StorageOverlayProps> = ({
|
||||
|
||||
return (
|
||||
<FormOverlayView
|
||||
title="添加存储"
|
||||
title={intl.formatMessage({ id: 'gpuservice.storage.add' })}
|
||||
open={open}
|
||||
width={600}
|
||||
onCancel={handleCancel}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useModel } from '@@/plugin-model';
|
||||
import { InputNumber as CInputNumber, Select } from '@gpustack/core-ui';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Button, Flex, Form, Radio } from 'antd';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
@@ -17,6 +18,7 @@ const FieldBlock = styled.div`
|
||||
const DEFAULT_TEMP_CAPACITY_GB = 50;
|
||||
|
||||
const StorageVolume = () => {
|
||||
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
|
||||
}
|
||||
]}
|
||||
/>
|
||||
<Button
|
||||
@@ -102,7 +112,7 @@ const StorageVolume = () => {
|
||||
style={{ marginBottom: 6 }}
|
||||
onClick={() => setOverlayOpen(true)}
|
||||
>
|
||||
添加存储
|
||||
{intl.formatMessage({ id: 'gpuservice.storage.add' })}
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
@@ -112,11 +122,19 @@ const StorageVolume = () => {
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请选择持久卷'
|
||||
message: intl.formatMessage({
|
||||
id: 'gpuservice.storage.persistentVolume.required'
|
||||
})
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Select label="持久卷" required options={storageOptions} />
|
||||
<Select
|
||||
label={intl.formatMessage({
|
||||
id: 'gpuservice.storage.persistentVolume'
|
||||
})}
|
||||
required
|
||||
options={storageOptions}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
@@ -132,11 +150,20 @@ const StorageVolume = () => {
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: '请输入本地临时存储容量'
|
||||
message: intl.formatMessage({
|
||||
id: 'gpuservice.storage.tempCapacity.required'
|
||||
})
|
||||
}
|
||||
]}
|
||||
>
|
||||
<CInputNumber min={1} precision={0} label="存储容量 (GB)" required />
|
||||
<CInputNumber
|
||||
min={1}
|
||||
precision={0}
|
||||
label={intl.formatMessage({
|
||||
id: 'gpuservice.storage.tempCapacity'
|
||||
})}
|
||||
required
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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<TemplateSelectorProps> = ({
|
||||
onChange,
|
||||
dataList = []
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
return (
|
||||
<TemplateGrid>
|
||||
{dataList.map((item: TemplateItem) => (
|
||||
@@ -71,7 +73,11 @@ const TemplateSelector: React.FC<TemplateSelectorProps> = ({
|
||||
</div>
|
||||
<div className="info">
|
||||
<span>
|
||||
<IconFont className="icon" type="icon-model" /> 镜像:
|
||||
<IconFont className="icon" type="icon-model" />{' '}
|
||||
{intl.formatMessage({
|
||||
id: 'gpuservice.instance.template.image'
|
||||
})}
|
||||
:
|
||||
</span>
|
||||
<AutoTooltip ghost minWidth={20}>
|
||||
<span className="value">{item.spec?.image || '-'}</span>
|
||||
@@ -79,7 +85,11 @@ const TemplateSelector: React.FC<TemplateSelectorProps> = ({
|
||||
</div>
|
||||
<div className="info">
|
||||
<span>
|
||||
<IconFont className="icon" type="icon-storage-outlined" /> 挂载:
|
||||
<IconFont className="icon" type="icon-storage-outlined" />{' '}
|
||||
{intl.formatMessage({
|
||||
id: 'gpuservice.instance.template.mount'
|
||||
})}
|
||||
:
|
||||
</span>
|
||||
<span className="value">{item.spec?.volumeMount || '-'}</span>
|
||||
</div>
|
||||
|
||||
@@ -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<ListItem> => {
|
||||
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;
|
||||
|
||||
@@ -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={<IconFont type="icon-instances-outlined" />}
|
||||
image={<IconFont type="icon-cloud-outlined" />}
|
||||
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}
|
||||
|
||||
@@ -7,23 +7,26 @@ export const StorageTypeValueMap = {
|
||||
};
|
||||
|
||||
export const StorageTypeLabelMap: Record<string, string> = {
|
||||
[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
|
||||
|
||||
@@ -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' })
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<CInput.Input label="名称" required />
|
||||
<CInput.Input
|
||||
label={intl.formatMessage({ id: 'common.table.name' })}
|
||||
required
|
||||
/>
|
||||
</Form.Item>
|
||||
<Flex gap={16}>
|
||||
<div style={{ flex: 1 }}>
|
||||
@@ -39,12 +47,15 @@ const Basic = ({ open }: { open: boolean }) => {
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: getRuleMessage('select', '存储类型')
|
||||
message: getRuleMessage(
|
||||
'select',
|
||||
intl.formatMessage({ id: 'gpuservice.storage.type' })
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<SealSelect
|
||||
label="存储类型"
|
||||
label={intl.formatMessage({ id: 'gpuservice.storage.type' })}
|
||||
required
|
||||
loading={loading}
|
||||
options={storageClassList}
|
||||
@@ -61,11 +72,17 @@ const Basic = ({ open }: { open: boolean }) => {
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: getRuleMessage('input', '容量')
|
||||
message: getRuleMessage(
|
||||
'input',
|
||||
intl.formatMessage({ id: 'gpuservice.storage.capacity' })
|
||||
)
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber label="容量" required />
|
||||
<InputNumber
|
||||
label={intl.formatMessage({ id: 'gpuservice.storage.capacity' })}
|
||||
required
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Flex>
|
||||
|
||||
@@ -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<ListItem> => {
|
||||
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;
|
||||
|
||||
@@ -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={<IconFont type="icon-storage-outlined" />}
|
||||
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}
|
||||
|
||||
@@ -34,6 +34,7 @@ const Basic: React.FC<BasicProps> = ({ page = 'template' }) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-field="template"></div>
|
||||
{page === 'template' && (
|
||||
<>
|
||||
<Form.Item<FormData>
|
||||
|
||||
Reference in New Issue
Block a user