feat: template port name
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 286 64"><g><g><defs><path id="SVGID_1_" d="M47.5 17.6L25 4.8v52.6l9-5.2V37.4l6.8 3.9-.1-10.1-6.7-3.9v-5.9l13.5 7.9z"/></defs><clipPath id="SVGID_2_"><use xlink:href="#SVGID_1_" overflow="visible"/></clipPath><g clip-path="url(#SVGID_2_)"><linearGradient id="SVGID_3_" gradientUnits="userSpaceOnUse" x1="-1.6" y1="335.05" x2="53.6" y2="335.05" gradientTransform="translate(0 -304)"><stop offset="0" stop-color="#ff6f00"/><stop offset="1" stop-color="#ffa800"/></linearGradient><path d="M-1.6 4.6h55.2v52.9H-1.6V4.6z" fill="url(#SVGID_3_)"/></g></g></g><g><g><defs><path id="SVGID_4_" d="M.5 17.6L23 4.8v52.6l-9-5.2V21.4L.5 29.3z"/></defs><clipPath id="SVGID_5_"><use xlink:href="#SVGID_4_" overflow="visible"/></clipPath><g clip-path="url(#SVGID_5_)"><linearGradient id="SVGID_6_" gradientUnits="userSpaceOnUse" x1="-1.9" y1="335.05" x2="53.3" y2="335.05" gradientTransform="translate(0 -304)"><stop offset="0" stop-color="#ff6f00"/><stop offset="1" stop-color="#ffa800"/></linearGradient><path d="M-1.9 4.6h55.2v52.9H-1.9V4.6z" fill="url(#SVGID_6_)"/></g></g></g><path style="fill:#425066" d="M88.2 21.1h-10v27.7h-5.6V21.1h-10v-4.5h25.6v4.5z"/><path style="fill:#425066" d="M94.9 49.2c-3.4 0-6.2-1.1-8.3-3.2-2.1-2.1-3.2-5-3.2-8.6v-.7c0-2.2.4-4.4 1.4-6.4.9-1.8 2.2-3.3 3.9-4.4 1.7-1.1 3.6-1.6 5.6-1.6 3.3 0 5.8 1 7.6 3.1s2.7 5 2.7 8.8v2.2H88.9c.1 1.8.8 3.4 2 4.7 1.2 1.2 2.7 1.8 4.4 1.7 2.4.1 4.6-1.1 6-3l2.9 2.8c-1 1.4-2.3 2.6-3.8 3.3-1.8 1-3.6 1.4-5.5 1.3zm-.6-20.5c-1.4-.1-2.7.5-3.6 1.5-1 1.2-1.6 2.7-1.7 4.3h10.3v-.4c-.1-1.8-.6-3.2-1.4-4.1-1-.8-2.3-1.4-3.6-1.3zm19.4-3.9l.2 2.8c1.7-2.1 4.3-3.3 7-3.2 5 0 7.5 2.9 7.6 8.6v15.8h-5.4V33.3c0-1.5-.3-2.6-1-3.4-.7-.7-1.7-1.1-3.2-1.1-2.1-.1-4 1.1-4.9 2.9v17h-5.4v-24l5.1.1zm32.2 17.5c0-.9-.4-1.7-1.2-2.2-1.2-.7-2.6-1.1-3.9-1.3-1.6-.3-3.1-.8-4.6-1.5-2.7-1.3-4-3.2-4-5.6 0-2 1-4 2.6-5.2 1.7-1.4 4-2.1 6.6-2.1 2.9 0 5.2.7 6.9 2.1 1.7 1.3 2.7 3.4 2.6 5.5h-5.4c0-1-.4-1.9-1.2-2.6-.9-.7-1.9-1.1-3.1-1-1 0-2 .2-2.9.8-.7.5-1.1 1.3-1.1 2.2 0 .8.4 1.5 1 1.9.7.5 2.1.9 4.2 1.4 1.7.3 3.4.9 5 1.7 1.1.5 2 1.3 2.7 2.3.6 1 .9 2.1.9 3.3 0 2.1-1 4-2.7 5.2-1.8 1.3-4.1 2-7 2-1.8 0-3.6-.3-5.2-1.1-1.4-.6-2.7-1.6-3.6-2.9-.8-1.2-1.3-2.6-1.3-4h5.2c0 1.1.5 2.2 1.4 2.9 1 .7 2.3 1.1 3.5 1 1.4 0 2.5-.3 3.2-.8 1-.4 1.4-1.2 1.4-2zm8.1-5.7c0-2.2.4-4.4 1.4-6.3.9-1.8 2.2-3.3 3.9-4.3 1.8-1 3.8-1.6 5.8-1.5 3.2 0 5.9 1 7.9 3.1s3.1 4.8 3.3 8.3v1.3c0 2.2-.4 4.3-1.4 6.3-.8 1.8-2.2 3.3-3.9 4.3-1.8 1-3.8 1.6-5.9 1.5-3.4 0-6.1-1.1-8.1-3.4-2-2.2-3-5.2-3.1-9l.1-.3zm5.3.5c0 2.5.5 4.4 1.5 5.8 1.8 2.3 5.1 2.8 7.5 1 .4-.3.7-.6 1-1 1-1.4 1.5-3.5 1.5-6.2 0-2.4-.5-4.3-1.6-5.8-1.7-2.3-5-2.8-7.4-1.1-.4.3-.8.7-1.1 1-.8 1.4-1.4 3.5-1.4 6.3zm33.1-7.3c-.7-.1-1.5-.2-2.2-.2-2.5 0-4.1.9-5 2.8v16.4h-5.4v-24h5.1l.1 2.7c1.3-2.1 3.1-3.1 5.4-3.1.6 0 1.3.1 1.9.3l.1 5.1zm22.5 5.3h-13v13.7h-5.6V16.6h20.5v4.5h-14.9v9.6h13v4.4zm10.7 13.7h-5.4V16.5h5.4v32.3zm3.9-12.2c0-2.2.4-4.4 1.4-6.3.9-1.8 2.2-3.3 3.9-4.3 1.8-1 3.8-1.6 5.8-1.5 3.2 0 5.9 1 7.9 3.1s3.1 4.8 3.3 8.3v1.3c0 2.2-.4 4.3-1.3 6.3-.8 1.8-2.2 3.3-3.9 4.3-1.8 1-3.8 1.6-5.9 1.5-3.4 0-6.1-1.1-8.1-3.4-2-2.2-3.1-5.2-3.1-9v-.3zm5.4.5c0 2.5.5 4.4 1.5 5.8 1 1.4 2.6 2.2 4.3 2.1 1.7.1 3.3-.7 4.2-2.1 1-1.4 1.5-3.5 1.5-6.2 0-2.4-.5-4.3-1.6-5.8-1.7-2.3-5-2.8-7.4-1.1-.4.3-.8.7-1.1 1-.9 1.4-1.4 3.5-1.4 6.3zm41.2 4.3l3.8-16.5h5.2l-6.5 24h-4.4l-5.1-16.5-5.1 16.5h-4.4l-6.6-24h5.3l3.9 16.4 4.9-16.4h4.1l4.9 16.5z"/></svg>
|
||||||
|
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
@@ -358,7 +358,7 @@ export default (props: any) => {
|
|||||||
config={{
|
config={{
|
||||||
apiBaseUrl: GPUSTACK_API_BASE_URL,
|
apiBaseUrl: GPUSTACK_API_BASE_URL,
|
||||||
theme: userSettings.theme,
|
theme: userSettings.theme,
|
||||||
iconUrl: '//at.alicdn.com/t/c/font_4613488_r6z6oew38db.js',
|
iconUrl: '//at.alicdn.com/t/c/font_4613488_vy4szhexci.js',
|
||||||
isDarkTheme: userSettings.isDarkTheme,
|
isDarkTheme: userSettings.isDarkTheme,
|
||||||
defaultColorPrimary: COLOR_PRIMARY
|
defaultColorPrimary: COLOR_PRIMARY
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -15,10 +15,16 @@ export default {
|
|||||||
'gpuservice.template.mountPath': 'Mount Path',
|
'gpuservice.template.mountPath': 'Mount Path',
|
||||||
'gpuservice.template.containerDisk': 'Container Disk (GB)',
|
'gpuservice.template.containerDisk': 'Container Disk (GB)',
|
||||||
'gpuservice.template.memory': 'Memory (GB)',
|
'gpuservice.template.memory': 'Memory (GB)',
|
||||||
|
'gpuservice.template.displayName': 'Display Name',
|
||||||
|
'gpuservice.template.displayName.max':
|
||||||
|
'Display name cannot exceed 63 characters.',
|
||||||
'gpuservice.template.ports': 'Ports',
|
'gpuservice.template.ports': 'Ports',
|
||||||
'gpuservice.template.ports.add': 'Add Port',
|
'gpuservice.template.ports.add': 'Add Port',
|
||||||
'gpuservice.template.ports.invalid':
|
'gpuservice.template.ports.invalid':
|
||||||
'Please complete the port configuration.',
|
'Please complete the port configuration.',
|
||||||
|
'gpuservice.template.ports.name': 'Name',
|
||||||
|
'gpuservice.template.ports.name.max':
|
||||||
|
'Port name cannot exceed 16 characters.',
|
||||||
'gpuservice.template.env': 'Environment Variables',
|
'gpuservice.template.env': 'Environment Variables',
|
||||||
'gpuservice.template.env.add': 'Add Environment Variable',
|
'gpuservice.template.env.add': 'Add Environment Variable',
|
||||||
'gpuservice.template.env.invalid':
|
'gpuservice.template.env.invalid':
|
||||||
@@ -53,8 +59,7 @@ export default {
|
|||||||
'gpuservice.instance.sliced': 'Sliced',
|
'gpuservice.instance.sliced': 'Sliced',
|
||||||
'gpuservice.instance.memory': 'Memory',
|
'gpuservice.instance.memory': 'Memory',
|
||||||
'gpuservice.instance.ram': 'RAM',
|
'gpuservice.instance.ram': 'RAM',
|
||||||
'gpuservice.instance.search.type.placeholder':
|
'gpuservice.instance.search.type.placeholder': 'Search by name',
|
||||||
'Search by name, VRAM, memory or vCPU',
|
|
||||||
'gpuservice.instance.search.template.placeholder':
|
'gpuservice.instance.search.template.placeholder':
|
||||||
'Search by template name, image or mount path',
|
'Search by template name, image or mount path',
|
||||||
'gpuservice.instance.template.image': 'Image',
|
'gpuservice.instance.template.image': 'Image',
|
||||||
|
|||||||
@@ -15,9 +15,15 @@ export default {
|
|||||||
'gpuservice.template.mountPath': 'マウントパス',
|
'gpuservice.template.mountPath': 'マウントパス',
|
||||||
'gpuservice.template.containerDisk': 'コンテナディスク (GB)',
|
'gpuservice.template.containerDisk': 'コンテナディスク (GB)',
|
||||||
'gpuservice.template.memory': 'メモリ (GB)',
|
'gpuservice.template.memory': 'メモリ (GB)',
|
||||||
|
'gpuservice.template.displayName': '表示名',
|
||||||
|
'gpuservice.template.displayName.max':
|
||||||
|
'表示名は 63 文字以内で入力してください。',
|
||||||
'gpuservice.template.ports': 'ポート',
|
'gpuservice.template.ports': 'ポート',
|
||||||
'gpuservice.template.ports.add': 'ポートを追加',
|
'gpuservice.template.ports.add': 'ポートを追加',
|
||||||
'gpuservice.template.ports.invalid': 'ポート設定を完成させてください。',
|
'gpuservice.template.ports.invalid': 'ポート設定を完成させてください。',
|
||||||
|
'gpuservice.template.ports.name': '名前',
|
||||||
|
'gpuservice.template.ports.name.max':
|
||||||
|
'ポート名は 16 文字以内で入力してください。',
|
||||||
'gpuservice.template.env': '環境変数',
|
'gpuservice.template.env': '環境変数',
|
||||||
'gpuservice.template.env.add': '環境変数を追加',
|
'gpuservice.template.env.add': '環境変数を追加',
|
||||||
'gpuservice.template.env.invalid': '環境変数を完成させてください。',
|
'gpuservice.template.env.invalid': '環境変数を完成させてください。',
|
||||||
@@ -51,8 +57,7 @@ export default {
|
|||||||
'gpuservice.instance.sliced': '分割',
|
'gpuservice.instance.sliced': '分割',
|
||||||
'gpuservice.instance.memory': 'Memory',
|
'gpuservice.instance.memory': 'Memory',
|
||||||
'gpuservice.instance.ram': 'RAM',
|
'gpuservice.instance.ram': 'RAM',
|
||||||
'gpuservice.instance.search.type.placeholder':
|
'gpuservice.instance.search.type.placeholder': '名前で検索',
|
||||||
'名前、VRAM、メモリまたは vCPU で検索',
|
|
||||||
'gpuservice.instance.search.template.placeholder':
|
'gpuservice.instance.search.template.placeholder':
|
||||||
'テンプレート名、イメージまたはマウントパスで検索',
|
'テンプレート名、イメージまたはマウントパスで検索',
|
||||||
'gpuservice.instance.template.image': 'イメージ',
|
'gpuservice.instance.template.image': 'イメージ',
|
||||||
|
|||||||
@@ -16,9 +16,15 @@ export default {
|
|||||||
'gpuservice.template.mountPath': 'Путь монтирования',
|
'gpuservice.template.mountPath': 'Путь монтирования',
|
||||||
'gpuservice.template.containerDisk': 'Диск контейнера (GB)',
|
'gpuservice.template.containerDisk': 'Диск контейнера (GB)',
|
||||||
'gpuservice.template.memory': 'Память (GB)',
|
'gpuservice.template.memory': 'Память (GB)',
|
||||||
|
'gpuservice.template.displayName': 'Отображаемое имя',
|
||||||
|
'gpuservice.template.displayName.max':
|
||||||
|
'Отображаемое имя не должно превышать 63 символа.',
|
||||||
'gpuservice.template.ports': 'Порты',
|
'gpuservice.template.ports': 'Порты',
|
||||||
'gpuservice.template.ports.add': 'Добавить порт',
|
'gpuservice.template.ports.add': 'Добавить порт',
|
||||||
'gpuservice.template.ports.invalid': 'Заполните настройки портов полностью.',
|
'gpuservice.template.ports.invalid': 'Заполните настройки портов полностью.',
|
||||||
|
'gpuservice.template.ports.name': 'Имя',
|
||||||
|
'gpuservice.template.ports.name.max':
|
||||||
|
'Имя порта не должно превышать 16 символов.',
|
||||||
'gpuservice.template.env': 'Переменные окружения',
|
'gpuservice.template.env': 'Переменные окружения',
|
||||||
'gpuservice.template.env.add': 'Добавить переменную окружения',
|
'gpuservice.template.env.add': 'Добавить переменную окружения',
|
||||||
'gpuservice.template.env.invalid':
|
'gpuservice.template.env.invalid':
|
||||||
@@ -53,8 +59,7 @@ export default {
|
|||||||
'gpuservice.instance.sliced': 'Разделено',
|
'gpuservice.instance.sliced': 'Разделено',
|
||||||
'gpuservice.instance.memory': 'Память',
|
'gpuservice.instance.memory': 'Память',
|
||||||
'gpuservice.instance.ram': 'RAM',
|
'gpuservice.instance.ram': 'RAM',
|
||||||
'gpuservice.instance.search.type.placeholder':
|
'gpuservice.instance.search.type.placeholder': 'Поиск по имени',
|
||||||
'Поиск по имени, VRAM, памяти или vCPU',
|
|
||||||
'gpuservice.instance.search.template.placeholder':
|
'gpuservice.instance.search.template.placeholder':
|
||||||
'Поиск по имени шаблона, образу или пути монтирования',
|
'Поиск по имени шаблона, образу или пути монтирования',
|
||||||
'gpuservice.instance.template.image': 'Образ',
|
'gpuservice.instance.template.image': 'Образ',
|
||||||
|
|||||||
@@ -15,10 +15,16 @@ export default {
|
|||||||
'gpuservice.template.mountPath': 'Bağlama Yolu',
|
'gpuservice.template.mountPath': 'Bağlama Yolu',
|
||||||
'gpuservice.template.containerDisk': 'Konteyner Diski (GB)',
|
'gpuservice.template.containerDisk': 'Konteyner Diski (GB)',
|
||||||
'gpuservice.template.memory': 'Bellek (GB)',
|
'gpuservice.template.memory': 'Bellek (GB)',
|
||||||
|
'gpuservice.template.displayName': 'Görünen Ad',
|
||||||
|
'gpuservice.template.displayName.max':
|
||||||
|
'Görünen ad 63 karakterden uzun olamaz.',
|
||||||
'gpuservice.template.ports': 'Bağlantı Noktaları',
|
'gpuservice.template.ports': 'Bağlantı Noktaları',
|
||||||
'gpuservice.template.ports.add': 'Bağlantı Noktası Ekle',
|
'gpuservice.template.ports.add': 'Bağlantı Noktası Ekle',
|
||||||
'gpuservice.template.ports.invalid':
|
'gpuservice.template.ports.invalid':
|
||||||
'Bağlantı noktası yapılandırmasını eksiksiz doldurun.',
|
'Bağlantı noktası yapılandırmasını eksiksiz doldurun.',
|
||||||
|
'gpuservice.template.ports.name': 'Ad',
|
||||||
|
'gpuservice.template.ports.name.max':
|
||||||
|
'Bağlantı noktası adı 16 karakterden uzun olamaz.',
|
||||||
'gpuservice.template.env': 'Ortam Değişkenleri',
|
'gpuservice.template.env': 'Ortam Değişkenleri',
|
||||||
'gpuservice.template.env.add': 'Ortam Değişkeni Ekle',
|
'gpuservice.template.env.add': 'Ortam Değişkeni Ekle',
|
||||||
'gpuservice.template.env.invalid': 'Ortam değişkenlerini eksiksiz doldurun.',
|
'gpuservice.template.env.invalid': 'Ortam değişkenlerini eksiksiz doldurun.',
|
||||||
@@ -52,8 +58,7 @@ export default {
|
|||||||
'gpuservice.instance.sliced': 'Bölünmüş',
|
'gpuservice.instance.sliced': 'Bölünmüş',
|
||||||
'gpuservice.instance.memory': 'Bellek',
|
'gpuservice.instance.memory': 'Bellek',
|
||||||
'gpuservice.instance.ram': 'RAM',
|
'gpuservice.instance.ram': 'RAM',
|
||||||
'gpuservice.instance.search.type.placeholder':
|
'gpuservice.instance.search.type.placeholder': 'Ada göre ara',
|
||||||
"Ada, VRAM, belleğe veya vCPU'ya göre ara",
|
|
||||||
'gpuservice.instance.search.template.placeholder':
|
'gpuservice.instance.search.template.placeholder':
|
||||||
'Şablon adına, imaja veya bağlama yoluna göre ara',
|
'Şablon adına, imaja veya bağlama yoluna göre ara',
|
||||||
'gpuservice.instance.template.image': 'İmaj',
|
'gpuservice.instance.template.image': 'İmaj',
|
||||||
|
|||||||
@@ -15,9 +15,13 @@ export default {
|
|||||||
'gpuservice.template.mountPath': '挂载路径',
|
'gpuservice.template.mountPath': '挂载路径',
|
||||||
'gpuservice.template.containerDisk': '容器磁盘(GB)',
|
'gpuservice.template.containerDisk': '容器磁盘(GB)',
|
||||||
'gpuservice.template.memory': '内存(GB)',
|
'gpuservice.template.memory': '内存(GB)',
|
||||||
|
'gpuservice.template.displayName': '显示名称',
|
||||||
|
'gpuservice.template.displayName.max': '显示名称不能超过 63 个字符',
|
||||||
'gpuservice.template.ports': '端口',
|
'gpuservice.template.ports': '端口',
|
||||||
'gpuservice.template.ports.add': '添加端口',
|
'gpuservice.template.ports.add': '添加端口',
|
||||||
'gpuservice.template.ports.invalid': '请填写完整的端口配置',
|
'gpuservice.template.ports.invalid': '请填写完整的端口配置',
|
||||||
|
'gpuservice.template.ports.name': '名称',
|
||||||
|
'gpuservice.template.ports.name.max': '端口名称不能超过 16 个字符',
|
||||||
'gpuservice.template.env': '环境变量',
|
'gpuservice.template.env': '环境变量',
|
||||||
'gpuservice.template.env.add': '添加环境变量',
|
'gpuservice.template.env.add': '添加环境变量',
|
||||||
'gpuservice.template.env.invalid': '请填写完整的环境变量',
|
'gpuservice.template.env.invalid': '请填写完整的环境变量',
|
||||||
@@ -50,7 +54,7 @@ export default {
|
|||||||
'gpuservice.instance.sliced': '切分',
|
'gpuservice.instance.sliced': '切分',
|
||||||
'gpuservice.instance.memory': '显存',
|
'gpuservice.instance.memory': '显存',
|
||||||
'gpuservice.instance.ram': '内存',
|
'gpuservice.instance.ram': '内存',
|
||||||
'gpuservice.instance.search.type.placeholder': '搜索名称、显存、内存或 vCPU',
|
'gpuservice.instance.search.type.placeholder': '搜索名称',
|
||||||
'gpuservice.instance.search.template.placeholder':
|
'gpuservice.instance.search.template.placeholder':
|
||||||
'搜索模板名称、镜像或挂载路径',
|
'搜索模板名称、镜像或挂载路径',
|
||||||
'gpuservice.instance.template.image': '镜像',
|
'gpuservice.instance.template.image': '镜像',
|
||||||
|
|||||||
@@ -176,16 +176,7 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
|
|
||||||
// filter instance types
|
// filter instance types
|
||||||
const filteredInstanceTypes = instanceTypeList.filter((item) =>
|
const filteredInstanceTypes = instanceTypeList.filter((item) =>
|
||||||
matchKeyword(
|
matchKeyword([item.metadata?.name], instanceKeyword)
|
||||||
[
|
|
||||||
item.metadata?.name,
|
|
||||||
item.spec?.memory,
|
|
||||||
item.status?.cpu?.capacity,
|
|
||||||
item.status?.ram?.capacity,
|
|
||||||
item.status?.accelerator?.remaining
|
|
||||||
],
|
|
||||||
instanceKeyword
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// filter templates based on selection and keyword
|
// filter templates based on selection and keyword
|
||||||
@@ -256,17 +247,20 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
<div className={styles.colWrapper}>
|
<div className={styles.colWrapper}>
|
||||||
<ColumnWrapper styles={{ container: { paddingBlock: 0 } }}>
|
<ColumnWrapper styles={{ container: { paddingBlock: 0 } }}>
|
||||||
<div className={styles.panelBody}>
|
<div className={styles.panelBody}>
|
||||||
<ColTitle style={{ paddingBottom: 0 }}>
|
|
||||||
{intl.formatMessage({ id: 'gpuservice.instance.types' })}
|
|
||||||
</ColTitle>
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 16,
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: 0,
|
top: 0,
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
backgroundColor: 'var(--ant-color-bg-elevated)'
|
backgroundColor: 'var(--ant-color-bg-elevated)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<ColTitle style={{ paddingBottom: 0 }}>
|
||||||
|
{intl.formatMessage({ id: 'gpuservice.instance.types' })}
|
||||||
|
</ColTitle>
|
||||||
<Input
|
<Input
|
||||||
allowClear
|
allowClear
|
||||||
prefix={<SearchOutlined className="text-tertiary" />}
|
prefix={<SearchOutlined className="text-tertiary" />}
|
||||||
@@ -290,17 +284,20 @@ const AddModal: React.FC<AddModalProps> = ({
|
|||||||
<div className={styles.colWrapper}>
|
<div className={styles.colWrapper}>
|
||||||
<ColumnWrapper styles={{ container: { paddingBlock: 0 } }}>
|
<ColumnWrapper styles={{ container: { paddingBlock: 0 } }}>
|
||||||
<div className={styles.panelBody}>
|
<div className={styles.panelBody}>
|
||||||
<ColTitle style={{ paddingBottom: 0 }}>
|
|
||||||
{intl.formatMessage({ id: 'gpuservice.instance.templates' })}
|
|
||||||
</ColTitle>
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 16,
|
||||||
position: 'sticky',
|
position: 'sticky',
|
||||||
top: 0,
|
top: 0,
|
||||||
zIndex: 10,
|
zIndex: 10,
|
||||||
backgroundColor: 'var(--ant-color-bg-elevated)'
|
backgroundColor: 'var(--ant-color-bg-elevated)'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<ColTitle style={{ paddingBottom: 0 }}>
|
||||||
|
{intl.formatMessage({ id: 'gpuservice.instance.templates' })}
|
||||||
|
</ColTitle>
|
||||||
<Input
|
<Input
|
||||||
allowClear
|
allowClear
|
||||||
prefix={<SearchOutlined className="text-tertiary" />}
|
prefix={<SearchOutlined className="text-tertiary" />}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export interface FormData {
|
|||||||
ports: {
|
ports: {
|
||||||
port: number;
|
port: number;
|
||||||
protocol?: string;
|
protocol?: string;
|
||||||
|
name?: string;
|
||||||
}[];
|
}[];
|
||||||
env: {
|
env: {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ const TemplateSelector: React.FC<TemplateSelectorProps> = ({
|
|||||||
<TemplateContent>
|
<TemplateContent>
|
||||||
<div className="name">
|
<div className="name">
|
||||||
<AutoTooltip ghost minWidth={20}>
|
<AutoTooltip ghost minWidth={20}>
|
||||||
{item.name}
|
{item.displayName || item.name || '-'}
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
</div>
|
</div>
|
||||||
<div className="info">
|
<div className="info">
|
||||||
|
|||||||
@@ -2,17 +2,27 @@ import ascendLogo from '@/assets/logo/ascend.png';
|
|||||||
import CambriconPNG from '@/assets/logo/cambricon.png';
|
import CambriconPNG from '@/assets/logo/cambricon.png';
|
||||||
import hyponPNG from '@/assets/logo/hygon.png';
|
import hyponPNG from '@/assets/logo/hygon.png';
|
||||||
import iluvatarWEBP from '@/assets/logo/Iluvatar.png';
|
import iluvatarWEBP from '@/assets/logo/Iluvatar.png';
|
||||||
|
import jupyterLogo from '@/assets/logo/jupyter.png';
|
||||||
import metaxLogo from '@/assets/logo/metax.png';
|
import metaxLogo from '@/assets/logo/metax.png';
|
||||||
import mooreLogo from '@/assets/logo/moore-logo.png';
|
import mooreLogo from '@/assets/logo/moore-logo.png';
|
||||||
import nvidiaLogo from '@/assets/logo/nvidia.png';
|
import nvidiaLogo from '@/assets/logo/nvidia.png';
|
||||||
|
import pytorchBlackLogo from '@/assets/logo/pytorch_black.png';
|
||||||
|
import sgLangLogo from '@/assets/logo/sglang.png';
|
||||||
import theadLogoEN from '@/assets/logo/t-head-en.png';
|
import theadLogoEN from '@/assets/logo/t-head-en.png';
|
||||||
import theadLogoZH from '@/assets/logo/t-head-zh.png';
|
import theadLogoZH from '@/assets/logo/t-head-zh.png';
|
||||||
import { manfacturerValueMap } from '@/pages/resources/config/gpu-driver';
|
import tensorflowkLogo from '@/assets/logo/tensorflow.svg';
|
||||||
|
import ubuntuLogo from '@/assets/logo/ubuntu.png';
|
||||||
|
import vllmLogo from '@/assets/logo/vllm.png';
|
||||||
|
import {
|
||||||
|
GPUsConfigs,
|
||||||
|
manfacturerValueMap
|
||||||
|
} from '@/pages/resources/config/gpu-driver';
|
||||||
import {
|
import {
|
||||||
AutoTooltip,
|
AutoTooltip,
|
||||||
DropdownActions,
|
DropdownActions,
|
||||||
IconFont,
|
IconFont,
|
||||||
TemplateCard
|
TemplateCard,
|
||||||
|
ThemeTag
|
||||||
} from '@gpustack/core-ui';
|
} from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Button, Tag } from 'antd';
|
import { Button, Tag } from 'antd';
|
||||||
@@ -20,6 +30,56 @@ import styled from 'styled-components';
|
|||||||
import { templateActions } from '../config';
|
import { templateActions } from '../config';
|
||||||
import { ListItem } from '../config/types';
|
import { ListItem } from '../config/types';
|
||||||
|
|
||||||
|
const imageLogoMap = {
|
||||||
|
vllm: vllmLogo,
|
||||||
|
sglang: sgLangLogo,
|
||||||
|
cann: ascendLogo,
|
||||||
|
jupyter: jupyterLogo,
|
||||||
|
pytorch: pytorchBlackLogo,
|
||||||
|
tensorflow: tensorflowkLogo,
|
||||||
|
ubuntu: ubuntuLogo
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export const manufactureColorMap: Record<string, string> = {
|
||||||
|
nvidia: 'green',
|
||||||
|
amd: 'volcano',
|
||||||
|
ascend: 'orange',
|
||||||
|
hygon: 'magenta',
|
||||||
|
moorthreads: 'cyan',
|
||||||
|
iluvatar: 'purple',
|
||||||
|
metax: 'geekblue',
|
||||||
|
cambricon: 'gold',
|
||||||
|
thead: 'red',
|
||||||
|
cpu: 'blue'
|
||||||
|
};
|
||||||
|
|
||||||
|
const manufacturerLabelMap: Record<string, string> = Object.values(
|
||||||
|
GPUsConfigs
|
||||||
|
).reduce(
|
||||||
|
(acc, item) => {
|
||||||
|
if (item.gpuVendor) acc[item.gpuVendor] = item.label;
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{ cpu: 'CPU' } as Record<string, string>
|
||||||
|
);
|
||||||
|
|
||||||
|
const matchImageLogo = (image: string | undefined): string | null => {
|
||||||
|
if (!image) return null;
|
||||||
|
const lower = image.toLowerCase();
|
||||||
|
let matched: keyof typeof imageLogoMap | null = null;
|
||||||
|
let earliest = Infinity;
|
||||||
|
(Object.keys(imageLogoMap) as Array<keyof typeof imageLogoMap>).forEach(
|
||||||
|
(key) => {
|
||||||
|
const idx = lower.indexOf(key);
|
||||||
|
if (idx !== -1 && idx < earliest) {
|
||||||
|
earliest = idx;
|
||||||
|
matched = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return matched ? imageLogoMap[matched] : null;
|
||||||
|
};
|
||||||
|
|
||||||
const StyledCard = styled(TemplateCard)`
|
const StyledCard = styled(TemplateCard)`
|
||||||
&:hover {
|
&:hover {
|
||||||
.operations {
|
.operations {
|
||||||
@@ -38,6 +98,8 @@ const Header = styled.div`
|
|||||||
.title {
|
.title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -73,7 +135,7 @@ const InfoItem = styled.div`
|
|||||||
color: var(--ant-color-text-quaternary);
|
color: var(--ant-color-text-quaternary);
|
||||||
}
|
}
|
||||||
.value {
|
.value {
|
||||||
color: var(--ant-color-text-secondary);
|
color: var(--ant-color-text-tertiary);
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -98,6 +160,10 @@ const TemplateCardItem: React.FC<TemplateCardProps> = ({ data, onSelect }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderLogo = () => {
|
const renderLogo = () => {
|
||||||
|
const imageLogo = matchImageLogo(data.spec?.image);
|
||||||
|
if (imageLogo) {
|
||||||
|
return <LogoImg src={imageLogo} height={22} />;
|
||||||
|
}
|
||||||
switch (data.manufacturer) {
|
switch (data.manufacturer) {
|
||||||
case manfacturerValueMap.NVIDIA:
|
case manfacturerValueMap.NVIDIA:
|
||||||
return <LogoImg src={nvidiaLogo} height={18} />;
|
return <LogoImg src={nvidiaLogo} height={18} />;
|
||||||
@@ -141,6 +207,19 @@ const TemplateCardItem: React.FC<TemplateCardProps> = ({ data, onSelect }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const renderManufacturerTag = () => {
|
||||||
|
if (!data.manufacturer) return null;
|
||||||
|
const label =
|
||||||
|
manufacturerLabelMap[data.manufacturer] ??
|
||||||
|
data.manufacturer.toUpperCase();
|
||||||
|
const color = manufactureColorMap[data.manufacturer] ?? 'purple';
|
||||||
|
return (
|
||||||
|
<ThemeTag color={color} style={{ fontWeight: 400 }}>
|
||||||
|
{label}
|
||||||
|
</ThemeTag>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const renderActions = () => {
|
const renderActions = () => {
|
||||||
return (
|
return (
|
||||||
<span onClick={handleonClickAction} className="operations">
|
<span onClick={handleonClickAction} className="operations">
|
||||||
@@ -177,8 +256,9 @@ const TemplateCardItem: React.FC<TemplateCardProps> = ({ data, onSelect }) => {
|
|||||||
<Content>
|
<Content>
|
||||||
<CardName>
|
<CardName>
|
||||||
<AutoTooltip ghost minWidth={20}>
|
<AutoTooltip ghost minWidth={20}>
|
||||||
{data.name || '-'}
|
{data.displayName || data.name || '-'}
|
||||||
</AutoTooltip>
|
</AutoTooltip>
|
||||||
|
{renderManufacturerTag()}
|
||||||
</CardName>
|
</CardName>
|
||||||
<InfoItem>
|
<InfoItem>
|
||||||
<span>
|
<span>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export interface PortItem {
|
export interface PortItem {
|
||||||
protocol: 'UDP' | 'TCP';
|
protocol: 'UDP' | 'TCP';
|
||||||
port: number;
|
port: number;
|
||||||
|
name?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ImagePullPolicy = 'Always' | 'IfNotPresent' | 'Never';
|
export type ImagePullPolicy = 'Always' | 'IfNotPresent' | 'Never';
|
||||||
@@ -13,6 +14,7 @@ export interface EnvItem {
|
|||||||
export interface FormData {
|
export interface FormData {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
displayName?: string;
|
||||||
manufacturer: string;
|
manufacturer: string;
|
||||||
spec: {
|
spec: {
|
||||||
image: string;
|
image: string;
|
||||||
|
|||||||
@@ -76,6 +76,25 @@ const Basic: React.FC<BasicProps> = ({ page = 'template', onceMaxRequest }) => {
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item<FormData>
|
||||||
|
name="displayName"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
max: 63,
|
||||||
|
message: intl.formatMessage({
|
||||||
|
id: 'gpuservice.template.displayName.max'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<CInput.Input
|
||||||
|
label={intl.formatMessage({
|
||||||
|
id: 'gpuservice.template.displayName'
|
||||||
|
})}
|
||||||
|
showCount
|
||||||
|
maxLength={63}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
<Form.Item<FormData>
|
<Form.Item<FormData>
|
||||||
name="manufacturer"
|
name="manufacturer"
|
||||||
rules={[
|
rules={[
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ const GPUServiceTemplateForm: React.FC<TemplateFormProps> = forwardRef(
|
|||||||
const handleFinish = async (values: FormData) => {
|
const handleFinish = async (values: FormData) => {
|
||||||
await onFinish({
|
await onFinish({
|
||||||
...values,
|
...values,
|
||||||
|
displayName: values.displayName?.trim() || values.name,
|
||||||
spec: {
|
spec: {
|
||||||
...values.spec,
|
...values.spec,
|
||||||
command: values.spec?.command?.filter(Boolean) ?? []
|
command: values.spec?.command?.filter(Boolean) ?? []
|
||||||
@@ -65,7 +66,8 @@ const GPUServiceTemplateForm: React.FC<TemplateFormProps> = forwardRef(
|
|||||||
ports: [
|
ports: [
|
||||||
{
|
{
|
||||||
protocol: 'TCP',
|
protocol: 'TCP',
|
||||||
port: 22
|
port: 22,
|
||||||
|
name: 'SSH'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
volumeMount: '/workspace',
|
volumeMount: '/workspace',
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import {
|
import {
|
||||||
|
Input as CInput,
|
||||||
InputNumber as CInputNumber,
|
InputNumber as CInputNumber,
|
||||||
MetadataList,
|
MetadataList,
|
||||||
Select as SealSelect
|
Select as SealSelect
|
||||||
} from '@gpustack/core-ui';
|
} from '@gpustack/core-ui';
|
||||||
import { useIntl } from '@umijs/max';
|
import { useIntl } from '@umijs/max';
|
||||||
import { Form } from 'antd';
|
import { Form } from 'antd';
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { FormData, PortItem as PortItemType } from '../config/types';
|
import { FormData, PortItem as PortItemType } from '../config/types';
|
||||||
|
|
||||||
type PortProtocol = PortItemType['protocol'];
|
type PortProtocol = PortItemType['protocol'];
|
||||||
|
|
||||||
|
const PORT_NAME_MAX = 16;
|
||||||
|
|
||||||
const protocolOptions = [
|
const protocolOptions = [
|
||||||
{
|
{
|
||||||
label: 'UDP',
|
label: 'UDP',
|
||||||
@@ -20,43 +24,154 @@ const protocolOptions = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const wellKnownTcpPortNames: Record<number, string> = {
|
||||||
|
22: 'SSH',
|
||||||
|
80: 'HTTP',
|
||||||
|
443: 'HTTPS'
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAutoFilledName = (
|
||||||
|
protocol: PortProtocol | undefined,
|
||||||
|
port: number | undefined,
|
||||||
|
previousProtocol: PortProtocol | undefined,
|
||||||
|
previousPort: number | undefined,
|
||||||
|
currentName: string | undefined
|
||||||
|
): string | undefined => {
|
||||||
|
if (protocol !== 'TCP' || !port) {
|
||||||
|
return currentName;
|
||||||
|
}
|
||||||
|
const suggested = wellKnownTcpPortNames[port];
|
||||||
|
if (!suggested) {
|
||||||
|
return currentName;
|
||||||
|
}
|
||||||
|
const previousSuggested =
|
||||||
|
previousProtocol === 'TCP' && previousPort
|
||||||
|
? wellKnownTcpPortNames[previousPort]
|
||||||
|
: undefined;
|
||||||
|
// Only overwrite when the name is empty or matches a previously suggested
|
||||||
|
// well-known name — never stomp on a user-entered value.
|
||||||
|
if (!currentName || currentName === previousSuggested) {
|
||||||
|
return suggested;
|
||||||
|
}
|
||||||
|
return currentName;
|
||||||
|
};
|
||||||
|
|
||||||
interface PortItemProps {
|
interface PortItemProps {
|
||||||
item: PortItemType;
|
item: PortItemType;
|
||||||
index: number;
|
index: number;
|
||||||
onChange: (item: PortItemType) => void;
|
onChange: (item: PortItemType) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PortNameInputProps {
|
||||||
|
value: string | undefined;
|
||||||
|
onChange: (value: string) => void;
|
||||||
|
placeholder: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PortNameInput: React.FC<PortNameInputProps> = ({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
placeholder
|
||||||
|
}) => {
|
||||||
|
const [localValue, setLocalValue] = useState<string>(value ?? '');
|
||||||
|
const isComposingRef = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isComposingRef.current) {
|
||||||
|
setLocalValue(value ?? '');
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CInput.Input
|
||||||
|
showCount
|
||||||
|
maxLength={PORT_NAME_MAX}
|
||||||
|
value={localValue}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onCompositionStart={() => {
|
||||||
|
isComposingRef.current = true;
|
||||||
|
}}
|
||||||
|
onCompositionEnd={(e) => {
|
||||||
|
isComposingRef.current = false;
|
||||||
|
const next = (e.target as HTMLInputElement).value;
|
||||||
|
setLocalValue(next);
|
||||||
|
onChange(next);
|
||||||
|
}}
|
||||||
|
onChange={(e) => {
|
||||||
|
const next = e.target.value;
|
||||||
|
setLocalValue(next);
|
||||||
|
if (!isComposingRef.current) {
|
||||||
|
onChange(next);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const PortItem: React.FC<PortItemProps> = ({ item, index, onChange }) => {
|
const PortItem: React.FC<PortItemProps> = ({ item, index, onChange }) => {
|
||||||
|
const intl = useIntl();
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', gap: 12, width: '100%' }}>
|
<div style={{ display: 'flex', gap: 12, width: '100%' }}>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ width: 120 }}>
|
||||||
<SealSelect
|
<SealSelect
|
||||||
value={item.protocol}
|
value={item.protocol}
|
||||||
options={protocolOptions}
|
options={protocolOptions}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
const nextProtocol = value as PortProtocol;
|
||||||
onChange({
|
onChange({
|
||||||
...item,
|
...item,
|
||||||
protocol: value as PortProtocol
|
protocol: nextProtocol,
|
||||||
|
name: getAutoFilledName(
|
||||||
|
nextProtocol,
|
||||||
|
item.port,
|
||||||
|
item.protocol,
|
||||||
|
item.port,
|
||||||
|
item.name
|
||||||
|
)
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
></SealSelect>
|
></SealSelect>
|
||||||
</div>
|
</div>
|
||||||
<div style={{ flex: 1 }}>
|
<div style={{ width: 120 }}>
|
||||||
<CInputNumber
|
<CInputNumber
|
||||||
min={1}
|
min={1}
|
||||||
max={65535}
|
max={65535}
|
||||||
precision={0}
|
precision={0}
|
||||||
value={item.port}
|
value={item.port}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
const nextPort =
|
||||||
|
typeof value === 'number' ? value : (undefined as any);
|
||||||
onChange({
|
onChange({
|
||||||
...item,
|
...item,
|
||||||
port: typeof value === 'number' ? value : (undefined as any)
|
port: nextPort,
|
||||||
|
name: getAutoFilledName(
|
||||||
|
item.protocol,
|
||||||
|
nextPort,
|
||||||
|
item.protocol,
|
||||||
|
item.port,
|
||||||
|
item.name
|
||||||
|
)
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div style={{ flex: 1 }}>
|
||||||
|
<PortNameInput
|
||||||
|
value={item.name}
|
||||||
|
placeholder={intl.formatMessage({
|
||||||
|
id: 'gpuservice.template.ports.name'
|
||||||
|
})}
|
||||||
|
onChange={(next) => {
|
||||||
|
onChange({
|
||||||
|
...item,
|
||||||
|
name: next
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -75,7 +190,8 @@ const Ports: React.FC = () => {
|
|||||||
...ports,
|
...ports,
|
||||||
{
|
{
|
||||||
protocol: 'TCP',
|
protocol: 'TCP',
|
||||||
port: undefined as any
|
port: undefined as any,
|
||||||
|
name: ''
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
@@ -113,6 +229,19 @@ const Ports: React.FC = () => {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const hasInvalidName = value.some(
|
||||||
|
(item: PortItemType) =>
|
||||||
|
item.name && item.name.length > PORT_NAME_MAX
|
||||||
|
);
|
||||||
|
if (hasInvalidName) {
|
||||||
|
return Promise.reject(
|
||||||
|
new Error(
|
||||||
|
intl.formatMessage({
|
||||||
|
id: 'gpuservice.template.ports.name.max'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user