fix: display templates by manufacturer
This commit is contained in:
@@ -31,5 +31,47 @@ export default {
|
|||||||
'gpuservice.template.card.ports': 'Ports',
|
'gpuservice.template.card.ports': 'Ports',
|
||||||
'gpuservice.publicKey.label': 'SSH Public Key',
|
'gpuservice.publicKey.label': 'SSH Public Key',
|
||||||
'gpuservice.publicKey.placeholder':
|
'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':
|
'noresult.gpuservice.template.subTitle':
|
||||||
'Once you create an instance template, it will appear here.',
|
'Once you create an instance template, it will appear here.',
|
||||||
'noresult.gpuservice.template.nofound':
|
'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.template.card.ports': 'ポート',
|
||||||
'gpuservice.publicKey.label': 'SSH 公開鍵',
|
'gpuservice.publicKey.label': 'SSH 公開鍵',
|
||||||
'gpuservice.publicKey.placeholder':
|
'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.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': '一致するストレージが見つかりません。'
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -31,5 +31,46 @@ export default {
|
|||||||
'gpuservice.template.card.ports': 'Порты',
|
'gpuservice.template.card.ports': 'Порты',
|
||||||
'gpuservice.publicKey.label': 'Открытый ключ SSH',
|
'gpuservice.publicKey.label': 'Открытый ключ SSH',
|
||||||
'gpuservice.publicKey.placeholder':
|
'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.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': 'Подходящие хранилища не найдены.'
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
|||||||
@@ -30,5 +30,47 @@ export default {
|
|||||||
'gpuservice.template.card.ports': 'Bağlantı Noktaları',
|
'gpuservice.template.card.ports': 'Bağlantı Noktaları',
|
||||||
'gpuservice.publicKey.label': 'SSH Açık Anahtarı',
|
'gpuservice.publicKey.label': 'SSH Açık Anahtarı',
|
||||||
'gpuservice.publicKey.placeholder':
|
'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.title': 'Örnek Şablonu Yok',
|
||||||
'noresult.gpuservice.template.subTitle':
|
'noresult.gpuservice.template.subTitle':
|
||||||
'Bir örnek şablonu oluşturduğunuzda burada görünecek.',
|
'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) ==========
|
// ========== To-Do: Translate Keys (Remove After Translation) ==========
|
||||||
|
|||||||
@@ -29,5 +29,43 @@ export default {
|
|||||||
'gpuservice.template.card.ports': '端口',
|
'gpuservice.template.card.ports': '端口',
|
||||||
'gpuservice.publicKey.label': 'SSH 公钥',
|
'gpuservice.publicKey.label': 'SSH 公钥',
|
||||||
'gpuservice.publicKey.placeholder':
|
'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.instances.nofound': '未找到匹配的实例',
|
||||||
'noresult.gpuservice.template.title': '暂无实例模板',
|
'noresult.gpuservice.template.title': '暂无实例模板',
|
||||||
'noresult.gpuservice.template.subTitle': '创建一个实例模板后会显示在这里',
|
'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 Separator from '@/pages/llmodels/components/separator';
|
||||||
import { SearchOutlined } from '@ant-design/icons';
|
import { SearchOutlined } from '@ant-design/icons';
|
||||||
import { ColumnWrapper, GSDrawer, ModalFooter } from '@gpustack/core-ui';
|
import { ColumnWrapper, GSDrawer, ModalFooter } from '@gpustack/core-ui';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
import { Empty, Input, Typography } from 'antd';
|
import { Empty, Input, Typography } from 'antd';
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -58,8 +59,10 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
data,
|
data,
|
||||||
onCancel
|
onCancel
|
||||||
}) => {
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
const form = useRef<any>(null);
|
const form = useRef<any>(null);
|
||||||
const [selectedInstanceType, setSelectedInstanceType] = useState<string>();
|
const [selectedInstanceType, setSelectedInstanceType] = useState<string>();
|
||||||
|
const [selectedManufacturer, setSelectedManufacturer] = useState<string>();
|
||||||
const [templateId, setTemplateId] = useState<number>();
|
const [templateId, setTemplateId] = useState<number>();
|
||||||
const [instanceKeyword, setInstanceKeyword] = useState('');
|
const [instanceKeyword, setInstanceKeyword] = useState('');
|
||||||
const [templateKeyword, setTemplateKeyword] = useState('');
|
const [templateKeyword, setTemplateKeyword] = useState('');
|
||||||
@@ -95,17 +98,26 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
});
|
});
|
||||||
}, [instanceKeyword, instanceTypeList]);
|
}, [instanceKeyword, instanceTypeList]);
|
||||||
|
|
||||||
|
const manufacturerMatchedTemplates = useMemo(() => {
|
||||||
|
if (!selectedManufacturer) {
|
||||||
|
return templateList;
|
||||||
|
}
|
||||||
|
return templateList.filter(
|
||||||
|
(item) => item.manufacturer === selectedManufacturer
|
||||||
|
);
|
||||||
|
}, [selectedManufacturer, templateList]);
|
||||||
|
|
||||||
const filteredTemplates = useMemo(() => {
|
const filteredTemplates = useMemo(() => {
|
||||||
const keyword = templateKeyword.trim().toLowerCase();
|
const keyword = templateKeyword.trim().toLowerCase();
|
||||||
if (!keyword) {
|
if (!keyword) {
|
||||||
return templateList;
|
return manufacturerMatchedTemplates;
|
||||||
}
|
}
|
||||||
return templateList.filter((item) =>
|
return manufacturerMatchedTemplates.filter((item) =>
|
||||||
[item.name, item.spec?.image, item.spec?.volumeMount].some((text) =>
|
[item.name, item.spec?.image, item.spec?.volumeMount].some((text) =>
|
||||||
(text || '').toLowerCase().includes(keyword)
|
(text || '').toLowerCase().includes(keyword)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}, [templateKeyword, templateList]);
|
}, [templateKeyword, manufacturerMatchedTemplates]);
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
form.current?.submit();
|
form.current?.submit();
|
||||||
@@ -124,13 +136,47 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
|
|
||||||
const handleInstanceTypeChange = (item: InstanceTypeItem) => {
|
const handleInstanceTypeChange = (item: InstanceTypeItem) => {
|
||||||
const name = item.metadata?.name;
|
const name = item.metadata?.name;
|
||||||
|
const manufacturer = item.spec?.manufacturer;
|
||||||
setSelectedInstanceType(name);
|
setSelectedInstanceType(name);
|
||||||
form.current?.setFieldsValue({
|
setSelectedManufacturer(manufacturer);
|
||||||
spec: {
|
|
||||||
type: name,
|
const currentSpec = form.current?.getFieldsValue()?.spec || {};
|
||||||
resources: { accelerator: '1' }
|
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) => {
|
const handleTemplateChange = (id: number, item: TemplateItem) => {
|
||||||
@@ -176,7 +222,9 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
<Input
|
<Input
|
||||||
allowClear
|
allowClear
|
||||||
prefix={<SearchOutlined className="text-tertiary" />}
|
prefix={<SearchOutlined className="text-tertiary" />}
|
||||||
placeholder="搜索名称、显存、内存或 vCPU"
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'gpuservice.instance.search.type.placeholder'
|
||||||
|
})}
|
||||||
value={instanceKeyword}
|
value={instanceKeyword}
|
||||||
onChange={(e) => setInstanceKeyword(e.target.value)}
|
onChange={(e) => setInstanceKeyword(e.target.value)}
|
||||||
/>
|
/>
|
||||||
@@ -208,7 +256,9 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
<Input
|
<Input
|
||||||
allowClear
|
allowClear
|
||||||
prefix={<SearchOutlined className="text-tertiary" />}
|
prefix={<SearchOutlined className="text-tertiary" />}
|
||||||
placeholder="搜索模板名称、镜像或挂载路径"
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'gpuservice.instance.search.template.placeholder'
|
||||||
|
})}
|
||||||
value={templateKeyword}
|
value={templateKeyword}
|
||||||
onChange={(e) => setTemplateKeyword(e.target.value)}
|
onChange={(e) => setTemplateKeyword(e.target.value)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { AutoTooltip, StatusTag, TemplateCard } from '@gpustack/core-ui';
|
import { AutoTooltip, StatusTag, TemplateCard } from '@gpustack/core-ui';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
import { Flex, Tag } from 'antd';
|
import { Flex, Tag } from 'antd';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import {
|
import {
|
||||||
@@ -55,6 +56,7 @@ const InstanceTypeList: React.FC<InstanceTypeListProps> = ({
|
|||||||
onChange,
|
onChange,
|
||||||
dataList = []
|
dataList = []
|
||||||
}) => {
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
const handleSelect = (item: InstanceTypeItem) => {
|
const handleSelect = (item: InstanceTypeItem) => {
|
||||||
if (!isAvailable(item)) return;
|
if (!isAvailable(item)) return;
|
||||||
onChange?.(item);
|
onChange?.(item);
|
||||||
@@ -104,7 +106,8 @@ const InstanceTypeList: React.FC<InstanceTypeListProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span>
|
<span>
|
||||||
库存 {item.status?.accelerator?.remaining ?? '-'}
|
{intl.formatMessage({ id: 'gpuservice.instance.stock' })}{' '}
|
||||||
|
{item.status?.accelerator?.remaining ?? '-'}
|
||||||
</span>
|
</span>
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
@@ -114,12 +117,16 @@ const InstanceTypeList: React.FC<InstanceTypeListProps> = ({
|
|||||||
<span style={{ display: 'flex', height: 15 }}>
|
<span style={{ display: 'flex', height: 15 }}>
|
||||||
{item.spec?.acceleratable && (
|
{item.spec?.acceleratable && (
|
||||||
<span className="meta-row">
|
<span className="meta-row">
|
||||||
显存 {item.spec?.memory ?? '-'}
|
{intl.formatMessage({ id: 'gpuservice.instance.memory' })}{' '}
|
||||||
|
{item.spec?.memory ?? '-'}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
<span className="meta-row gap-16">
|
<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>vCPU {item.status?.cpu?.capacity ?? '-'}</span>
|
||||||
</span>
|
</span>
|
||||||
</TypeMeta>
|
</TypeMeta>
|
||||||
|
|||||||
@@ -46,13 +46,15 @@ export const status: Record<string, StatusType> = {
|
|||||||
|
|
||||||
export const rowActionList = [
|
export const rowActionList = [
|
||||||
{
|
{
|
||||||
label: '编辑',
|
label: 'common.button.edit',
|
||||||
key: 'edit',
|
key: 'edit',
|
||||||
|
locale: true,
|
||||||
icon: icons.EditOutlined
|
icon: icons.EditOutlined
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '删除',
|
label: 'common.button.delete',
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
|
locale: true,
|
||||||
icon: icons.DeleteOutlined,
|
icon: icons.DeleteOutlined,
|
||||||
props: {
|
props: {
|
||||||
danger: true
|
danger: true
|
||||||
|
|||||||
@@ -1,31 +1,5 @@
|
|||||||
export type ImagePullPolicy = 'Always' | 'IfNotPresent' | 'Never';
|
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
|
// instance form data
|
||||||
export interface FormData {
|
export interface FormData {
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -38,63 +12,74 @@ export interface FormData {
|
|||||||
imagePullPolicy: ImagePullPolicy;
|
imagePullPolicy: ImagePullPolicy;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
command: string[];
|
command: string[];
|
||||||
ports: InstancePort[];
|
ports: {
|
||||||
env: InstanceEnvVar[];
|
port: number;
|
||||||
|
protocol?: string;
|
||||||
|
}[];
|
||||||
|
env: {
|
||||||
|
name: string;
|
||||||
|
value?: string;
|
||||||
|
}[];
|
||||||
volumeMount: string;
|
volumeMount: string;
|
||||||
resources: InstanceResources;
|
resources: {
|
||||||
|
cpu?: string;
|
||||||
|
ram?: string;
|
||||||
|
localStorage?: string;
|
||||||
|
accelerator?: string;
|
||||||
|
};
|
||||||
description: string;
|
description: string;
|
||||||
volume: InstanceVolume;
|
volume: {
|
||||||
|
ephemeral?: {
|
||||||
|
capacity?: string;
|
||||||
|
};
|
||||||
|
persistent?: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
sshPublicKey: {
|
sshPublicKey: {
|
||||||
name: string;
|
name: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ManagedField {
|
export type InstanceServicePortProtocol = 'TCP' | 'UDP';
|
||||||
manager: string;
|
|
||||||
operation: string;
|
|
||||||
apiVersion: string;
|
|
||||||
time: string;
|
|
||||||
fieldsType: string;
|
|
||||||
fieldsV1: Record<string, any>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Metadata {
|
export interface InstanceStatus {
|
||||||
name: string;
|
hostIPs?: {
|
||||||
namespace?: string;
|
ip: string;
|
||||||
uid: string;
|
}[];
|
||||||
resourceVersion: string;
|
phase?: string;
|
||||||
creationTimestamp: string;
|
phaseMessage?: string;
|
||||||
annotations?: Record<string, string>;
|
podIPs?: {
|
||||||
managedFields?: ManagedField[];
|
ip: string;
|
||||||
|
}[];
|
||||||
|
ports?: {
|
||||||
|
port: number;
|
||||||
|
nodePort?: number;
|
||||||
|
protocol?: InstanceServicePortProtocol;
|
||||||
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// instance list item
|
// instance list item
|
||||||
export interface ListItem extends Omit<FormData, 'metadata'> {
|
export interface ListItem extends Omit<FormData, 'metadata'> {
|
||||||
id: number;
|
id: number;
|
||||||
status?: string;
|
status?: InstanceStatus;
|
||||||
metadata: Metadata;
|
metadata: {
|
||||||
}
|
name: string;
|
||||||
|
namespace?: string;
|
||||||
export interface InstanceItem {
|
uid: string;
|
||||||
id: number;
|
resourceVersion: string;
|
||||||
name: string;
|
creationTimestamp: string;
|
||||||
vram: number; // GiB
|
annotations?: Record<string, string>;
|
||||||
ram: number; // GiB
|
managedFields?: {
|
||||||
vCPU: number; // cores
|
manager: string;
|
||||||
gpu_count: number;
|
operation: string;
|
||||||
status: string; // available, unavailable
|
apiVersion: string;
|
||||||
}
|
time: string;
|
||||||
|
fieldsType: string;
|
||||||
export interface InstanceTypeSpec {
|
fieldsV1: Record<string, any>;
|
||||||
acceleratable: boolean;
|
}[];
|
||||||
computeCapability?: string;
|
};
|
||||||
family?: string;
|
|
||||||
group: string;
|
|
||||||
manufacturer?: string;
|
|
||||||
memory?: string;
|
|
||||||
product?: string;
|
|
||||||
sliced?: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Quality = `${number}Ki` | `${number}Gi`;
|
type Quality = `${number}Ki` | `${number}Gi`;
|
||||||
@@ -105,15 +90,6 @@ export interface InstanceTypeResource {
|
|||||||
remaining?: Quality;
|
remaining?: Quality;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InstanceTypeStatus {
|
|
||||||
accelerator: InstanceTypeResource;
|
|
||||||
cpu: InstanceTypeResource;
|
|
||||||
localStorage: InstanceTypeResource;
|
|
||||||
phase: string;
|
|
||||||
phaseMessage?: string;
|
|
||||||
ram: InstanceTypeResource;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface InstanceTypeItem {
|
export interface InstanceTypeItem {
|
||||||
apiVersion: string;
|
apiVersion: string;
|
||||||
kind: string;
|
kind: string;
|
||||||
@@ -122,6 +98,22 @@ export interface InstanceTypeItem {
|
|||||||
namespace: string;
|
namespace: string;
|
||||||
};
|
};
|
||||||
id: number;
|
id: number;
|
||||||
spec: InstanceTypeSpec;
|
spec: {
|
||||||
status: InstanceTypeStatus;
|
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 { Input as CInput } from '@gpustack/core-ui';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import { FormData } from '../config/types';
|
import { FormData } from '../config/types';
|
||||||
|
|
||||||
const Basic = () => {
|
const Basic = () => {
|
||||||
|
const intl = useIntl();
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
@@ -11,14 +13,22 @@ const Basic = () => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
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>
|
||||||
<Form.Item<FormData> name={['spec', 'description']}>
|
<Form.Item<FormData> name={['spec', 'description']}>
|
||||||
<CInput.TextArea label="描述" scaleSize />
|
<CInput.TextArea
|
||||||
|
label={intl.formatMessage({ id: 'common.table.description' })}
|
||||||
|
scaleSize
|
||||||
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
useScrollActiveChange,
|
useScrollActiveChange,
|
||||||
useWrapperContext
|
useWrapperContext
|
||||||
} from '@gpustack/core-ui';
|
} from '@gpustack/core-ui';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
import {
|
import {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
@@ -77,6 +78,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
namespace = 'default',
|
namespace = 'default',
|
||||||
onFinish
|
onFinish
|
||||||
} = props;
|
} = props;
|
||||||
|
const intl = useIntl();
|
||||||
const [form] = Form.useForm<InstanceFormValues>();
|
const [form] = Form.useForm<InstanceFormValues>();
|
||||||
const scrollTabsRef = useRef<any>(null);
|
const scrollTabsRef = useRef<any>(null);
|
||||||
const { detailData, fetchData } = useGetSshkey();
|
const { detailData, fetchData } = useGetSshkey();
|
||||||
@@ -98,9 +100,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
fetchData({}).then((res) => {
|
fetchData({});
|
||||||
form?.setFieldValue(['spec', 'sshPublicKey', 'name'], res.name);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
@@ -108,30 +108,36 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
value: TABKeysMap.BASIC,
|
value: TABKeysMap.BASIC,
|
||||||
label: '基础信息',
|
label: intl.formatMessage({
|
||||||
|
id: 'gpuservice.instance.section.basic'
|
||||||
|
}),
|
||||||
icon: <IconFont type="icon-basic" />,
|
icon: <IconFont type="icon-basic" />,
|
||||||
field: 'name'
|
field: 'name'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: TABKeysMap.INSTANCE_TYPE,
|
value: TABKeysMap.INSTANCE_TYPE,
|
||||||
label: '实例类型',
|
label: intl.formatMessage({ id: 'gpuservice.instance.section.type' }),
|
||||||
icon: <IconFont type="icon-gpu1" />,
|
icon: <IconFont type="icon-gpu1" />,
|
||||||
field: 'instanceType'
|
field: 'instanceType'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: TABKeysMap.TEMPLATE,
|
value: TABKeysMap.TEMPLATE,
|
||||||
label: '实例模板',
|
label: intl.formatMessage({
|
||||||
|
id: 'gpuservice.instance.section.template'
|
||||||
|
}),
|
||||||
icon: <IconFont type="icon-model" />,
|
icon: <IconFont type="icon-model" />,
|
||||||
field: 'template'
|
field: 'template'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: TABKeysMap.STORAGE,
|
value: TABKeysMap.STORAGE,
|
||||||
label: '存储卷',
|
label: intl.formatMessage({
|
||||||
|
id: 'gpuservice.instance.section.storage'
|
||||||
|
}),
|
||||||
icon: <IconFont type="icon-storage-outlined" />,
|
icon: <IconFont type="icon-storage-outlined" />,
|
||||||
field: 'storage'
|
field: 'storage'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
[]
|
[intl]
|
||||||
);
|
);
|
||||||
|
|
||||||
const ports = (Form.useWatch(['spec', 'ports'], form) ||
|
const ports = (Form.useWatch(['spec', 'ports'], form) ||
|
||||||
@@ -211,7 +217,7 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
|
|
||||||
const handleFinish = async (values: InstanceFormValues) => {
|
const handleFinish = async (values: InstanceFormValues) => {
|
||||||
const payload = buildPayload(values);
|
const payload = buildPayload(values);
|
||||||
await onFinish(payload);
|
await onFinish(values);
|
||||||
};
|
};
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
@@ -275,19 +281,25 @@ const GPUServiceInstanceForm: React.FC<InstanceFormProps> = forwardRef(
|
|||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
key: TABKeysMap.INSTANCE_TYPE,
|
key: TABKeysMap.INSTANCE_TYPE,
|
||||||
label: '实例类型',
|
label: intl.formatMessage({
|
||||||
|
id: 'gpuservice.instance.section.type'
|
||||||
|
}),
|
||||||
forceRender: true,
|
forceRender: true,
|
||||||
children: <InstanceTypeFormItem />
|
children: <InstanceTypeFormItem />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: TABKeysMap.TEMPLATE,
|
key: TABKeysMap.TEMPLATE,
|
||||||
label: '实例模板',
|
label: intl.formatMessage({
|
||||||
|
id: 'gpuservice.instance.section.template'
|
||||||
|
}),
|
||||||
forceRender: true,
|
forceRender: true,
|
||||||
children: <TemplateBasicForm page="instance" />
|
children: <TemplateBasicForm page="instance" />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: TABKeysMap.STORAGE,
|
key: TABKeysMap.STORAGE,
|
||||||
label: '存储卷',
|
label: intl.formatMessage({
|
||||||
|
id: 'gpuservice.instance.section.storage'
|
||||||
|
}),
|
||||||
forceRender: true,
|
forceRender: true,
|
||||||
children: <StorageVolume />
|
children: <StorageVolume />
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { AutoTooltip, InputNumber as CInputNumber } from '@gpustack/core-ui';
|
import { AutoTooltip, InputNumber as CInputNumber } from '@gpustack/core-ui';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
import { Flex, Form, Tag } from 'antd';
|
import { Flex, Form, Tag } from 'antd';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -47,6 +48,7 @@ const InstanceTypePicker: React.FC<InstanceTypePickerProps> = ({
|
|||||||
value,
|
value,
|
||||||
dataList
|
dataList
|
||||||
}) => {
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
const selected = useMemo(() => {
|
const selected = useMemo(() => {
|
||||||
return dataList.find((item) => (item.metadata?.name || '') === value);
|
return dataList.find((item) => (item.metadata?.name || '') === value);
|
||||||
}, [value, dataList]);
|
}, [value, dataList]);
|
||||||
@@ -67,17 +69,24 @@ const InstanceTypePicker: React.FC<InstanceTypePickerProps> = ({
|
|||||||
color: 'var(--ant-color-text-tertiary)'
|
color: 'var(--ant-color-text-tertiary)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
库存 {selected?.status?.accelerator?.remaining ?? '-'}
|
{intl.formatMessage({ id: 'gpuservice.instance.stock' })}{' '}
|
||||||
|
{selected?.status?.accelerator?.remaining ?? '-'}
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</SummaryTitle>
|
</SummaryTitle>
|
||||||
<SummaryMeta>
|
<SummaryMeta>
|
||||||
{selected?.spec?.acceleratable && (
|
{selected?.spec?.acceleratable && (
|
||||||
<span>显存 {selected?.spec?.memory ?? '-'}</span>
|
<span>
|
||||||
|
{intl.formatMessage({ id: 'gpuservice.instance.memory' })}{' '}
|
||||||
|
{selected?.spec?.memory ?? '-'}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
<Flex gap={16}>
|
<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>
|
<span>vCPU {selected?.status?.cpu?.capacity ?? '-'}</span>
|
||||||
</Flex>
|
</Flex>
|
||||||
</SummaryMeta>
|
</SummaryMeta>
|
||||||
@@ -88,6 +97,7 @@ const InstanceTypePicker: React.FC<InstanceTypePickerProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const InstanceTypeFormItem = () => {
|
const InstanceTypeFormItem = () => {
|
||||||
|
const intl = useIntl();
|
||||||
const form = Form.useFormInstance<FormData>();
|
const form = Form.useFormInstance<FormData>();
|
||||||
const typeName = Form.useWatch(['spec', 'type'], form) as string | undefined;
|
const typeName = Form.useWatch(['spec', 'type'], form) as string | undefined;
|
||||||
|
|
||||||
@@ -132,14 +142,16 @@ const InstanceTypeFormItem = () => {
|
|||||||
}, [form, typeName, maxGpuCount, dataList]);
|
}, [form, typeName, maxGpuCount, dataList]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div data-field="instanceType">
|
||||||
<FieldBlock data-field="instanceType">
|
<FieldBlock>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={['spec', 'type']}
|
name={['spec', 'type']}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: '请选择实例类型'
|
message: intl.formatMessage({
|
||||||
|
id: 'gpuservice.instance.type.required'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
@@ -152,13 +164,20 @@ const InstanceTypeFormItem = () => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: '请输入 GPU 数量'
|
message: intl.formatMessage({
|
||||||
|
id: 'gpuservice.instance.gpuCount.required'
|
||||||
|
})
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
validator: (_, value) => {
|
validator: (_, value) => {
|
||||||
if (value > maxGpuCount) {
|
if (value > maxGpuCount) {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
new Error(`当前实例类型最多支持 ${maxGpuCount} 个 GPU`)
|
new Error(
|
||||||
|
intl.formatMessage(
|
||||||
|
{ id: 'gpuservice.instance.gpuCount.max' },
|
||||||
|
{ count: maxGpuCount }
|
||||||
|
)
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
@@ -170,12 +189,12 @@ const InstanceTypeFormItem = () => {
|
|||||||
min={1}
|
min={1}
|
||||||
max={maxGpuCount}
|
max={maxGpuCount}
|
||||||
precision={0}
|
precision={0}
|
||||||
label="GPU 数量"
|
label={intl.formatMessage({ id: 'gpuservice.instance.gpuCount' })}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { PageAction } from '@/config';
|
import { PageAction } from '@/config';
|
||||||
import FormOverlayView from '@/pages/_components/form-overlay-view';
|
import FormOverlayView from '@/pages/_components/form-overlay-view';
|
||||||
import { ModalFooter } from '@gpustack/core-ui';
|
import { ModalFooter } from '@gpustack/core-ui';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
import { useCallback, useRef } from 'react';
|
import { useCallback, useRef } from 'react';
|
||||||
import { FormData as StorageFormData } from '../../storage/config/types';
|
import { FormData as StorageFormData } from '../../storage/config/types';
|
||||||
import GPUServiceStorageForm from '../../storage/forms';
|
import GPUServiceStorageForm from '../../storage/forms';
|
||||||
@@ -18,6 +19,7 @@ const StorageOverlay: React.FC<StorageOverlayProps> = ({
|
|||||||
onCancel,
|
onCancel,
|
||||||
onSubmit
|
onSubmit
|
||||||
}) => {
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
const formRef = useRef<any>(null);
|
const formRef = useRef<any>(null);
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
@@ -43,7 +45,7 @@ const StorageOverlay: React.FC<StorageOverlayProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<FormOverlayView
|
<FormOverlayView
|
||||||
title="添加存储"
|
title={intl.formatMessage({ id: 'gpuservice.storage.add' })}
|
||||||
open={open}
|
open={open}
|
||||||
width={600}
|
width={600}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useModel } from '@@/plugin-model';
|
import { useModel } from '@@/plugin-model';
|
||||||
import { InputNumber as CInputNumber, Select } from '@gpustack/core-ui';
|
import { InputNumber as CInputNumber, Select } from '@gpustack/core-ui';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Flex, Form, Radio } from 'antd';
|
import { Button, Flex, Form, Radio } from 'antd';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
@@ -17,6 +18,7 @@ const FieldBlock = styled.div`
|
|||||||
const DEFAULT_TEMP_CAPACITY_GB = 50;
|
const DEFAULT_TEMP_CAPACITY_GB = 50;
|
||||||
|
|
||||||
const StorageVolume = () => {
|
const StorageVolume = () => {
|
||||||
|
const intl = useIntl();
|
||||||
const { initialState } = useModel('@@initialState');
|
const { initialState } = useModel('@@initialState');
|
||||||
const { fetchData: createStorage } = useCreateStorage();
|
const { fetchData: createStorage } = useCreateStorage();
|
||||||
const { detailData: storageData, fetchData: fetchStorage } =
|
const { detailData: storageData, fetchData: fetchStorage } =
|
||||||
@@ -92,8 +94,16 @@ const StorageVolume = () => {
|
|||||||
value={storageMode}
|
value={storageMode}
|
||||||
onChange={(e) => handleModeChange(e.target.value)}
|
onChange={(e) => handleModeChange(e.target.value)}
|
||||||
options={[
|
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
|
<Button
|
||||||
@@ -102,7 +112,7 @@ const StorageVolume = () => {
|
|||||||
style={{ marginBottom: 6 }}
|
style={{ marginBottom: 6 }}
|
||||||
onClick={() => setOverlayOpen(true)}
|
onClick={() => setOverlayOpen(true)}
|
||||||
>
|
>
|
||||||
添加存储
|
{intl.formatMessage({ id: 'gpuservice.storage.add' })}
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
@@ -112,11 +122,19 @@ const StorageVolume = () => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
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>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -132,11 +150,20 @@ const StorageVolume = () => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
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>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { AutoTooltip, IconFont, TemplateCard } from '@gpustack/core-ui';
|
import { AutoTooltip, IconFont, TemplateCard } from '@gpustack/core-ui';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { ListItem as TemplateItem } from '../../templates/config/types';
|
import { ListItem as TemplateItem } from '../../templates/config/types';
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@ const TemplateSelector: React.FC<TemplateSelectorProps> = ({
|
|||||||
onChange,
|
onChange,
|
||||||
dataList = []
|
dataList = []
|
||||||
}) => {
|
}) => {
|
||||||
|
const intl = useIntl();
|
||||||
return (
|
return (
|
||||||
<TemplateGrid>
|
<TemplateGrid>
|
||||||
{dataList.map((item: TemplateItem) => (
|
{dataList.map((item: TemplateItem) => (
|
||||||
@@ -71,7 +73,11 @@ const TemplateSelector: React.FC<TemplateSelectorProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="info">
|
<div className="info">
|
||||||
<span>
|
<span>
|
||||||
<IconFont className="icon" type="icon-model" /> 镜像:
|
<IconFont className="icon" type="icon-model" />{' '}
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: 'gpuservice.instance.template.image'
|
||||||
|
})}
|
||||||
|
:
|
||||||
</span>
|
</span>
|
||||||
<AutoTooltip ghost minWidth={20}>
|
<AutoTooltip ghost minWidth={20}>
|
||||||
<span className="value">{item.spec?.image || '-'}</span>
|
<span className="value">{item.spec?.image || '-'}</span>
|
||||||
@@ -79,7 +85,11 @@ const TemplateSelector: React.FC<TemplateSelectorProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className="info">
|
<div className="info">
|
||||||
<span>
|
<span>
|
||||||
<IconFont className="icon" type="icon-storage-outlined" /> 挂载:
|
<IconFont className="icon" type="icon-storage-outlined" />{' '}
|
||||||
|
{intl.formatMessage({
|
||||||
|
id: 'gpuservice.instance.template.mount'
|
||||||
|
})}
|
||||||
|
:
|
||||||
</span>
|
</span>
|
||||||
<span className="value">{item.spec?.volumeMount || '-'}</span>
|
<span className="value">{item.spec?.volumeMount || '-'}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { tableSorter } from '@/config/settings';
|
import { tableSorter } from '@/config/settings';
|
||||||
import { AutoTooltip, DropdownButtons, StatusTag } from '@gpustack/core-ui';
|
import { AutoTooltip, DropdownButtons, StatusTag } from '@gpustack/core-ui';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
import type { ColumnsType } from 'antd/lib/table';
|
import type { ColumnsType } from 'antd/lib/table';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@@ -15,10 +16,11 @@ const useInstancesColumns = ({
|
|||||||
handleSelect,
|
handleSelect,
|
||||||
sortOrder
|
sortOrder
|
||||||
}: ColumnsHookProps): ColumnsType<ListItem> => {
|
}: ColumnsHookProps): ColumnsType<ListItem> => {
|
||||||
|
const intl = useIntl();
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: intl.formatMessage({ id: 'common.table.name' }),
|
||||||
dataIndex: ['metadata', 'name'],
|
dataIndex: ['metadata', 'name'],
|
||||||
key: 'name',
|
key: 'name',
|
||||||
sorter: tableSorter(1),
|
sorter: tableSorter(1),
|
||||||
@@ -32,7 +34,7 @@ const useInstancesColumns = ({
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: intl.formatMessage({ id: 'common.table.status' }),
|
||||||
dataIndex: ['status', 'phase'],
|
dataIndex: ['status', 'phase'],
|
||||||
key: 'status',
|
key: 'status',
|
||||||
sorter: tableSorter(2),
|
sorter: tableSorter(2),
|
||||||
@@ -48,7 +50,7 @@ const useInstancesColumns = ({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '镜像',
|
title: intl.formatMessage({ id: 'gpuservice.template.image' }),
|
||||||
dataIndex: ['spec', 'image'],
|
dataIndex: ['spec', 'image'],
|
||||||
key: 'image',
|
key: 'image',
|
||||||
sorter: tableSorter(3),
|
sorter: tableSorter(3),
|
||||||
@@ -60,7 +62,7 @@ const useInstancesColumns = ({
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '创建时间',
|
title: intl.formatMessage({ id: 'common.table.createTime' }),
|
||||||
dataIndex: ['metadata', 'creationTimestamp'],
|
dataIndex: ['metadata', 'creationTimestamp'],
|
||||||
key: 'creationTimestamp',
|
key: 'creationTimestamp',
|
||||||
sorter: tableSorter(5),
|
sorter: tableSorter(5),
|
||||||
@@ -74,7 +76,7 @@ const useInstancesColumns = ({
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: intl.formatMessage({ id: 'common.table.operation' }),
|
||||||
key: 'operation',
|
key: 'operation',
|
||||||
dataIndex: 'operation',
|
dataIndex: 'operation',
|
||||||
render: (_text, record) => (
|
render: (_text, record) => (
|
||||||
@@ -85,7 +87,7 @@ const useInstancesColumns = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}, [handleSelect, sortOrder]);
|
}, [handleSelect, sortOrder, intl]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useInstancesColumns;
|
export default useInstancesColumns;
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import useCreateInstance from './services/use-create-instance';
|
|||||||
import useUpdateInstance from './services/use-update-instance';
|
import useUpdateInstance from './services/use-update-instance';
|
||||||
|
|
||||||
const GPUService: React.FC = () => {
|
const GPUService: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
const { initialState } = useModel('@@initialState');
|
const { initialState } = useModel('@@initialState');
|
||||||
const namespace = initialState?.currentUser?.org_name || 'default';
|
const namespace = initialState?.currentUser?.org_name || 'default';
|
||||||
const [currentCluster, setCurrentCluster] = useAtom(currentClusterAtom);
|
const [currentCluster, setCurrentCluster] = useAtom(currentClusterAtom);
|
||||||
@@ -97,7 +98,7 @@ const GPUService: React.FC = () => {
|
|||||||
deleteAPI: deleteInstance,
|
deleteAPI: deleteInstance,
|
||||||
watch: true,
|
watch: true,
|
||||||
API: GPU_SERVICE_INSTANCES_API({ namespace, clusterID }),
|
API: GPU_SERVICE_INSTANCES_API({ namespace, clusterID }),
|
||||||
contentForDelete: 'GPU 实例'
|
contentForDelete: intl.formatMessage({ id: 'gpuservice.instance' })
|
||||||
});
|
});
|
||||||
|
|
||||||
const { fetchData: createInstance } = useCreateInstance();
|
const { fetchData: createInstance } = useCreateInstance();
|
||||||
@@ -107,7 +108,6 @@ const GPUService: React.FC = () => {
|
|||||||
cancelRequest: cancelClusterRequest,
|
cancelRequest: cancelClusterRequest,
|
||||||
clusterList
|
clusterList
|
||||||
} = useQueryClusterList();
|
} = useQueryClusterList();
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const k8sClusterList = useMemo(
|
const k8sClusterList = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -155,7 +155,7 @@ const GPUService: React.FC = () => {
|
|||||||
const handleAddInstance = () => {
|
const handleAddInstance = () => {
|
||||||
setOpenAddModalStatus({
|
setOpenAddModalStatus({
|
||||||
action: PageAction.CREATE,
|
action: PageAction.CREATE,
|
||||||
title: '添加 GPU 实例',
|
title: intl.formatMessage({ id: 'gpuservice.instance.add' }),
|
||||||
open: true,
|
open: true,
|
||||||
currentData: null
|
currentData: null
|
||||||
});
|
});
|
||||||
@@ -164,7 +164,7 @@ const GPUService: React.FC = () => {
|
|||||||
const handleEditInstance = (row: ListItem) => {
|
const handleEditInstance = (row: ListItem) => {
|
||||||
setOpenAddModalStatus({
|
setOpenAddModalStatus({
|
||||||
action: PageAction.EDIT,
|
action: PageAction.EDIT,
|
||||||
title: '编辑 GPU 实例',
|
title: intl.formatMessage({ id: 'gpuservice.instance.edit' }),
|
||||||
open: true,
|
open: true,
|
||||||
currentData: row
|
currentData: row
|
||||||
});
|
});
|
||||||
@@ -192,9 +192,9 @@ const GPUService: React.FC = () => {
|
|||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
closeModal();
|
closeModal();
|
||||||
message.success('操作成功');
|
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('操作失败');
|
message.error(intl.formatMessage({ id: 'common.message.fail' }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -224,13 +224,19 @@ const GPUService: React.FC = () => {
|
|||||||
loading={dataSource.loading}
|
loading={dataSource.loading}
|
||||||
loadend={dataSource.loadend}
|
loadend={dataSource.loadend}
|
||||||
dataSource={dataSource.dataList}
|
dataSource={dataSource.dataList}
|
||||||
image={<IconFont type="icon-instances-outlined" />}
|
image={<IconFont type="icon-cloud-outlined" />}
|
||||||
filters={_.omit(queryParams, ['sort_by'])}
|
filters={_.omit(queryParams, ['sort_by'])}
|
||||||
noFoundText="未找到匹配的 GPU 实例"
|
noFoundText={intl.formatMessage({
|
||||||
title="暂无 GPU 实例"
|
id: 'noresult.gpuservice.instance.nofound'
|
||||||
subTitle="创建一个 GPU 实例后会显示在这里"
|
})}
|
||||||
|
title={intl.formatMessage({
|
||||||
|
id: 'noresult.gpuservice.instance.title'
|
||||||
|
})}
|
||||||
|
subTitle={intl.formatMessage({
|
||||||
|
id: 'noresult.gpuservice.instance.subTitle'
|
||||||
|
})}
|
||||||
onClick={handleAddInstance}
|
onClick={handleAddInstance}
|
||||||
buttonText="立即添加"
|
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -267,8 +273,10 @@ const GPUService: React.FC = () => {
|
|||||||
showSelect={false}
|
showSelect={false}
|
||||||
selectOptions={k8sClusterList}
|
selectOptions={k8sClusterList}
|
||||||
select={{ showSearch: true }}
|
select={{ showSearch: true }}
|
||||||
selectHolder="按集群过滤"
|
selectHolder={intl.formatMessage({
|
||||||
buttonText="添加 GPU 实例"
|
id: 'gpuservice.instance.filter.cluster'
|
||||||
|
})}
|
||||||
|
buttonText={intl.formatMessage({ id: 'gpuservice.instance.add' })}
|
||||||
handleSearch={handleSearch}
|
handleSearch={handleSearch}
|
||||||
handleSelectChange={handleClusterChange}
|
handleSelectChange={handleClusterChange}
|
||||||
handleDeleteByBatch={handleDeleteBatch}
|
handleDeleteByBatch={handleDeleteBatch}
|
||||||
|
|||||||
@@ -7,23 +7,26 @@ export const StorageTypeValueMap = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const StorageTypeLabelMap: Record<string, string> = {
|
export const StorageTypeLabelMap: Record<string, string> = {
|
||||||
[StorageTypeValueMap.Local]: '本地存储',
|
[StorageTypeValueMap.Local]: 'gpuservice.storage.type.local',
|
||||||
[StorageTypeValueMap.Shared]: '共享存储',
|
[StorageTypeValueMap.Shared]: 'gpuservice.storage.type.shared',
|
||||||
[StorageTypeValueMap.Object]: '对象存储'
|
[StorageTypeValueMap.Object]: 'gpuservice.storage.type.object'
|
||||||
};
|
};
|
||||||
|
|
||||||
export const StorageTypeOptions = [
|
export const StorageTypeOptions = [
|
||||||
{
|
{
|
||||||
label: StorageTypeLabelMap[StorageTypeValueMap.Local],
|
label: StorageTypeLabelMap[StorageTypeValueMap.Local],
|
||||||
value: StorageTypeValueMap.Local
|
value: StorageTypeValueMap.Local,
|
||||||
|
locale: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: StorageTypeLabelMap[StorageTypeValueMap.Shared],
|
label: StorageTypeLabelMap[StorageTypeValueMap.Shared],
|
||||||
value: StorageTypeValueMap.Shared
|
value: StorageTypeValueMap.Shared,
|
||||||
|
locale: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: StorageTypeLabelMap[StorageTypeValueMap.Object],
|
label: StorageTypeLabelMap[StorageTypeValueMap.Object],
|
||||||
value: StorageTypeValueMap.Object
|
value: StorageTypeValueMap.Object,
|
||||||
|
locale: true
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -76,13 +79,15 @@ export const ReClaimPolicyOptions = [
|
|||||||
|
|
||||||
export const rowActionList = [
|
export const rowActionList = [
|
||||||
{
|
{
|
||||||
label: '编辑',
|
label: 'common.button.edit',
|
||||||
key: 'edit',
|
key: 'edit',
|
||||||
|
locale: true,
|
||||||
icon: icons.EditOutlined
|
icon: icons.EditOutlined
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '删除',
|
label: 'common.button.delete',
|
||||||
key: 'delete',
|
key: 'delete',
|
||||||
|
locale: true,
|
||||||
icon: icons.DeleteOutlined,
|
icon: icons.DeleteOutlined,
|
||||||
props: {
|
props: {
|
||||||
danger: true
|
danger: true
|
||||||
|
|||||||
@@ -4,12 +4,14 @@ import {
|
|||||||
Select as SealSelect,
|
Select as SealSelect,
|
||||||
useAppUtils
|
useAppUtils
|
||||||
} from '@gpustack/core-ui';
|
} from '@gpustack/core-ui';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
import { Flex, Form } from 'antd';
|
import { Flex, Form } from 'antd';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { FormData } from '../config/types';
|
import { FormData } from '../config/types';
|
||||||
import useQueryStorageClass from '../services/use-query-storage-class';
|
import useQueryStorageClass from '../services/use-query-storage-class';
|
||||||
|
|
||||||
const Basic = ({ open }: { open: boolean }) => {
|
const Basic = ({ open }: { open: boolean }) => {
|
||||||
|
const intl = useIntl();
|
||||||
const { getRuleMessage } = useAppUtils();
|
const { getRuleMessage } = useAppUtils();
|
||||||
const { storageClassList, fetchData, loading } = useQueryStorageClass();
|
const { storageClassList, fetchData, loading } = useQueryStorageClass();
|
||||||
|
|
||||||
@@ -26,11 +28,17 @@ const Basic = ({ open }: { open: boolean }) => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
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>
|
</Form.Item>
|
||||||
<Flex gap={16}>
|
<Flex gap={16}>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ flex: 1 }}>
|
||||||
@@ -39,12 +47,15 @@ const Basic = ({ open }: { open: boolean }) => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: getRuleMessage('select', '存储类型')
|
message: getRuleMessage(
|
||||||
|
'select',
|
||||||
|
intl.formatMessage({ id: 'gpuservice.storage.type' })
|
||||||
|
)
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<SealSelect
|
<SealSelect
|
||||||
label="存储类型"
|
label={intl.formatMessage({ id: 'gpuservice.storage.type' })}
|
||||||
required
|
required
|
||||||
loading={loading}
|
loading={loading}
|
||||||
options={storageClassList}
|
options={storageClassList}
|
||||||
@@ -61,11 +72,17 @@ const Basic = ({ open }: { open: boolean }) => {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
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>
|
</Form.Item>
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { tableSorter } from '@/config/settings';
|
import { tableSorter } from '@/config/settings';
|
||||||
import { AutoTooltip, DropdownButtons } from '@gpustack/core-ui';
|
import { AutoTooltip, DropdownButtons } from '@gpustack/core-ui';
|
||||||
|
import { useIntl } from '@umijs/max';
|
||||||
import type { ColumnsType } from 'antd/lib/table';
|
import type { ColumnsType } from 'antd/lib/table';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
@@ -15,10 +16,11 @@ const useStorageColumns = ({
|
|||||||
handleSelect,
|
handleSelect,
|
||||||
sortOrder
|
sortOrder
|
||||||
}: ColumnsHookProps): ColumnsType<ListItem> => {
|
}: ColumnsHookProps): ColumnsType<ListItem> => {
|
||||||
|
const intl = useIntl();
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
title: '名称',
|
title: intl.formatMessage({ id: 'common.table.name' }),
|
||||||
dataIndex: ['metadata', 'name'],
|
dataIndex: ['metadata', 'name'],
|
||||||
key: 'name',
|
key: 'name',
|
||||||
sorter: tableSorter(1),
|
sorter: tableSorter(1),
|
||||||
@@ -32,28 +34,31 @@ const useStorageColumns = ({
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '类型',
|
title: intl.formatMessage({ id: 'common.table.type' }),
|
||||||
dataIndex: ['spec', 'type'],
|
dataIndex: ['spec', 'type'],
|
||||||
key: 'type',
|
key: 'type',
|
||||||
sorter: tableSorter(2),
|
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'],
|
dataIndex: ['spec', 'capacity'],
|
||||||
key: 'capacity',
|
key: 'capacity',
|
||||||
sorter: tableSorter(3),
|
sorter: tableSorter(3),
|
||||||
render: (value: string) => value ?? '-'
|
render: (value: string) => value ?? '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '访问模式',
|
title: intl.formatMessage({ id: 'gpuservice.storage.accessMode' }),
|
||||||
dataIndex: ['spec', 'accessMode'],
|
dataIndex: ['spec', 'accessMode'],
|
||||||
key: 'accessMode',
|
key: 'accessMode',
|
||||||
sorter: tableSorter(4),
|
sorter: tableSorter(4),
|
||||||
render: (value: string) => value || '-'
|
render: (value: string) => value || '-'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '创建时间',
|
title: intl.formatMessage({ id: 'common.table.createTime' }),
|
||||||
dataIndex: ['metadata', 'creationTimestamp'],
|
dataIndex: ['metadata', 'creationTimestamp'],
|
||||||
key: 'creationTimestamp',
|
key: 'creationTimestamp',
|
||||||
sorter: tableSorter(5),
|
sorter: tableSorter(5),
|
||||||
@@ -67,7 +72,7 @@ const useStorageColumns = ({
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '操作',
|
title: intl.formatMessage({ id: 'common.table.operation' }),
|
||||||
key: 'operation',
|
key: 'operation',
|
||||||
dataIndex: 'operation',
|
dataIndex: 'operation',
|
||||||
render: (_text, record) => (
|
render: (_text, record) => (
|
||||||
@@ -78,7 +83,7 @@ const useStorageColumns = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}, [handleSelect, sortOrder]);
|
}, [handleSelect, sortOrder, intl]);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useStorageColumns;
|
export default useStorageColumns;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import useCreateStorage from './services/use-create-storage';
|
|||||||
import useUpdateStorage from './services/use-update-storage';
|
import useUpdateStorage from './services/use-update-storage';
|
||||||
|
|
||||||
const GPUServiceStorage: React.FC = () => {
|
const GPUServiceStorage: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
const { initialState } = useModel('@@initialState');
|
const { initialState } = useModel('@@initialState');
|
||||||
const namespace = initialState?.currentUser?.org_name || 'default';
|
const namespace = initialState?.currentUser?.org_name || 'default';
|
||||||
const [currentCluster, setCurrentCluster] = useAtom(currentClusterAtom);
|
const [currentCluster, setCurrentCluster] = useAtom(currentClusterAtom);
|
||||||
@@ -98,7 +99,7 @@ const GPUServiceStorage: React.FC = () => {
|
|||||||
deleteAPI: deleteStorage,
|
deleteAPI: deleteStorage,
|
||||||
watch: false,
|
watch: false,
|
||||||
API: GPU_SERVICE_STORAGE_API({ namespace, clusterID }),
|
API: GPU_SERVICE_STORAGE_API({ namespace, clusterID }),
|
||||||
contentForDelete: '存储'
|
contentForDelete: intl.formatMessage({ id: 'gpuservice.storage' })
|
||||||
});
|
});
|
||||||
|
|
||||||
const { fetchData: createStorage } = useCreateStorage();
|
const { fetchData: createStorage } = useCreateStorage();
|
||||||
@@ -108,7 +109,6 @@ const GPUServiceStorage: React.FC = () => {
|
|||||||
cancelRequest: cancelClusterRequest,
|
cancelRequest: cancelClusterRequest,
|
||||||
clusterList
|
clusterList
|
||||||
} = useQueryClusterList();
|
} = useQueryClusterList();
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const k8sClusterList = useMemo(
|
const k8sClusterList = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -156,7 +156,7 @@ const GPUServiceStorage: React.FC = () => {
|
|||||||
const handleAddStorage = () => {
|
const handleAddStorage = () => {
|
||||||
setOpenAddModalStatus({
|
setOpenAddModalStatus({
|
||||||
action: PageAction.CREATE,
|
action: PageAction.CREATE,
|
||||||
title: '添加存储',
|
title: intl.formatMessage({ id: 'gpuservice.storage.add' }),
|
||||||
open: true,
|
open: true,
|
||||||
currentData: null
|
currentData: null
|
||||||
});
|
});
|
||||||
@@ -165,7 +165,7 @@ const GPUServiceStorage: React.FC = () => {
|
|||||||
const handleEditStorage = (row: ListItem) => {
|
const handleEditStorage = (row: ListItem) => {
|
||||||
setOpenAddModalStatus({
|
setOpenAddModalStatus({
|
||||||
action: PageAction.EDIT,
|
action: PageAction.EDIT,
|
||||||
title: '编辑存储',
|
title: intl.formatMessage({ id: 'gpuservice.storage.edit' }),
|
||||||
open: true,
|
open: true,
|
||||||
currentData: row
|
currentData: row
|
||||||
});
|
});
|
||||||
@@ -193,9 +193,9 @@ const GPUServiceStorage: React.FC = () => {
|
|||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
closeModal();
|
closeModal();
|
||||||
message.success('操作成功');
|
message.success(intl.formatMessage({ id: 'common.message.success' }));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error('操作失败');
|
message.error(intl.formatMessage({ id: 'common.message.fail' }));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -227,11 +227,15 @@ const GPUServiceStorage: React.FC = () => {
|
|||||||
dataSource={dataSource.dataList}
|
dataSource={dataSource.dataList}
|
||||||
image={<IconFont type="icon-storage-outlined" />}
|
image={<IconFont type="icon-storage-outlined" />}
|
||||||
filters={_.omit(queryParams, ['sort_by'])}
|
filters={_.omit(queryParams, ['sort_by'])}
|
||||||
noFoundText="未找到匹配的存储"
|
noFoundText={intl.formatMessage({
|
||||||
title="暂无存储"
|
id: 'noresult.gpuservice.storage.nofound'
|
||||||
subTitle="创建一个存储后会显示在这里"
|
})}
|
||||||
|
title={intl.formatMessage({ id: 'noresult.gpuservice.storage.title' })}
|
||||||
|
subTitle={intl.formatMessage({
|
||||||
|
id: 'noresult.gpuservice.storage.subTitle'
|
||||||
|
})}
|
||||||
onClick={handleAddStorage}
|
onClick={handleAddStorage}
|
||||||
buttonText="立即添加"
|
buttonText={intl.formatMessage({ id: 'noresult.button.add' })}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -269,8 +273,10 @@ const GPUServiceStorage: React.FC = () => {
|
|||||||
showSelect={false}
|
showSelect={false}
|
||||||
selectOptions={k8sClusterList}
|
selectOptions={k8sClusterList}
|
||||||
select={{ showSearch: true }}
|
select={{ showSearch: true }}
|
||||||
selectHolder="按集群过滤"
|
selectHolder={intl.formatMessage({
|
||||||
buttonText="添加存储"
|
id: 'gpuservice.storage.filter.cluster'
|
||||||
|
})}
|
||||||
|
buttonText={intl.formatMessage({ id: 'gpuservice.storage.add' })}
|
||||||
handleSearch={handleSearch}
|
handleSearch={handleSearch}
|
||||||
handleSelectChange={handleClusterChange}
|
handleSelectChange={handleClusterChange}
|
||||||
handleDeleteByBatch={handleDeleteBatch}
|
handleDeleteByBatch={handleDeleteBatch}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ const Basic: React.FC<BasicProps> = ({ page = 'template' }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<div data-field="template"></div>
|
||||||
{page === 'template' && (
|
{page === 'template' && (
|
||||||
<>
|
<>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
|
|||||||
Reference in New Issue
Block a user