From c6e7075bf5146f98b1094f027a6b5c6afa95c320 Mon Sep 17 00:00:00 2001 From: jialin Date: Thu, 14 May 2026 16:35:33 +0800 Subject: [PATCH] feat: template port name --- src/assets/logo/jupyter.png | Bin 0 -> 21006 bytes src/assets/logo/pytorch_black.png | Bin 0 -> 5317 bytes src/assets/logo/tensorflow.svg | 1 + src/assets/logo/ubuntu.png | Bin 0 -> 14493 bytes src/layouts/index.tsx | 2 +- src/locales/en-US/gpuservice.ts | 9 +- src/locales/ja-JP/gpuservice.ts | 9 +- src/locales/ru-RU/gpuservice.ts | 9 +- src/locales/tr-TR/gpuservice.ts | 9 +- src/locales/zh-CN/gpuservice.ts | 6 +- .../instances/components/add-modal.tsx | 29 ++-- .../gpu-service/instances/config/types.ts | 1 + .../instances/forms/template-selector.tsx | 2 +- .../templates/components/template-card.tsx | 88 ++++++++++- .../gpu-service/templates/config/types.ts | 2 + .../gpu-service/templates/forms/basic.tsx | 19 +++ .../gpu-service/templates/forms/index.tsx | 4 +- .../gpu-service/templates/forms/ports.tsx | 139 +++++++++++++++++- 18 files changed, 292 insertions(+), 37 deletions(-) create mode 100644 src/assets/logo/jupyter.png create mode 100644 src/assets/logo/pytorch_black.png create mode 100644 src/assets/logo/tensorflow.svg create mode 100644 src/assets/logo/ubuntu.png diff --git a/src/assets/logo/jupyter.png b/src/assets/logo/jupyter.png new file mode 100644 index 0000000000000000000000000000000000000000..e3297c1fda336f33932277ed2673b620f81d62f6 GIT binary patch literal 21006 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 literal 0 HcmV?d00001 diff --git a/src/assets/logo/tensorflow.svg b/src/assets/logo/tensorflow.svg new file mode 100644 index 00000000..c0778626 --- /dev/null +++ b/src/assets/logo/tensorflow.svg @@ -0,0 +1 @@ + diff --git a/src/assets/logo/ubuntu.png b/src/assets/logo/ubuntu.png new file mode 100644 index 0000000000000000000000000000000000000000..22875fcbb4322c602dbe3de6390e5fc8289c1ca8 GIT binary patch literal 14493 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
    Ru^2L_AqXWaiYo zgG+}efiy^Bkbkgab|uE9oHuH(9uyC{Upi0EG(9-JQIENiLM990p<@EaLK6=1kS1_C zTk2T9f~8$rz&sN$HW^yX+uoF6WeG`LAwe&A2gtKuF8v*1=6)_&^cPJMiIDdcYJ+ce9dYiNWIY<8ER-2^m74>6y#pF<9Sii$sTN=&6;m-lY zD@hJ57f7=exIddx8%tqgmKd8z6s=RaDrZ-0fILdJrL!iDvCOf@`~4%5-PZ1GDs-}| zG5ybW4o=`6wNlA*`$aqBK(SfYZ?U-a0jdd$QlNi8mfBd`*WZpT-Nv_PA3xWVhv7fJ zc+W)m5>q$Ozt0L3U#7s#gX<}k=5b?77juFm7ul(;L>q(p+dd89#Ma&e*6>Oe$qiT6@05$7jvoi7kN zbi;Rfm#D{oaTL%84~ly~cNo>dBwHDT-Bi;GCj%+qv@(kPY&EhD+@)f;6s+TTYv7(V z3|o9}75zoEHymsCl7P_-Ofq~+N&4Q6>n-?-Ro0QA6<5oVt}m%`n%lKp1uF&L zK_ZhmXRhd+QpGd#Uq0;`h#f)+`_VVz=F_|QgSRG8f>+p)?DN)Bj=eE1nbR6X1u3ps z!=B5=Z!9IRoSc=Z}2#=&xp@iCR|&@JrU2(e%*~> zSoC)1Tbt3N2P0`lJ(A|*+R@0= zSGQ_gLTaz%g}KOS)~N>X9$k;$-SN@R$rvIbop?2PSGGBCn{4QdywgkFFsXvAOnz0?L>h_+F8Ji(;cY-VuXo4K&sHZpvdC&jMS)U z?tDL)PyhfiL%AEKT@>Sm7zNkY?3ijSKJ_L-cIw`xuH)_z;;_CuamQWjm{KJByL+JT z3hwh{!MqQaFuRcgyQgJa6ds%;9CXS-L-?2;4fEQ2n`X5^b{9cbigu5b2F8vfIzcuu z9dJ{f;v%(z+{*}Rm7E0uI%1}GtPhh`%XXm?TS?JiVx<|Z`fKC7!>i{8)YcJVFxv3)~SKWIQ~@_9d|H?+eB)0dQJrQL`0W0N-@-0RXrxf0NDdS$ zim38iddY`nEzAV&>Oi6G)m!tm_Xqi<^M-tD^!iA3ZMK4kr=w2bBzV^vLCje71>Q@4 zfYvc#ujmIR_U?{oM5_nXorS;=0aJIbb0it6=c)hRA|;a7utb;3@9KPM0szlexyv>5 z*qMmtLSAk%ub5>%yFF8_S4)FM4;&zL9L4#uBYk`mcGp=SP&92UZ;nV$w1B_;u*vhG z`34;bi-Ou$4VvMFwzkh-!;^MOH~rAfdms4xX=KuPUU0tVzRmHgy-T@vdn%5lvR>=i zNiwyF>^PXE!8S5dxBM&jWC~?9Sg$8bp}knu@Y}uX5uNk*3K68?-U<^5tt&GGL{;}F z_~g-F!>I{+UYwXQ{spnDoD!TEn|TUfJE4df5`93oSN~C0jc{rMl(EWlx7TXp`1UPH z)smwJ5nh^E<{_+To;nmlDK%ZXkZJ_;B&ONiNzg`@^M~i$uh(jvKgVCcW#W`MIHmdV zQATtb5Cjw&8P=WW<|CYk_O-5i|ICuyRUaf}3Xf|*k(;I!H}qwkWBIEW>-J@`%t^=R zx1XQsFE<{vBDale;;43(iwMt9PB?I?Q8qn3YK`vlz8c~AZ%`Qy`};TkAf1zXwJ{qY zoG;71+Oy>koRe^#+=YJSE7LSjjkLd~6~{kVpuj7WzA;CKTGsuM#od8caPIRY{!Ni#`OpX)<1~U#l#B_)Z zCEOkwO%+g*8MQG}-DEXKnfV0H7}yBIwE?A+Kqoh%;ATKWsZ+SnVyY7MVdsx5w+NFA z>8M)RRCVigb!VgR<`2e9_tX4*`(vx#O~W3oUtbLZY{R}d3zAl5jT8VKm4MO$TJ9sC zYK|3;<4^zE?Q%Fl9DYvx0BYxZ%|FBEo*$l ze@mm1lJk(X*|H?Dq#2=r5a^w&BZM!ohj+U#Wq#ug3#}Hi!Q>IBSsJ@>DZbYQNQyDj zk%p*VKIrT+vr;oT4ZnvbNySbp;IT3@3UrV<8v~NROlBy`_TUObGtMhF7k=&3a9BZy1LSb^^hq$E-t0 zC;91tEF%AcRHxlZ`%lm5>c6h*A21>Q-cS2soj)xF?aReojrh7#vG6$XG+?ezyspW& zn;`JC*Gd>OyR3OYPt@Atu{hWzp6hbf-Q=F*Yq4dr7%h%Be=)hDnD_OCN9)lpVdZ5i zqie61e`pGe*Ec+fqXGKdrZSuF`AvGR!btB|mkzCed)toPjUu6K(TA!< zyxn@<&bBV>;2TP4hkC3e@W|UI)jSVey_*y7JbO;%8@{kGMA?l5TQD-RLZ?UT*@}8w zbF?eJ^BRKhID9x`M@fdACLEyhjQ9y3|Lw~pa{I>El!e3S+4v3F6^P2 zrcCBOO;#&o-E-#Y1BdU1l!22+pPQ@0*@#4%K{IKOt8`w}))A~ZD}Au=$fMT-0iT8u zaaQxpuvi8er2xE?Qp}Wk;EoF{*!ZxM$PN^Yj?dj4PnU2pvBsI9xivOdi1Kfj;wQPA zNoZXxbx$+D#+GU=7eyZ+UR5K7P_97GCTRU|bf&DGc>EnbjC|=GRKRaS1#z~5hm2W0 z77Iw6ZY<&7E5v=k5=_<f%GFly*Y|Ip=Fez|4kvN%i2_3}2_m~{j2;L+EcBFP z8;M&FDL#35zDJkK2nw4HtFUO#pm_m>tgij{M=WGIr-V(Ye##GRZ>2G&%rZ-DKH-`3 z2Hl@!-~=}Pktia{375`30^AL@y4Y1LpD8C76W5FjJ-sBKH4OTP2OqBytP|?lY+e$K zR*aa}P(ST*SiN(PdvN&NNtZZq;aG1Jp1!$6DBUO}`kqADo)#LdpN6(3z_uTk)Hqrw zqZJ~4_$~v6H6ojB;MIp`y4&3)0jiyll3pdB(GL4k*tv96>iBb0{KcGEXsyCqtAzf{ zYvUZM*B2(=SL9^NDNK2@VsLrRaD8Vx#X8^j{;#{>HfS8oKSS|6%3`g_dN?xEV4J+UY}5l`n8bZy=Ak0;5PW-hwHe_7fA2`F9A4JF4xsP89F&A`^ z1Ccl|!UPruNLClhM+9W7=ro0}uUQp(w$+Sxn8XhlZZnO})YT8t?Cj+)+Uy8$ZNh_wW)lEQxej?^z7g(+3NzF z52J>_IpvWO4fJFo4ulO!TRKs1AXvxCN=!8i%IIT<+=%v2Fxr+YP~-JRb?Vo`hkzYl zTNIf96wy!uGvTrq$X#(nDAq^g;0U{#MzoE&0~X0>KeX6Qcq*k(*VyWR`sftppnWDh z!xhBlEy^%@MY6jMKtfB>y~jg83}EMS0Ax0zd+Xw=*zk>22SuvxZ6c!r`GmQ^@Z*B; zr7JkW#1XmYtf0UpS_E&~2MqmcRH0}gh@;a4M*qAsP(r_i%l;mLhEqgqhU}&k@06Q{ zbOaWYvU~q{%mJvgV7~_tX*R0Ezd}TXqQblvYK3kUGFdy(Dx#$Y7lF&nuFLiZw7+3n z0PzXvqyw!fqnZS;&6k3ci$ZKF(of;iP=ABTO#sgH z{YS64gl1&jRpx3t78E*@OvFT>A}zc(MRDoFIP18;N`q!HOvQU#L5w{ENiL;@pUdpX zLIHRXh4-`&T4ma3gUmu`?o^Nf+#p75VRt(Zr(gU;v))8k%RI2)y5U2J1F(|PuisG; zk)#vgIz;Xecxg69{(VD$5iVvPJvIt1Z2RhGs{XE#8K|~T z9|hH-Wuw`OH4Hfpi$fKyA7!A3>fF>^3^o@lI-EvgV-&34CsNx_C`c+#s9gcd<6Uj? zG_%y5{!5nfV1MAnH&|W5Td1FwSglewf=f9oj#p!Tmr2JREOlUw3AeAuH{@q{GScB z8>i^bM75saDMB;r+3>$Tg8*77Z9e15erN$VAOPUTUKo}r-gWQM(1kGL|`asp3HNuj`*KB-jI&Mn}cqu312E*a>uFX zb~-@aH(5=KYYFD}M8R^#1c7z+$!JhHEWB~E!p(J0Km%g+e&OIp1j-R}4JN;Sjzr>< zmpGH9XB{OuJ`c4%P3IT>%pbq114XiJA(M%l*Sq+KFB)$DsU0xF5$x%(ct??mNq37C z1-t)?JiNN<82?$?P|l{2);fu;!MI(nN8_C~DcFIRO$eGBQ-_h~ z!y$QmA&0{NW{6}{E&Y<;%d72}m5|W|PY-us;-*Drey;zFM@h58&_96Qr$I$x#?9;Z zZ?`=Ga4z$s5aceXWCz+VLpS3M!*|&}ZfN)8{{XkkfGjT&eq(RrH-SoOE){A`m8(fn zU?))<_<@q#*7gPyXe2lj`K!~m`0>7=None=m=HIJ_^ZA? zAv1wyn$piJdwNV(92;>=s{Sz+r`ekCMI?Th!(lGF-(#6*#C0 zk~5iMoe6Lh7@i8_3M`%R{x){Pej literal 0 HcmV?d00001 diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index 8f76a8b0..6e64d2dd 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -358,7 +358,7 @@ export default (props: any) => { config={{ apiBaseUrl: GPUSTACK_API_BASE_URL, 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, defaultColorPrimary: COLOR_PRIMARY }} diff --git a/src/locales/en-US/gpuservice.ts b/src/locales/en-US/gpuservice.ts index a15a7ff5..ec166546 100644 --- a/src/locales/en-US/gpuservice.ts +++ b/src/locales/en-US/gpuservice.ts @@ -15,10 +15,16 @@ export default { 'gpuservice.template.mountPath': 'Mount Path', 'gpuservice.template.containerDisk': 'Container Disk (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.add': 'Add Port', 'gpuservice.template.ports.invalid': '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.add': 'Add Environment Variable', 'gpuservice.template.env.invalid': @@ -53,8 +59,7 @@ export default { 'gpuservice.instance.sliced': 'Sliced', 'gpuservice.instance.memory': 'Memory', 'gpuservice.instance.ram': 'RAM', - 'gpuservice.instance.search.type.placeholder': - 'Search by name, VRAM, memory or vCPU', + 'gpuservice.instance.search.type.placeholder': 'Search by name', 'gpuservice.instance.search.template.placeholder': 'Search by template name, image or mount path', 'gpuservice.instance.template.image': 'Image', diff --git a/src/locales/ja-JP/gpuservice.ts b/src/locales/ja-JP/gpuservice.ts index ed59c68e..645372b9 100644 --- a/src/locales/ja-JP/gpuservice.ts +++ b/src/locales/ja-JP/gpuservice.ts @@ -15,9 +15,15 @@ export default { 'gpuservice.template.mountPath': 'マウントパス', 'gpuservice.template.containerDisk': 'コンテナディスク (GB)', 'gpuservice.template.memory': 'メモリ (GB)', + 'gpuservice.template.displayName': '表示名', + 'gpuservice.template.displayName.max': + '表示名は 63 文字以内で入力してください。', 'gpuservice.template.ports': 'ポート', 'gpuservice.template.ports.add': 'ポートを追加', 'gpuservice.template.ports.invalid': 'ポート設定を完成させてください。', + 'gpuservice.template.ports.name': '名前', + 'gpuservice.template.ports.name.max': + 'ポート名は 16 文字以内で入力してください。', 'gpuservice.template.env': '環境変数', 'gpuservice.template.env.add': '環境変数を追加', 'gpuservice.template.env.invalid': '環境変数を完成させてください。', @@ -51,8 +57,7 @@ export default { 'gpuservice.instance.sliced': '分割', 'gpuservice.instance.memory': 'Memory', 'gpuservice.instance.ram': 'RAM', - 'gpuservice.instance.search.type.placeholder': - '名前、VRAM、メモリまたは vCPU で検索', + 'gpuservice.instance.search.type.placeholder': '名前で検索', 'gpuservice.instance.search.template.placeholder': 'テンプレート名、イメージまたはマウントパスで検索', 'gpuservice.instance.template.image': 'イメージ', diff --git a/src/locales/ru-RU/gpuservice.ts b/src/locales/ru-RU/gpuservice.ts index 06fb38c0..a5efd9c1 100644 --- a/src/locales/ru-RU/gpuservice.ts +++ b/src/locales/ru-RU/gpuservice.ts @@ -16,9 +16,15 @@ export default { 'gpuservice.template.mountPath': 'Путь монтирования', 'gpuservice.template.containerDisk': 'Диск контейнера (GB)', 'gpuservice.template.memory': 'Память (GB)', + 'gpuservice.template.displayName': 'Отображаемое имя', + 'gpuservice.template.displayName.max': + 'Отображаемое имя не должно превышать 63 символа.', 'gpuservice.template.ports': 'Порты', 'gpuservice.template.ports.add': 'Добавить порт', 'gpuservice.template.ports.invalid': 'Заполните настройки портов полностью.', + 'gpuservice.template.ports.name': 'Имя', + 'gpuservice.template.ports.name.max': + 'Имя порта не должно превышать 16 символов.', 'gpuservice.template.env': 'Переменные окружения', 'gpuservice.template.env.add': 'Добавить переменную окружения', 'gpuservice.template.env.invalid': @@ -53,8 +59,7 @@ export default { 'gpuservice.instance.sliced': 'Разделено', 'gpuservice.instance.memory': 'Память', 'gpuservice.instance.ram': 'RAM', - 'gpuservice.instance.search.type.placeholder': - 'Поиск по имени, VRAM, памяти или vCPU', + 'gpuservice.instance.search.type.placeholder': 'Поиск по имени', 'gpuservice.instance.search.template.placeholder': 'Поиск по имени шаблона, образу или пути монтирования', 'gpuservice.instance.template.image': 'Образ', diff --git a/src/locales/tr-TR/gpuservice.ts b/src/locales/tr-TR/gpuservice.ts index b049fde1..7c05331a 100644 --- a/src/locales/tr-TR/gpuservice.ts +++ b/src/locales/tr-TR/gpuservice.ts @@ -15,10 +15,16 @@ export default { 'gpuservice.template.mountPath': 'Bağlama Yolu', 'gpuservice.template.containerDisk': 'Konteyner Diski (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.add': 'Bağlantı Noktası Ekle', 'gpuservice.template.ports.invalid': '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.add': 'Ortam Değişkeni Ekle', 'gpuservice.template.env.invalid': 'Ortam değişkenlerini eksiksiz doldurun.', @@ -52,8 +58,7 @@ export default { 'gpuservice.instance.sliced': 'Bölünmüş', 'gpuservice.instance.memory': 'Bellek', 'gpuservice.instance.ram': 'RAM', - 'gpuservice.instance.search.type.placeholder': - "Ada, VRAM, belleğe veya vCPU'ya göre ara", + 'gpuservice.instance.search.type.placeholder': 'Ada göre ara', 'gpuservice.instance.search.template.placeholder': 'Şablon adına, imaja veya bağlama yoluna göre ara', 'gpuservice.instance.template.image': 'İmaj', diff --git a/src/locales/zh-CN/gpuservice.ts b/src/locales/zh-CN/gpuservice.ts index 1fbd2d6c..7cb77080 100644 --- a/src/locales/zh-CN/gpuservice.ts +++ b/src/locales/zh-CN/gpuservice.ts @@ -15,9 +15,13 @@ export default { 'gpuservice.template.mountPath': '挂载路径', 'gpuservice.template.containerDisk': '容器磁盘(GB)', 'gpuservice.template.memory': '内存(GB)', + 'gpuservice.template.displayName': '显示名称', + 'gpuservice.template.displayName.max': '显示名称不能超过 63 个字符', 'gpuservice.template.ports': '端口', 'gpuservice.template.ports.add': '添加端口', 'gpuservice.template.ports.invalid': '请填写完整的端口配置', + 'gpuservice.template.ports.name': '名称', + 'gpuservice.template.ports.name.max': '端口名称不能超过 16 个字符', 'gpuservice.template.env': '环境变量', 'gpuservice.template.env.add': '添加环境变量', 'gpuservice.template.env.invalid': '请填写完整的环境变量', @@ -50,7 +54,7 @@ export default { 'gpuservice.instance.sliced': '切分', 'gpuservice.instance.memory': '显存', 'gpuservice.instance.ram': '内存', - 'gpuservice.instance.search.type.placeholder': '搜索名称、显存、内存或 vCPU', + 'gpuservice.instance.search.type.placeholder': '搜索名称', 'gpuservice.instance.search.template.placeholder': '搜索模板名称、镜像或挂载路径', 'gpuservice.instance.template.image': '镜像', diff --git a/src/pages/gpu-service/instances/components/add-modal.tsx b/src/pages/gpu-service/instances/components/add-modal.tsx index 888f6d1a..f9c0de6a 100644 --- a/src/pages/gpu-service/instances/components/add-modal.tsx +++ b/src/pages/gpu-service/instances/components/add-modal.tsx @@ -176,16 +176,7 @@ const AddModal: React.FC = ({ // filter instance types const filteredInstanceTypes = instanceTypeList.filter((item) => - matchKeyword( - [ - item.metadata?.name, - item.spec?.memory, - item.status?.cpu?.capacity, - item.status?.ram?.capacity, - item.status?.accelerator?.remaining - ], - instanceKeyword - ) + matchKeyword([item.metadata?.name], instanceKeyword) ); // filter templates based on selection and keyword @@ -256,17 +247,20 @@ const AddModal: React.FC = ({
    - - {intl.formatMessage({ id: 'gpuservice.instance.types' })} -
    + + {intl.formatMessage({ id: 'gpuservice.instance.types' })} + } @@ -290,17 +284,20 @@ const AddModal: React.FC = ({
    - - {intl.formatMessage({ id: 'gpuservice.instance.templates' })} -
    + + {intl.formatMessage({ id: 'gpuservice.instance.templates' })} + } diff --git a/src/pages/gpu-service/instances/config/types.ts b/src/pages/gpu-service/instances/config/types.ts index 94ecd3c2..f6423362 100644 --- a/src/pages/gpu-service/instances/config/types.ts +++ b/src/pages/gpu-service/instances/config/types.ts @@ -15,6 +15,7 @@ export interface FormData { ports: { port: number; protocol?: string; + name?: string; }[]; env: { name: string; diff --git a/src/pages/gpu-service/instances/forms/template-selector.tsx b/src/pages/gpu-service/instances/forms/template-selector.tsx index 704cbcb8..72380c07 100644 --- a/src/pages/gpu-service/instances/forms/template-selector.tsx +++ b/src/pages/gpu-service/instances/forms/template-selector.tsx @@ -68,7 +68,7 @@ const TemplateSelector: React.FC = ({
    - {item.name} + {item.displayName || item.name || '-'}
    diff --git a/src/pages/gpu-service/templates/components/template-card.tsx b/src/pages/gpu-service/templates/components/template-card.tsx index 32da7994..96041476 100644 --- a/src/pages/gpu-service/templates/components/template-card.tsx +++ b/src/pages/gpu-service/templates/components/template-card.tsx @@ -2,17 +2,27 @@ import ascendLogo from '@/assets/logo/ascend.png'; import CambriconPNG from '@/assets/logo/cambricon.png'; import hyponPNG from '@/assets/logo/hygon.png'; import iluvatarWEBP from '@/assets/logo/Iluvatar.png'; +import jupyterLogo from '@/assets/logo/jupyter.png'; import metaxLogo from '@/assets/logo/metax.png'; import mooreLogo from '@/assets/logo/moore-logo.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 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 { AutoTooltip, DropdownActions, IconFont, - TemplateCard + TemplateCard, + ThemeTag } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Button, Tag } from 'antd'; @@ -20,6 +30,56 @@ import styled from 'styled-components'; import { templateActions } from '../config'; 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 = { + nvidia: 'green', + amd: 'volcano', + ascend: 'orange', + hygon: 'magenta', + moorthreads: 'cyan', + iluvatar: 'purple', + metax: 'geekblue', + cambricon: 'gold', + thead: 'red', + cpu: 'blue' +}; + +const manufacturerLabelMap: Record = Object.values( + GPUsConfigs +).reduce( + (acc, item) => { + if (item.gpuVendor) acc[item.gpuVendor] = item.label; + return acc; + }, + { cpu: 'CPU' } as Record +); + +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).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)` &:hover { .operations { @@ -38,6 +98,8 @@ const Header = styled.div` .title { display: flex; align-items: center; + gap: 8px; + min-width: 0; } `; @@ -73,7 +135,7 @@ const InfoItem = styled.div` color: var(--ant-color-text-quaternary); } .value { - color: var(--ant-color-text-secondary); + color: var(--ant-color-text-tertiary); } `; @@ -98,6 +160,10 @@ const TemplateCardItem: React.FC = ({ data, onSelect }) => { }; const renderLogo = () => { + const imageLogo = matchImageLogo(data.spec?.image); + if (imageLogo) { + return ; + } switch (data.manufacturer) { case manfacturerValueMap.NVIDIA: return ; @@ -141,6 +207,19 @@ const TemplateCardItem: React.FC = ({ data, onSelect }) => { } }; + const renderManufacturerTag = () => { + if (!data.manufacturer) return null; + const label = + manufacturerLabelMap[data.manufacturer] ?? + data.manufacturer.toUpperCase(); + const color = manufactureColorMap[data.manufacturer] ?? 'purple'; + return ( + + {label} + + ); + }; + const renderActions = () => { return ( @@ -177,8 +256,9 @@ const TemplateCardItem: React.FC = ({ data, onSelect }) => { - {data.name || '-'} + {data.displayName || data.name || '-'} + {renderManufacturerTag()} diff --git a/src/pages/gpu-service/templates/config/types.ts b/src/pages/gpu-service/templates/config/types.ts index db261d35..528ba03d 100644 --- a/src/pages/gpu-service/templates/config/types.ts +++ b/src/pages/gpu-service/templates/config/types.ts @@ -1,6 +1,7 @@ export interface PortItem { protocol: 'UDP' | 'TCP'; port: number; + name?: string; } export type ImagePullPolicy = 'Always' | 'IfNotPresent' | 'Never'; @@ -13,6 +14,7 @@ export interface EnvItem { export interface FormData { name: string; description: string; + displayName?: string; manufacturer: string; spec: { image: string; diff --git a/src/pages/gpu-service/templates/forms/basic.tsx b/src/pages/gpu-service/templates/forms/basic.tsx index e2cf4759..1174533b 100644 --- a/src/pages/gpu-service/templates/forms/basic.tsx +++ b/src/pages/gpu-service/templates/forms/basic.tsx @@ -76,6 +76,25 @@ const Basic: React.FC = ({ page = 'template', onceMaxRequest }) => { required /> + + name="displayName" + rules={[ + { + max: 63, + message: intl.formatMessage({ + id: 'gpuservice.template.displayName.max' + }) + } + ]} + > + + name="manufacturer" rules={[ diff --git a/src/pages/gpu-service/templates/forms/index.tsx b/src/pages/gpu-service/templates/forms/index.tsx index 09b2246a..e9eb359f 100644 --- a/src/pages/gpu-service/templates/forms/index.tsx +++ b/src/pages/gpu-service/templates/forms/index.tsx @@ -36,6 +36,7 @@ const GPUServiceTemplateForm: React.FC = forwardRef( const handleFinish = async (values: FormData) => { await onFinish({ ...values, + displayName: values.displayName?.trim() || values.name, spec: { ...values.spec, command: values.spec?.command?.filter(Boolean) ?? [] @@ -65,7 +66,8 @@ const GPUServiceTemplateForm: React.FC = forwardRef( ports: [ { protocol: 'TCP', - port: 22 + port: 22, + name: 'SSH' } ], volumeMount: '/workspace', diff --git a/src/pages/gpu-service/templates/forms/ports.tsx b/src/pages/gpu-service/templates/forms/ports.tsx index 31d9514a..458d1368 100644 --- a/src/pages/gpu-service/templates/forms/ports.tsx +++ b/src/pages/gpu-service/templates/forms/ports.tsx @@ -1,14 +1,18 @@ import { + Input as CInput, InputNumber as CInputNumber, MetadataList, Select as SealSelect } from '@gpustack/core-ui'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; +import { useEffect, useRef, useState } from 'react'; import { FormData, PortItem as PortItemType } from '../config/types'; type PortProtocol = PortItemType['protocol']; +const PORT_NAME_MAX = 16; + const protocolOptions = [ { label: 'UDP', @@ -20,43 +24,154 @@ const protocolOptions = [ } ]; +const wellKnownTcpPortNames: Record = { + 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 { item: PortItemType; index: number; onChange: (item: PortItemType) => void; } +interface PortNameInputProps { + value: string | undefined; + onChange: (value: string) => void; + placeholder: string; +} + +const PortNameInput: React.FC = ({ + value, + onChange, + placeholder +}) => { + const [localValue, setLocalValue] = useState(value ?? ''); + const isComposingRef = useRef(false); + + useEffect(() => { + if (!isComposingRef.current) { + setLocalValue(value ?? ''); + } + }, [value]); + + return ( + { + 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 = ({ item, index, onChange }) => { + const intl = useIntl(); return (
    -
    +
    { + const nextProtocol = value as PortProtocol; onChange({ ...item, - protocol: value as PortProtocol + protocol: nextProtocol, + name: getAutoFilledName( + nextProtocol, + item.port, + item.protocol, + item.port, + item.name + ) }); }} style={{ width: '100%' }} >
    -
    +
    { + const nextPort = + typeof value === 'number' ? value : (undefined as any); onChange({ ...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%' }} />
    +
    + { + onChange({ + ...item, + name: next + }); + }} + /> +
    ); }; @@ -75,7 +190,8 @@ const Ports: React.FC = () => { ...ports, { 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(); } }