diff --git a/web_src/js/features/cloudrbanin.js b/web_src/js/features/cloudrbanin.js
index 5a95dfe6c..d0c58bf3f 100644
--- a/web_src/js/features/cloudrbanin.js
+++ b/web_src/js/features/cloudrbanin.js
@@ -398,7 +398,7 @@ export default async function initCloudrain() {
$(`#${jobName}`).popup("toggle");
} else {
let versionData = data.filter((item) => {
- return item.Version === versionName;
+ return item.version === versionName;
});
if (versionData.length == 0) {
$(`#${jobName}`).popup("toggle");
diff --git a/web_src/js/features/i18nVue.js b/web_src/js/features/i18nVue.js
index f6d5a6411..2983b3dc1 100644
--- a/web_src/js/features/i18nVue.js
+++ b/web_src/js/features/i18nVue.js
@@ -105,6 +105,9 @@ export const i18nVue = {
file_sync_fail:"文件同步失败",
no_file_to_download:"没有文件可以下载",
task_not_finished:"任务还未结束,稍后再来看看",
+ local:"本地",
+ online:"线上",
+ modify:"修改",
},
US: {
computer_vision: "computer vision",
@@ -216,5 +219,8 @@ export const i18nVue = {
file_sync_fail:"File synchronization failed",
no_file_to_download:"No files can be downloaded",
task_not_finished:"Task not finished yet, please wait",
+ local:"Local",
+ online:"Online",
+ modify:"Modify",
},
};
diff --git a/web_src/js/index.js b/web_src/js/index.js
index bc3de08b5..001644d92 100755
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -6,8 +6,10 @@ import "./publicpath.js";
import "./polyfills.js";
import "./features/letteravatar.js";
import Vue from "vue";
-import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";
+import localeEn from 'element-ui/lib/locale/lang/en';
+import localeZh from 'element-ui/lib/locale/lang/zh-CN';
+import ElementUI from "element-ui";
import axios from "axios";
import qs from "qs";
import Cookies from "js-cookie";
@@ -56,15 +58,19 @@ import './features/ad.js';
import { Fancybox } from "./vendor/fancybox.esm.js";
-Vue.use(ElementUI);
Vue.prototype.$axios = axios;
Vue.prototype.$Cookies = Cookies;
Vue.prototype.qs = qs;
Vue.prototype.$message = Message;
Vue.prototype.$locale = i18nVue;
-window.i18n = i18nVue[document.querySelector('html').getAttribute('lang') == 'zh-CN' ? 'CN' : 'US'];
+const lang = document.querySelector('html').getAttribute('lang');
+window.i18n = i18nVue[lang == 'zh-CN' ? 'CN' : 'US'];
const { AppSubUrl, StaticUrlPrefix, csrf } = window.config;
+Vue.use(ElementUI, {
+ locale: lang === 'zh-CN' ? localeZh : localeEn,
+});
+
Object.defineProperty(Vue.prototype, "$echarts", {
value: echarts,
});
@@ -5071,12 +5077,7 @@ function initcreateRepo() {
initcreateRepo();
function initChartsNpu() {
- const url = window.location.href;
- const urlArr = url.split("/");
- let userName = urlArr.slice(-5)[0];
- let repoPath = urlArr.slice(-4)[0];
- let jobID = urlArr.slice(-1)[0];
-
+ const repoPath = $('.metric_chart').data('path')
let options = {
legend: {
data: [],
@@ -5127,7 +5128,7 @@ function initChartsNpu() {
document.getElementById(`metric-${versionName}`)
);
$.get(
- `${window.config.AppSubUrl}/api/v1/repos/${userName}/${repoPath}/modelarts/train-job/${jobID}/metric_statistics?version_name=${versionName}&statistic_type=each&metrics=`,
+ `${window.config.AppSubUrl}/api/v1/repos/${repoPath}`,
(res) => {
let filterDta = res.MetricsInfo.filter((item) => {
return ![
diff --git a/web_src/vuepages/apis/modules/modelmanage.js b/web_src/vuepages/apis/modules/modelmanage.js
new file mode 100644
index 000000000..7124dcfc9
--- /dev/null
+++ b/web_src/vuepages/apis/modules/modelmanage.js
@@ -0,0 +1,106 @@
+import service from "../service";
+import Qs from 'qs';
+
+// 保存本地模型
+export const saveLocalModel = (data) => {
+ return service({
+ url: `${data.repo}/modelmanage/create_local_model`,
+ method: 'post',
+ headers: { 'Content-type': 'application/x-www-form-urlencoded' },
+ params: {},
+ data: Qs.stringify(data),
+ });
+};
+
+// 修改模型
+// data: {id,type,name,version,engine,label,description:}
+export const modifyModel = (data) => {
+ return service({
+ url: `${data.repo}/modelmanage/modify_model`,
+ method: 'put',
+ headers: { 'Content-type': 'application/x-www-form-urlencoded' },
+ params: {},
+ data: Qs.stringify(data),
+ });
+};
+
+// 求模型信息
+export const getModelInfoByName = (params) => {
+ return service({
+ url: `${params.repo}/modelmanage/show_model_info_api`,
+ method: 'get',
+ params,
+ data: {},
+ });
+};
+
+// 求模型中文件列表
+// params {repo, ID, parentDir}
+export const getModelFiles = (params) => {
+ return service({
+ url: `${params.repo}/modelmanage/query_onelevel_modelfile`,
+ method: 'get',
+ params,
+ data: {},
+ });
+};
+
+// 删除模型文件
+// params {repo, id, fileName}
+export const deleteModelFile = (params) => {
+ return service({
+ url: `${params.repo}/modelmanage/delete_model_file`,
+ method: 'delete',
+ params,
+ data: {},
+ });
+};
+
+/* 文件上传相关 */
+// 上传文件1: 获取文件chunks信息
+// params: { md5, type: 0-CPU/GPU,1-NPU, file_name, scene: 'model', modeluuid }
+// return: uploadID, uuid, uploaded, chunks, attachID, modeluuid, modelName, fileName
+export const getChunks = (params) => {
+ return service({
+ url: `/attachments/model/get_chunks`,
+ method: 'get',
+ params,
+ data: {},
+ });
+};
+
+// 上传文件2: 上传新文件
+// params: { totalChunkCounts, md5, size, fileType, type, file_name, scene=model, modeluuid=xxxx }
+// return: uploadID, uuid
+export const getNewMultipart = (params) => {
+ return service({
+ url: `/attachments/model/new_multipart`,
+ method: 'get',
+ params,
+ data: {},
+ });
+};
+
+// 上传文件3: 获取分片上传地址
+// params: { uuid, uploadID, size, chunkNumber, type, file_name, scene=model }
+// return: url
+export const getMultipartUrl = (params) => {
+ return service({
+ url: `/attachments/model/get_multipart_url`,
+ method: 'get',
+ params,
+ data: {},
+ });
+};
+
+// 上传文件4: 完成上传后
+// data: { uuid, uploadID, size, type, file_name, dataset_id, description, scene=model, modeluuid=xxxx }
+export const setCompleteMultipart = (data) => {
+ return service({
+ url: `/attachments/model/complete_multipart`,
+ method: 'post',
+ headers: { 'Content-type': 'application/x-www-form-urlencoded' },
+ params: {},
+ data: Qs.stringify(data),
+ });
+};
diff --git a/web_src/vuepages/const/index.js b/web_src/vuepages/const/index.js
index 4051dbb17..7ca6326f0 100644
--- a/web_src/vuepages/const/index.js
+++ b/web_src/vuepages/const/index.js
@@ -14,3 +14,6 @@ export const AI_CENTER = [{ k: 'OpenIOne', v: i18n.t('resourcesManagement.OpenIO
export const COMPUTER_RESOURCES = [{ k: 'GPU', v: 'GPU' }, { k: 'NPU', v: 'NPU' }, { k: 'MLU', v: 'MLU' }];
export const ACC_CARD_TYPE = [{ k: 'T4', v: 'T4' }, { k: 'A100', v: 'A100' }, { k: 'V100', v: 'V100' }, { k: 'ASCEND910', v: 'Ascend 910' }, { k: 'MLU270', v: 'MLU270' }, { k: 'RTX3080', v: 'RTX3080' }];
export const SPECIFICATION_STATUS = [{ k: '1', v: i18n.t('resourcesManagement.willOnShelf') }, { k: '2', v: i18n.t('resourcesManagement.onShelf') }, { k: '3', v: i18n.t('resourcesManagement.offShelf') }];
+
+// 模型
+export const MODEL_ENGINES = [{ k: '0', v: 'PyTorch' }, { k: '1', v: 'TensorFlow' }, { k: '2', v: 'MindSpore' }, { k: '4', v: 'PaddlePaddle' }, { k: '5', v: 'OneFlow' }, { k: '6', v: 'MXNet' }, { k: '3', v: 'Other' }];
diff --git a/web_src/vuepages/langs/config/en-US.js b/web_src/vuepages/langs/config/en-US.js
index 6ea201cb2..0d30c6578 100644
--- a/web_src/vuepages/langs/config/en-US.js
+++ b/web_src/vuepages/langs/config/en-US.js
@@ -193,6 +193,67 @@ const en = {
dataDesensitizationModelDesc:'Use AI technology to desensitize the face and license plate number in the picture. For more information about this model, please visit the project',
limitFilesUpload:'Only jpg/jpeg/png files can be uploaded',
limitSizeUpload:'The size of the uploaded file cannot exceed 20M!',
+ modelManage: {
+ modelManage: 'Model management',
+ modelName: 'Model name',
+ useCluster: 'Available clusters',
+ local: 'Local',
+ online: 'Online',
+ createModel: 'Create Model',
+ importLocalModel: 'Import Lacal Model',
+ importOnlineModel: 'Import Online Model',
+ modifyModelInfo: 'Modify model information',
+ addModelFiles: 'Add model files',
+ uploadModelFiles: 'Upload model files',
+ pleaseInputModelName: 'Please input model name',
+ version: 'Version',
+ modelEngine: 'Model engine',
+ modelLabel: 'Model label',
+ modelLabelInputTips: 'Input labels, multiple labels are separated by spaces',
+ modelDescr: 'Model description',
+ modelDescrInputTips: 'The description should not exceed 256 characters',
+ confirm: 'Confirm',
+ cancel: 'Cancel',
+ modelCreateFailed: 'Model create failed',
+ modelModifyFailed: 'Model modify failed',
+ fileUpload: 'File upload',
+ upload: 'Upload',
+ uploadStatus: 'Upload status',
+ modelFileUploadDefaultTips: 'Click to add files or drag files here directly',
+ modelFileUploadErrTips: 'Up to 10 files can be uploaded at a time, and the total file size of the model does not exceed {size}GB',
+ modelFileNameTips: 'The file name should not exceed 128 characters',
+ fileIstoBig: 'File is to big',
+ removeFile: 'Rmove file',
+ uploadSuccess: 'upload success',
+ uploadFailed: 'upload failed',
+ calcFileMd5: 'Calculating file MD5...',
+ uploading: 'Uploading...',
+ fileHasAlreadyInTheModel: 'This file has already in the model: ',
+ basicInfo: 'Basic information',
+ modelSize: 'Model size',
+ descr: 'Description',
+ createTime: 'Create Time',
+ label: 'Label',
+ trainTaskInfo: 'Train task information',
+ trainTask: 'Train task',
+ codeBranch: 'Code branch',
+ bootFile: 'Boot file',
+ trainDataset: 'Train dataset',
+ specInfo: 'Specifications',
+ workServerNumber: 'Amount of compute node',
+ runParameters: 'Run parameters',
+ seeMore: 'View more',
+ collapseDetails: 'Collapse details',
+ modelFilesList: 'Mode files list',
+ fileName: 'File name',
+ fileSize: 'File size',
+ updateTime: 'Upate Time',
+ operate: 'Operation',
+ delete: 'Delete',
+ infoModificationFailed: 'Information modify failed',
+ deleteModelFileConfirmTips: 'Are you sure you want to delete the current model file?',
+ modelFileDeleteFailed: 'Model file delete failed',
+ },
}
export default en;
diff --git a/web_src/vuepages/langs/config/zh-CN.js b/web_src/vuepages/langs/config/zh-CN.js
index 5b8e800d8..88c953faf 100644
--- a/web_src/vuepages/langs/config/zh-CN.js
+++ b/web_src/vuepages/langs/config/zh-CN.js
@@ -193,6 +193,67 @@ const zh = {
dataDesensitizationModelDesc:'利用人工智能AI技术,把图片中的人脸、车牌号码进行脱敏处理。该模型更多信息请访问项目',
limitFilesUpload:'只能上传 jpg/jpeg/png 格式的文件',
limitSizeUpload:'上传文件大小不能超过 20M !',
+ modelManage: {
+ modelManage: '模型管理',
+ modelName: '模型名称',
+ useCluster: '可用集群',
+ local: '本地',
+ online: '线上',
+ createModel: '创建模型',
+ importLocalModel: '导入本地模型',
+ importOnlineModel: '导入线上模型',
+ modifyModelInfo: '修改模型信息',
+ addModelFiles: '增加模型文件',
+ uploadModelFiles: '上传模型文件',
+ pleaseInputModelName: '请输入模型名称',
+ version: '版本',
+ modelEngine: '模型框架',
+ modelLabel: '模型标签',
+ modelLabelInputTips: '输入标签,多个标签用空格区分',
+ modelDescr: '模型描述',
+ modelDescrInputTips: '描述字数不超过255个字符',
+ confirm: '确定',
+ cancel: '取消',
+ modelCreateFailed: '模型创建失败',
+ modelModifyFailed: '模型修改失败',
+ fileUpload: '文件上传',
+ upload: '上传',
+ uploadStatus: '上传状态',
+ modelFileUploadDefaultTips: '点击添加文件或直接拖拽文件到此处',
+ modelFileUploadErrTips: '单次最多上传10个文件,模型总文件大小不超过{size}G',
+ modelFileNameTips: '文件名长度不超过128个字符',
+ fileIstoBig: '文件太大',
+ removeFile: '移除文件',
+ uploadSuccess: '上传成功',
+ uploadFailed: '上传失败',
+ calcFileMd5: '计算文件MD5...',
+ uploading: '上传中...',
+ fileHasAlreadyInTheModel: '该文件已上传在模型:',
+ basicInfo: '基本信息',
+ modelSize: '模型大小',
+ descr: '描述',
+ createTime: '创建时间',
+ label: '标签',
+ trainTaskInfo: '训练相关信息',
+ trainTask: '训练任务',
+ codeBranch: '代码分支',
+ bootFile: '启动文件',
+ trainDataset: '训练数据集',
+ specInfo: '规格',
+ workServerNumber: '计算节点',
+ runParameters: '运行参数',
+ seeMore: '查看更多信息',
+ collapseDetails: '折叠详细信息',
+ modelFilesList: '模型文件列表',
+ fileName: '文件名称',
+ fileSize: '文件大小',
+ updateTime: '更新时间',
+ operate: '操作',
+ delete: '删除',
+ infoModificationFailed: '信息修改失败',
+ deleteModelFileConfirmTips: '请确认是否删除当前模型文件?',
+ modelFileDeleteFailed: '模型文件删除失败',
+ },
}
export default zh;
diff --git a/web_src/vuepages/pages/modelmanage/common/modelmanage-common-detail.vue b/web_src/vuepages/pages/modelmanage/common/modelmanage-common-detail.vue
new file mode 100644
index 000000000..995e2d83f
--- /dev/null
+++ b/web_src/vuepages/pages/modelmanage/common/modelmanage-common-detail.vue
@@ -0,0 +1,706 @@
+
+
+
+
+
+
{{ $t('modelManage.basicInfo') }}:
+
+
+
+
{{ $t('modelManage.useCluster') }}:
+
+
+ {{ state.typeStr }}
+
+
+
+
+
{{ $t('modelManage.modelSize') }}:
+
+
{{ state.modelSize }}
+
+
+
+
{{ $t('modelManage.descr') }}:
+
+
+ {{ state.description }}
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ $t('modelManage.modelEngine') }}:
+
+
{{ state.engineName }}
+
+
+
+
{{ $t('modelManage.createTime') }}:
+
+
{{ state.createTime }}
+
+
+
+
{{ $t('modelManage.label') }}:
+
+
+ {{ state.label }}
+
+
+
+
+
+
+
+
+
+
+
+
{{ $t('modelManage.trainTaskInfo') }}:
+
+
+
+
{{ $t('modelManage.trainTask') }}:
+
+
+
+
{{ $t('modelManage.codeBranch') }}:
+
+
+
+
{{ $t('modelManage.bootFile') }}:
+
+
+
+
{{ $t('modelManage.trainDataset') }}:
+
+
{{ state.datasetName }}
+
+
+
+
+
+
{{ $t('modelManage.specInfo') }}:
+
+
+
+
{{ $t('modelManage.workServerNumber') }}:
+
+
{{ state.workServerNumber }}
+
+
+
+
{{ $t('modelManage.runParameters') }}:
+
+
{{ state.parameters }}
+
+
+
+
+
+
+
+
+
+ {{ isExpanded ? $t('modelManage.collapseDetails') : $t('modelManage.seeMore') }}
+
+
+
+
+
+
+
{{ $t('modelManage.modelFilesList') }}:
+
+
+
+
+ {{ $t('modelManage.uploadModelFiles') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('modelManage.delete')
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web_src/vuepages/pages/modelmanage/common/vp-modelmanage-common-detail.js b/web_src/vuepages/pages/modelmanage/common/vp-modelmanage-common-detail.js
new file mode 100644
index 000000000..13814a369
--- /dev/null
+++ b/web_src/vuepages/pages/modelmanage/common/vp-modelmanage-common-detail.js
@@ -0,0 +1,17 @@
+import Vue from 'vue';
+import ElementUI from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+import localeEn from 'element-ui/lib/locale/lang/en';
+import localeZh from 'element-ui/lib/locale/lang/zh-CN';
+import { i18n, lang } from '~/langs';
+import App from './modelmanage-common-detail.vue';
+
+Vue.use(ElementUI, {
+ locale: lang === 'zh-CN' ? localeZh : localeEn,
+ size: 'small',
+});
+
+new Vue({
+ i18n,
+ render: (h) => h(App),
+}).$mount('#__vue-root');
diff --git a/web_src/vuepages/pages/modelmanage/local/modelmanage-local-create-1.vue b/web_src/vuepages/pages/modelmanage/local/modelmanage-local-create-1.vue
new file mode 100644
index 000000000..fefcf4114
--- /dev/null
+++ b/web_src/vuepages/pages/modelmanage/local/modelmanage-local-create-1.vue
@@ -0,0 +1,470 @@
+
+
+
+
+
+
+
1
+
{{ $t('modelManage.createModel') }}
+
+
+
+
2
+
{{ $t('modelManage.uploadModelFiles') }}
+
+
+
+
+
+
+
+
+
CPU / GPU
+
+
+
+
Ascend NPU
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ type == '1' ? $t('modelManage.confirm') :
+ $t('modelManage.createModel')
+ }}
+
+ {{ $t('modelManage.cancel') }}
+
+
+
+
+
+
+
+
+
+
diff --git a/web_src/vuepages/pages/modelmanage/local/modelmanage-local-create-2.vue b/web_src/vuepages/pages/modelmanage/local/modelmanage-local-create-2.vue
new file mode 100644
index 000000000..209707147
--- /dev/null
+++ b/web_src/vuepages/pages/modelmanage/local/modelmanage-local-create-2.vue
@@ -0,0 +1,851 @@
+
+
+
+
+
+
+
1
+
{{ $t('modelManage.createModel') }}
+
+
+
+
2
+
{{ $t('modelManage.uploadModelFiles') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('modelManage.upload') }}
+
+ {{ $t('modelManage.cancel') }}
+
+
+
+
+
+
+
{{ item.name }}
+
+
+
+
+
+
+
+
+
{{ uploadStatusList[index].status }}
+
+ {{ uploadStatusList[index].failedInfo }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web_src/vuepages/pages/modelmanage/local/vp-modelmanage-local-create-1.js b/web_src/vuepages/pages/modelmanage/local/vp-modelmanage-local-create-1.js
new file mode 100644
index 000000000..c2054c59a
--- /dev/null
+++ b/web_src/vuepages/pages/modelmanage/local/vp-modelmanage-local-create-1.js
@@ -0,0 +1,17 @@
+import Vue from 'vue';
+import ElementUI from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+import localeEn from 'element-ui/lib/locale/lang/en';
+import localeZh from 'element-ui/lib/locale/lang/zh-CN';
+import { i18n, lang } from '~/langs';
+import App from './modelmanage-local-create-1.vue';
+
+Vue.use(ElementUI, {
+ locale: lang === 'zh-CN' ? localeZh : localeEn,
+ size: 'small',
+});
+
+new Vue({
+ i18n,
+ render: (h) => h(App),
+}).$mount('#__vue-root');
diff --git a/web_src/vuepages/pages/modelmanage/local/vp-modelmanage-local-create-2.js b/web_src/vuepages/pages/modelmanage/local/vp-modelmanage-local-create-2.js
new file mode 100644
index 000000000..2cad84464
--- /dev/null
+++ b/web_src/vuepages/pages/modelmanage/local/vp-modelmanage-local-create-2.js
@@ -0,0 +1,17 @@
+import Vue from 'vue';
+import ElementUI from 'element-ui';
+import 'element-ui/lib/theme-chalk/index.css';
+import localeEn from 'element-ui/lib/locale/lang/en';
+import localeZh from 'element-ui/lib/locale/lang/zh-CN';
+import { i18n, lang } from '~/langs';
+import App from './modelmanage-local-create-2.vue';
+
+Vue.use(ElementUI, {
+ locale: lang === 'zh-CN' ? localeZh : localeEn,
+ size: 'small',
+});
+
+new Vue({
+ i18n,
+ render: (h) => h(App),
+}).$mount('#__vue-root');
diff --git a/web_src/vuepages/utils/index.js b/web_src/vuepages/utils/index.js
index 06abb4473..5b9ecc23f 100644
--- a/web_src/vuepages/utils/index.js
+++ b/web_src/vuepages/utils/index.js
@@ -1,7 +1,41 @@
+
+import { i18n } from '~/langs';
+import { ACC_CARD_TYPE } from '~/const';
+
export const getListValueWithKey = (list, key, k = 'k', v = 'v') => {
for (let i = 0, iLen = list.length; i < iLen; i++) {
const listI = list[i];
if (listI[k] === key) return listI[v];
}
- return '';
+ return key;
+};
+
+export const getUrlSearchParams = () => {
+ const params = new URLSearchParams(location.search);
+ const obj = {};
+ params.forEach((value, key) => {
+ obj[key] = value;
+ });
+ return obj;
+};
+
+export const transFileSize = (srcSize) => {
+ if (null == srcSize || srcSize == '') {
+ return '0 Bytes';
+ }
+ const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
+ srcSize = parseFloat(srcSize);
+ const index = Math.floor(Math.log(srcSize) / Math.log(1024));
+ const size = (srcSize / Math.pow(1024, index)).toFixed(2);
+ return size + ' ' + unitArr[index];
+};
+
+export const renderSpecStr = (spec, showPoint) => {
+ if (!spec) return '';
+ var ngpu = `${spec.ComputeResource}: ${spec.AccCardsNum + '*' + getListValueWithKey(ACC_CARD_TYPE, spec.AccCardType)}`;
+ var gpuMemStr = spec.GPUMemGiB != 0 ? `${i18n.t('resourcesManagement.gpuMem')}: ${spec.GPUMemGiB}GB, ` : '';
+ var sharedMemStr = spec.ShareMemGiB != 0 ? `, ${i18n.t('resourcesManagement.shareMem')}: ${spec.ShareMemGiB}GB` : '';
+ var pointStr = showPoint ? `, ${spec.UnitPrice == 0 ? i18n.t('resourcesManagement.free') : spec.UnitPrice + i18n.t('resourcesManagement.point_hr')}` : '';
+ var specStr = `${ngpu}, CPU: ${spec.CpuCores}, ${gpuMemStr}${i18n.t('resourcesManagement.mem')}: ${spec.MemGiB}GB${sharedMemStr}${pointStr}`;
+ return specStr;
};