#4108 资源规格管理优化

Merged
ychao_1983 merged 7 commits from spec-update into V20230517 1 year ago
  1. +11
    -7
      models/resource_scene.go
  2. +28
    -9
      models/resource_specification.go
  3. +2
    -2
      modules/grampus/grampus.go
  4. +11
    -9
      routers/admin/resources.go
  5. +2
    -2
      services/ai_task_service/task/cloudbrain_one_notebook_task.go
  6. +1
    -1
      services/ai_task_service/task/grampus_notebook_task.go
  7. +2
    -2
      services/ai_task_service/task/grampus_train_task.go
  8. +15
    -0
      services/cloudbrain/resource/resource_specification.go
  9. +1
    -0
      web_src/vuepages/apis/modules/resources.js
  10. +5
    -0
      web_src/vuepages/langs/config/en-US.js
  11. +5
    -0
      web_src/vuepages/langs/config/zh-CN.js
  12. +39
    -73
      web_src/vuepages/pages/resources/components/SceneDialog.vue
  13. +548
    -0
      web_src/vuepages/pages/resources/components/SpecSelect.vue
  14. +112
    -59
      web_src/vuepages/pages/resources/scene/index.vue

+ 11
- 7
models/resource_scene.go View File

@@ -50,6 +50,7 @@ type SearchResourceSceneOptions struct {
QueueId int64
ComputeResource string
AccCardType string
Cluster string
}

type ResourceSceneListRes struct {
@@ -70,7 +71,7 @@ type ResourceSceneRes struct {
JobType JobType
IsExclusive bool
ExclusiveOrg string
Specs []ResourceSpecWithSceneId
Specs []ResourceSpecInfo
}

func (ResourceSceneRes) TableName() string {
@@ -86,7 +87,7 @@ func (ResourceSceneBriefRes) TableName() string {
return "resource_scene"
}

type ResourceSpecWithSceneId struct {
type ResourceSpecInfo struct {
ID int64
SourceSpecId string
AccCardsNum int
@@ -108,7 +109,7 @@ type ResourceSpecWithSceneId struct {
AccCardType string
}

func (ResourceSpecWithSceneId) TableName() string {
func (ResourceSpecInfo) TableName() string {
return "resource_specification"
}

@@ -264,6 +265,9 @@ func SearchResourceScene(opts SearchResourceSceneOptions) (int64, []ResourceScen
if opts.AccCardType != "" {
cond = cond.And(builder.Eq{"resource_queue.acc_card_type": opts.AccCardType})
}
if opts.Cluster != "" {
cond = cond.And(builder.Eq{"resource_queue.cluster": opts.Cluster})
}
cond = cond.And(builder.NewCond().Or(builder.Eq{"resource_scene.delete_time": 0}).Or(builder.IsNull{"resource_scene.delete_time"}))
cols := []string{"resource_scene.id", "resource_scene.scene_name", "resource_scene.job_type", "resource_scene.is_exclusive",
"resource_scene.exclusive_org"}
@@ -297,7 +301,7 @@ func SearchResourceScene(opts SearchResourceSceneOptions) (int64, []ResourceScen
sceneIds = append(sceneIds, v.ID)
}

specs := make([]ResourceSpecWithSceneId, 0)
specs := make([]ResourceSpecInfo, 0)

if err := x.Cols("resource_specification.id", "resource_specification.source_spec_id",
"resource_specification.acc_cards_num", "resource_specification.cpu_cores",
@@ -316,10 +320,10 @@ func SearchResourceScene(opts SearchResourceSceneOptions) (int64, []ResourceScen
return 0, nil, err
}

specsMap := make(map[int64][]ResourceSpecWithSceneId, 0)
specsMap := make(map[int64][]ResourceSpecInfo, 0)
for _, v := range specs {
if _, ok := specsMap[v.SceneId]; !ok {
specsMap[v.SceneId] = []ResourceSpecWithSceneId{v}
specsMap[v.SceneId] = []ResourceSpecInfo{v}
} else {
specsMap[v.SceneId] = append(specsMap[v.SceneId], v)
}
@@ -328,7 +332,7 @@ func SearchResourceScene(opts SearchResourceSceneOptions) (int64, []ResourceScen
for i, v := range r {
s := specsMap[v.ID]
if s == nil {
s = make([]ResourceSpecWithSceneId, 0)
s = make([]ResourceSpecInfo, 0)
}
r[i].Specs = s
}


+ 28
- 9
models/resource_specification.go View File

@@ -159,6 +159,28 @@ func (r ResourceSpecAndQueue) ConvertToRes() *ResourceSpecAndQueueRes {
}
}

func (r ResourceSpecAndQueue) ConvertToResourceSpecInfo() *ResourceSpecInfo {
return &ResourceSpecInfo{
ID: r.ResourceSpecification.ID,
SourceSpecId: r.SourceSpecId,
AccCardsNum: r.AccCardsNum,
CpuCores: r.CpuCores,
MemGiB: r.MemGiB,
GPUMemGiB: r.GPUMemGiB,
ShareMemGiB: r.ShareMemGiB,
UnitPrice: r.UnitPrice,
Status: r.ResourceSpecification.Status,
UpdatedTime: r.ResourceSpecification.UpdatedTime,
Cluster: r.Cluster,
AiCenterCode: r.AiCenterCode,
AiCenterName: r.AiCenterName,
QueueCode: r.QueueCode,
QueueId: r.QueueId,
ComputeResource: r.ComputeResource,
AccCardType: r.AccCardType,
}
}

type FindSpecsOptions struct {
JobType JobType
ComputeResource string
@@ -209,7 +231,7 @@ func (Specification) TableName() string {
return "resource_specification"
}

func (s *Specification) loadRelatedSpecs() {
func (s *Specification) loadRelatedSpecs(jobType JobType) {
if s.RelatedSpecs != nil {
return
}
@@ -222,8 +244,9 @@ func (s *Specification) loadRelatedSpecs() {
ComputeResource: s.ComputeResource,
Cluster: s.Cluster,
SourceSpecId: s.SourceSpecId,
RequestAll: true,
RequestAll: false,
SpecStatus: SpecOnShelf,
JobType: jobType,
})
if err != nil {
s.RelatedSpecs = defaultSpecs
@@ -231,19 +254,15 @@ func (s *Specification) loadRelatedSpecs() {
}
s.RelatedSpecs = r
}
func (s *Specification) GetAvailableCenterIds(userIds ...int64) []string {
s.loadRelatedSpecs()
func (s *Specification) GetAvailableCenterIds(userId int64, jobType JobType) []string {
s.loadRelatedSpecs(jobType)

if len(s.RelatedSpecs) == 0 {
return make([]string, 0)
}

var uId int64
if len(userIds) > 0 {
uId = userIds[0]
}
//filter exclusive specs
specs := FilterExclusiveSpecs(s.RelatedSpecs, uId)
specs := FilterExclusiveSpecs(s.RelatedSpecs, userId)

centerIds := make([]string, len(specs))
for i, v := range specs {


+ 2
- 2
modules/grampus/grampus.go View File

@@ -275,7 +275,7 @@ func GenerateNotebookJob(ctx *context.Context, req *GenerateNotebookJobReq) (job
AutoStopDuration: autoStopDurationMs,
Capacity: setting.Capacity,
Command: req.Command,
CenterID: req.Spec.GetAvailableCenterIds(ctx.User.ID),
CenterID: req.Spec.GetAvailableCenterIds(ctx.User.ID, models.JobTypeDebug),
},
},
})
@@ -433,7 +433,7 @@ func GenerateTrainJob(ctx *context.Context, req *GenerateTrainJobReq) (jobId str
ResourceSpecId: req.Spec.SourceSpecId,
ImageId: req.ImageId,
ImageUrl: req.ImageUrl,
CenterID: req.Spec.GetAvailableCenterIds(ctx.User.ID),
CenterID: req.Spec.GetAvailableCenterIds(ctx.User.ID, models.JobTypeTrain),
ReplicaNum: 1,
Datasets: datasetGrampus,
Models: modelGrampus,


+ 11
- 9
routers/admin/resources.go View File

@@ -144,24 +144,24 @@ func GetResourceSpecificationList(ctx *context.Context) {
}

func GetAllResourceSpecificationList(ctx *context.Context) {
queue := ctx.QueryInt64("queue")
status := ctx.QueryInt("status")
cluster := ctx.Query("cluster")
available := ctx.QueryInt("available")
list, err := resource.GetAllDistinctResourceSpecification(models.SearchResourceSpecificationOptions{
QueueId: queue,
Status: status,
Cluster: cluster,
AvailableCode: available,
AccCardsNum: -1,
computeResource := ctx.Query("resource")
list, err := resource.GetAllResourceSpecification(models.SearchResourceSpecificationOptions{
Status: status,
Cluster: cluster,
AvailableCode: available,
AccCardsNum: -1,
ComputeResource: computeResource,
})
if err != nil {
log.Error("GetResourceSpecificationList error.%v", err)
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
return
}
ctx.JSON(http.StatusOK, response.SuccessWithData(list))
r := map[string]interface{}{"Specs": list}
ctx.JSON(http.StatusOK, response.SuccessWithData(r))
}

func GetResourceSpecificationScenes(ctx *context.Context) {
@@ -234,6 +234,7 @@ func GetResourceSceneList(ctx *context.Context) {
isExclusive := ctx.QueryInt("IsExclusive")
computeResource := ctx.Query("resource")
cardType := ctx.Query("cardType")
cluster := ctx.Query("cluster")
list, err := resource.GetResourceSceneList(models.SearchResourceSceneOptions{
ListOptions: models.ListOptions{Page: page, PageSize: 10},
JobType: jobType,
@@ -242,6 +243,7 @@ func GetResourceSceneList(ctx *context.Context) {
QueueId: queueId,
ComputeResource: computeResource,
AccCardType: cardType,
Cluster: cluster,
})
if err != nil {
log.Error("GetResourceSceneList error.%v", err)


+ 2
- 2
services/ai_task_service/task/cloudbrain_one_notebook_task.go View File

@@ -75,7 +75,7 @@ func (g CloudbrainOneNotebookTaskTemplate) CallCreationAPI(ctx *context.Creation
PreTrainModel: ctx.GetContainerDataArray(ai_task_entity.ContainerPreTrainModel),
AutoStopDuration: autoStopDurationMs,
Capacity: setting.Capacity,
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID),
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID, form.JobType),
Spec: ctx.Spec,
},
},
@@ -118,7 +118,7 @@ func (g CloudbrainOneNotebookTaskTemplate) CallRestartAPI(ctx *context.CreationC
PreTrainModel: ctx.GetContainerDataArray(ai_task_entity.ContainerPreTrainModel),
AutoStopDuration: autoStopDurationMs,
Capacity: setting.Capacity,
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID),
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID, form.JobType),
Spec: ctx.Spec,
},
},


+ 1
- 1
services/ai_task_service/task/grampus_notebook_task.go View File

@@ -101,7 +101,7 @@ func (g GrampusNoteBookTaskTemplate) CallCreationAPI(ctx *context.CreationContex
Code: ctx.GetContainerDataArray(ai_task_entity.ContainerCode),
AutoStopDuration: autoStopDurationMs,
Capacity: setting.Capacity,
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID),
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID, form.JobType),
Spec: ctx.Spec,
},
},


+ 2
- 2
services/ai_task_service/task/grampus_train_task.go View File

@@ -63,7 +63,7 @@ func (g GrampusTrainTaskTemplate) CallCreationAPI(ctx *context.CreationContext)
ImageUrl: strings.TrimSpace(form.ImageUrl),
Datasets: ctx.GetContainerDataArray(ai_task_entity.ContainerDataset),
Code: ctx.GetContainerData(ai_task_entity.ContainerCode),
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID),
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID, form.JobType),
Models: ctx.GetContainerDataArray(ai_task_entity.ContainerPreTrainModel),
BootFile: form.BootFile,
OutPut: ctx.GetContainerData(ai_task_entity.ContainerOutPutPath),
@@ -106,7 +106,7 @@ func (g GrampusTrainTaskTemplate) CallRestartAPI(ctx *context.CreationContext) *
ImageUrl: strings.TrimSpace(form.ImageUrl),
Datasets: ctx.GetContainerDataArray(ai_task_entity.ContainerDataset),
Code: ctx.GetContainerData(ai_task_entity.ContainerCode),
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID),
CenterID: ctx.Spec.GetAvailableCenterIds(ctx.User.ID, form.JobType),
Models: ctx.GetContainerDataArray(ai_task_entity.ContainerPreTrainModel),
BootFile: form.BootFile,
OutPut: ctx.GetContainerData(ai_task_entity.ContainerOutPutPath),


+ 15
- 0
services/cloudbrain/resource/resource_specification.go View File

@@ -147,6 +147,21 @@ func GetAllDistinctResourceSpecification(opts models.SearchResourceSpecification
return models.NewResourceSpecAndQueueListRes(0, nr), nil
}

func GetAllResourceSpecification(opts models.SearchResourceSpecificationOptions) ([]*models.ResourceSpecInfo, error) {
opts.Page = 0
opts.PageSize = 1000
opts.OrderBy = models.SearchSpecOrder4Standard
_, r, err := models.SearchResourceSpecification(opts)
if err != nil {
return nil, err
}
res := make([]*models.ResourceSpecInfo, len(r))
for i := 0; i < len(r); i++ {
res[i] = r[i].ConvertToResourceSpecInfo()
}
return res, nil
}

func distinctResourceSpecAndQueue(r []models.ResourceSpecAndQueue) []models.ResourceSpecAndQueue {
specs := make([]models.ResourceSpecAndQueue, 0, len(r))
sourceSpecIdMap := make(map[string]models.ResourceSpecAndQueue, 0)


+ 1
- 0
web_src/vuepages/apis/modules/resources.js View File

@@ -117,6 +117,7 @@ export const getResSpecificationList = (params) => {
// cluster 所属集群 :OpenI 启智集群,C2Net 智算集群
// queue 所属队列id
// status 状态 : 1 待审核 2已上架 3已下架
// available 是否可用 -1全部 1可用 2不可用
export const getResSpecificationListAll = (params) => {
return service({
url: '/admin/resources/specification/list/all',


+ 5
- 0
web_src/vuepages/langs/config/en-US.js View File

@@ -19,6 +19,8 @@ const en = {
star: 'Star',
unStar: 'UnStar',
submit: 'Submit',
selectAll: 'Select All',
selectNone: 'Select None',

accomplishTask: 'Accomplish Task',
adminOperate: 'Administrator Operation',
@@ -173,6 +175,9 @@ const en = {
resourceSpecificationIsAvailableAll: 'Specification Is Available(All)',
available: 'Available',
notAvailable: 'Not Available',
selectSpec: 'Select resource specification',
accordingSpec: 'According to resource specification',
accordingQueue: 'According to resources queue',
},
user: {
inviteFriends: 'Invite Friends',


+ 5
- 0
web_src/vuepages/langs/config/zh-CN.js View File

@@ -18,6 +18,8 @@ const zh = {
star: '收藏',
unStar: '取消收藏',
submit: '提交',
selectAll: '全选',
selectNone: '全不选',

accomplishTask: "积分任务",
adminOperate: "管理员操作",
@@ -172,6 +174,9 @@ const zh = {
resourceSpecificationIsAvailableAll: "资源规格是否可用(全部)",
available: "可用",
notAvailable: "不可用",
selectSpec: '选择资源规格',
accordingSpec: '按资源规格',
accordingQueue: '按资源池队列',
},
user: {
inviteFriends: "邀请好友",


+ 39
- 73
web_src/vuepages/pages/resources/components/SceneDialog.vue View File

@@ -1,6 +1,6 @@
<template>
<div class="base-dlg">
<BaseDialog :visible.sync="dialogShow" :width="`750px`"
<BaseDialog :visible.sync="dialogShow" :width="`900px`"
:title="type === 'add' ? $t('resourcesManagement.addResScene') : $t('resourcesManagement.editResScene')"
@open="open" @opened="opened" @close="close" @closed="closed">
<div class="dlg-content">
@@ -53,30 +53,19 @@
</el-select>
</div>
</div>
<div class="form-row">
<div class="title">
<span>{{ $t('resourcesManagement.resQueue') }}</span>
</div>
<div class="content">
<el-select v-model="dataInfo.QueueId" @change="changeQueue" :disabled="type === 'edit'">
<el-option v-for="item in queueList" :key="item.k" :label="item.v" :value="item.k" />
</el-select>
</div>
</div>
<div class="form-row">
<div class="title required">
<span>{{ $t('resourcesManagement.resourceSpecification') }}</span>
<span>{{ $t('resourcesManagement.computeResource') }}</span>
</div>
<div class="content">
<el-select v-model="dataInfo.SpecIds" multiple collapse-tags class="specSel">
<el-option v-for="item in specsList" :label="item.v" :key="item.k" :value="item.k">
<span v-html="item.v"></span>
</el-option>
<el-select v-model="dataInfo.Resource" @change="changeResource" :disabled="type === 'edit'">
<el-option v-for="item in resourceList" :key="item.k" :label="item.v" :value="item.k" />
</el-select>
</div>
</div>
<div class="form-row" style="margin-top: 20px">
<div class="title"></div>
<SpecSelect v-model="dataInfo.SpecIds" :specs="specsList"></SpecSelect>
<div class="form-row" style="margin-top:20px">
<div class="title" style="width:285px;"></div>
<div class="content">
<el-button type="primary" class="btn confirm-btn" @click="confirm">{{ $t('confirm') }}</el-button>
<el-button class="btn" @click="cancel">{{ $t('cancel') }}</el-button>
@@ -89,8 +78,9 @@
</template>
<script>
import BaseDialog from '~/components/BaseDialog.vue';
import SpecSelect from './SpecSelect.vue';
import { getResQueueCode, getResSpecificationListAll, addResScene, updateResScene } from '~/apis/modules/resources';
import { JOB_TYPE, CLUSTERS, ACC_CARD_TYPE, SPECIFICATION_STATUS } from '~/const';
import { JOB_TYPE, CLUSTERS, ACC_CARD_TYPE, SPECIFICATION_STATUS, COMPUTER_RESOURCES } from '~/const';
import { getListValueWithKey } from '~/utils';

export default {
@@ -101,9 +91,7 @@ export default {
type: { type: String, defalut: 'add' },
data: { type: Object, default: () => ({}) },
},
components: {
BaseDialog
},
components: { BaseDialog, SpecSelect },
data() {
return {
dialogShow: false,
@@ -113,7 +101,7 @@ export default {
accCardTypeList: [...ACC_CARD_TYPE],
statusList: [...SPECIFICATION_STATUS],
isExclusiveList: [{ k: '2', v: this.$t('resourcesManagement.commonUse') }, { k: '1', v: this.$t('resourcesManagement.exclusive') }],
queueList: [],
resourceList: [...COMPUTER_RESOURCES],
specsList: [],
};
},
@@ -129,61 +117,33 @@ export default {
JobType: '',
IsExclusive: '2',
ExclusiveOrg: '',
Cluster: '',
QueueId: '',
Cluster: 'OpenI',
Resource: 'GPU',
SpecIds: [],
}
this.queueList.splice(0, Infinity);
this.specsList.splice(0, Infinity);
},
getQueueList(next) {
return getResQueueCode({ cluster: this.dataInfo.Cluster }).then(res => {
res = res.data;
if (res.Code === 0) {
const data = res.Data;
const list = [];
for (let i = 0, iLen = data.length; i < iLen; i++) {
const item = data[i];
list.push({
k: item.ID,
v: `${item.QueueCode}(${getListValueWithKey(this.clusterList, item.Cluster)} - ${item.AiCenterName})`,
});
}
list.unshift({
k: '-1',
v: this.$t('resourcesManagement.allResQueue'),
});
this.queueList.splice(0, Infinity, ...list);
if (next) {
if (this.type === 'add') {
this.dataInfo.QueueId = '-1';
}
this.getResSpecificationList();
}
}
}).catch(err => {
console.log(err);
});
},
getResSpecificationList() {
const params = {
cluster: this.dataInfo.Cluster,
resource: this.dataInfo.Resource,
queue: this.dataInfo.QueueId === '-1' ? '' : this.dataInfo.QueueId,
available: 1,
// status: 2,
// page: 1,
};
return getResSpecificationListAll(params).then(res => {
res = res.data;
if (res.Code === 0) {
const list = res.Data.List;
const list = res.Data.Specs;
const data = list.map((item) => {
const Queue = item.Queue;
const Spec = item.Spec;
const NGPU = `${Queue.ComputeResource}:${Spec.AccCardsNum + '*' + getListValueWithKey(this.accCardTypeList, Queue.AccCardType)}`;
const statusStr = Spec.Status != '2' ? `<span style="color:rgb(245, 34, 45)">(${getListValueWithKey(this.statusList, Spec.Status.toString())})</span>` : '';
const NGPU = `${item.ComputeResource}:${item.AccCardsNum + '*' + getListValueWithKey(this.accCardTypeList, item.AccCardType)}`;
const statusStr = item.Status != '2' ? `<span style="color:rgb(245, 34, 45)">(${getListValueWithKey(this.statusList, item.Status.toString())})</span>` : '';
return {
k: Spec.ID,
v: `${NGPU}, CPU:${Spec.CpuCores}, ${this.$t('resourcesManagement.gpuMem')}:${Spec.GPUMemGiB}GB, ${this.$t('resourcesManagement.mem')}:${Spec.MemGiB}GB, ${this.$t('resourcesManagement.shareMem')}:${Spec.ShareMemGiB}GB, ${this.$t('resourcesManagement.unitPrice')}:${Spec.UnitPrice}${this.$t('resourcesManagement.point_hr')}${statusStr}`,
...item,
StatusStr: statusStr,
QueueStr: `${item.QueueCode}(${getListValueWithKey(this.clusterList, item.Cluster)} - ${item.AiCenterName})`,
SpecStr: `${NGPU}, CPU:${item.CpuCores}, ${this.$t('resourcesManagement.gpuMem')}:${item.GPUMemGiB}GB, ${this.$t('resourcesManagement.mem')}:${item.MemGiB}GB, ${this.$t('resourcesManagement.shareMem')}:${item.ShareMemGiB}GB, ${this.$t('resourcesManagement.unitPrice')}:${item.UnitPrice}${this.$t('resourcesManagement.point_hr')}`,
}
});
this.specsList.splice(0, Infinity, ...data);
@@ -196,13 +156,11 @@ export default {
this.dataInfo.ExclusiveOrg = '';
},
changeCluster() {
this.dataInfo.QueueId = '';
this.dataInfo.SpecIds = [];
this.queueList.splice(0, Infinity);
this.specsList.splice(0, Infinity);
this.getQueueList(true);
this.getResSpecificationList();
},
changeQueue() {
changeResource() {
this.dataInfo.SpecIds = [];
this.specsList.splice(0, Infinity);
this.getResSpecificationList();
@@ -212,12 +170,19 @@ export default {
if (this.type === 'add') {
//
} else if (this.type === 'edit') {
Object.assign(this.dataInfo, { ...this.data, QueueId: this.data.QueueIds.length === 1 ? this.data.QueueIds[0] : '-1' });
this.queueList.splice(0, Infinity);
this.specsList.splice(0, Infinity);
this.getQueueList(true);
Object.assign(this.dataInfo, {
ID: this.data.ID,
SceneName: this.data.SceneName,
JobType: this.data.JobType,
IsExclusive: this.data.IsExclusive,
ExclusiveOrg: this.data.ExclusiveOrg,
Cluster: this.data.Cluster,
Resource: this.data.ComputeResource,
SpecIds: [...this.data.SpecIds],
});
}
this.$emit("open");
this.getResSpecificationList();
},
opened() {
this.$emit("opened");
@@ -237,6 +202,8 @@ export default {
});
return;
}
// console.log('submit', this.dataInfo);
// return;
const setApi = this.type === 'add' ? addResScene : updateResScene;
setApi({
...this.dataInfo,
@@ -263,7 +230,6 @@ export default {
message: this.$t('submittedFailed')
});
})

},
cancel() {
this.dialogShow = false;
@@ -282,7 +248,7 @@ export default {
justify-content: center;

.form {
width: 600px;
width: 800px;

.form-row {
display: flex;
@@ -290,7 +256,7 @@ export default {
margin-bottom: 4px;

.title {
width: 160px;
width: 255px;
display: flex;
justify-content: flex-end;
align-items: center;


+ 548
- 0
web_src/vuepages/pages/resources/components/SpecSelect.vue View File

@@ -0,0 +1,548 @@
<template>
<div>
<div class="form-row">
<div class="title required">
<span>{{ $t('resourcesManagement.resourceSpecification') }}</span>
</div>
<div class="content"></div>
<div class="select-btn">
<a @click="dlgShow = true" href="javascript:;">{{ $t('resourcesManagement.selectSpec') }}</a>
</div>
<div class="base-dlg">
<BaseDialog :visible.sync="dlgShow" :width="`1200px`" :title="$t('resourcesManagement.selectSpec')" @open="open"
@opened="opened" @close="close" :appendToBody="true" @closed="closed">
<div class="dlg-content">
<div class="left-area">
<div class="tabs">
<div class="tab" :class="tabIndex == '1' ? 'focused' : ''" @click="changeTab(1)">{{
$t('resourcesManagement.accordingSpec') }}</div>
<div class="tab" :class="tabIndex == '2' ? 'focused' : ''" @click="changeTab(2)">{{
$t('resourcesManagement.accordingQueue') }}</div>
</div>
<div class="table-c">
<div v-if="tabIndex == '1'">
<div>
<div class="header row">
<div class="row-l" style="width:760px;">{{ $t('resourcesManagement.resourceSpecification') }}</div>
<div class="row-r" style="flex:1">{{ $t('resourcesManagement.resQueue') }}</div>
</div>
<div class="table-content">
<div class="row" v-for="item in tableData1">
<div class="row-l" style="width:760px;">{{ item.SpecStr }}</div>
<div class="row-r" style="flex:1">
<div class="btn-c">
<button @click="selectAll(item.queues)">{{ $t('selectAll') }}</button>
<button @click="clearSelectAll(item.queues)">{{ $t('selectNone') }}</button>
</div>
<div class="" v-for="_item in item.queues">
<el-checkbox :value="_item.checked" @change="selectChange(_item.ID)">
<span v-html="_item.QueueStr + _item.StatusStr"></span>
</el-checkbox>
</div>
</div>
</div>
</div>
</div>
</div>
<div v-if="tabIndex == '2'">
<div class="header row">
<div class="row-l" style="width:380px;">{{ $t('resourcesManagement.resQueue') }}</div>
<div class="row-r" style="flex:2">{{ $t('resourcesManagement.resourceSpecification') }}</div>
</div>
<div class="table-content">
<div class="row" v-for="item in tableData2">
<div class="row-l" style="width:380px;">{{ item.QueueStr }}</div>
<div class="row-r" style="flex:2">
<div class="btn-c">
<button @click="selectAll(item.specs)">{{ $t('selectAll') }}</button>
<button @click="clearSelectAll(item.specs)">{{ $t('selectNone') }}</button>
</div>
<div class="" v-for="_item in item.specs">
<el-checkbox :value="_item.checked" @change="selectChange(_item.ID)">
<span v-html="_item.SpecStr + _item.StatusStr"></span>
</el-checkbox>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="confirm-btn-c">
<el-button type="primary" class="btn confirm-btn" @click="confirm">{{ $t('confirm') }}</el-button>
</div>
</div>
</div>
</BaseDialog>
</div>
</div>
<div class="row-detail">
<div class="table-c">
<div>
<div class="header row">
<div class="row-l" style="width:500px;">{{ $t('resourcesManagement.resourceSpecification') }}</div>
<div class="row-r" style="flex:1">{{ $t('resourcesManagement.resQueue') }}</div>
</div>
<div class="table-content">
<div class="row" v-for="item in tableDataShow">
<div class="row-l" style="width:500px;">{{ item.SpecStr }}</div>
<div class="row-r" style="flex:1">
<div class="" v-for="_item in item.queues">
<span v-html="_item.QueueStr + _item.StatusStr"></span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>

<script>
import BaseDialog from '~/components/BaseDialog.vue';
import { SPECIFICATION_STATUS } from '~/const';

export default {
name: "SpecSelect",
props: {
visible: { type: Boolean, default: false },
value: { type: Array, required: true },
specs: { type: Array, required: true },
},
components: { BaseDialog },
data() {
return {
dlgShow: false,
tabIndex: '1',
selectList: [],
statusList: [...SPECIFICATION_STATUS],
};
},
watch: {
visible: function (val) {
this.dlgShow = val;
},
value: {
immediate: true,
deep: true,
handler(newVal) {
newVal = newVal === undefined ? [] : newVal;
this.selectList = [...newVal];
}
}
},
computed: {
tableData1: function () {
const map = {};
for (let i = 0, iLen = this.specs.length; i < iLen; i++) {
const spec = this.specs[i];
const key = `${spec.Cluster}|${spec.ComputeResource}|${spec.SourceSpecId || Math.random()}`;
const queue = {
...spec,
QueueId: spec.QueueId,
QueueCode: spec.QueueCode,
AiCenterCode: spec.AiCenterCode,
AiCenterName: spec.AiCenterName,
QueueStr: spec.QueueStr,
checked: this.selectList.indexOf(spec.ID) > -1,
};
if (map[key]) {
map[key].queues.push(queue);
} else {
map[key] = {
...spec,
queues: [queue],
}
}
}
const data = [];
for (let key in map) {
data.push(map[key]);
}
return data;
},
tableData2: function () {
const map = {};
for (let i = 0, iLen = this.specs.length; i < iLen; i++) {
const spec = this.specs[i];
const key = `${spec.Cluster}|${spec.ComputeResource}|${spec.QueueId}`;
const _spec = {
...spec,
QueueId: spec.QueueId,
QueueCode: spec.QueueCode,
AiCenterCode: spec.AiCenterCode,
AiCenterName: spec.AiCenterName,
QueueStr: spec.QueueStr,
checked: this.selectList.indexOf(spec.ID) > -1,
};
if (map[key]) {
map[key].specs.push(_spec);
} else {
map[key] = {
...spec,
specs: [_spec],
}
}
}
const data = [];
for (let key in map) {
data.push(map[key]);
}
return data;
},
tableDataShow: function () {
const map = {};
for (let i = 0, iLen = this.specs.length; i < iLen; i++) {
const spec = this.specs[i];
if (this.value.indexOf(spec.ID) < 0) continue;
const key = `${spec.Cluster}|${spec.ComputeResource}|${spec.SourceSpecId || Math.random()}`;
const queue = {
...spec,
QueueId: spec.QueueId,
QueueCode: spec.QueueCode,
AiCenterCode: spec.AiCenterCode,
AiCenterName: spec.AiCenterName,
QueueStr: spec.QueueStr,
};
if (map[key]) {
map[key].queues.push(queue);
} else {
map[key] = {
...spec,
queues: [queue],
}
}
}
const data = [];
for (let key in map) {
data.push(map[key]);
}
return data;
},
},
methods: {
resetDataInfo() {
this.dataInfo = {}
},
changeTab(tabIndex) {
if (this.tabIndex == tabIndex) return;
this.tabIndex = tabIndex;
},
selectChange(id) {
const index = this.selectList.indexOf(id);
if (index < 0) {
this.selectList.push(id);
} else {
this.selectList.splice(index, 1);
}
},
selectAll(list) {
list.forEach(_item => {
if (this.selectList.indexOf(_item.ID) < 0) {
this.selectList.push(_item.ID);
}
});
},
clearSelectAll(list) {
list.forEach(_item => {
const index = this.selectList.indexOf(_item.ID);
if (index >= 0) {
this.selectList.splice(index, 1);
}
});
},
open() {
this.$emit("open");
this.selectList = [...this.value];
this.tabIndex = '1';
},
opened() {
this.$emit("opened");
},
close() {
this.$emit("close");
},
closed() {
this.$emit("closed");
this.$emit("update:visible", false);
},
confirm() {
this.dlgShow = false;
this.$emit('input', this.selectList);
this.$emit('change', this.selectList);
},
cancel() {
this.dlgShow = false;
this.$emit("update:visible", false);
}
},
mounted() {
this.resetDataInfo();
},
};
</script>
<style scoped lang="less">
.form-row {
display: flex;
min-height: 42px;
margin-bottom: 4px;

.title {
width: 255px;
display: flex;
justify-content: flex-end;
align-items: center;
margin-right: 20px;
color: rgb(136, 136, 136);
font-size: 14px;

&.required {
span {
position: relative;
}

span::after {
position: absolute;
right: -10px;
top: -2px;
vertical-align: top;
content: '*';
color: #db2828;
}
}
}

.content {
width: 10px;
display: flex;
align-items: center;
}

.select-btn {
display: flex;
align-items: center;
font-size: 13px;
}
}

.row-detail {
font-size: 12px;

.table-c {
padding-bottom: 2px;
padding-right: 8px;

.table-content {
max-height: 300px;
overflow: auto;
margin-top: -1px;
padding-bottom: 2px;
}

.row {
display: flex;

&.header {
background-color: #f5f5f6;

.row-l {
justify-content: center;
border-bottom: 1px solid gainsboro;
}

.row-r {
flex-direction: row;
justify-content: center;
border-bottom: 1px solid gainsboro;
}
}

.row-l {
display: flex;
align-items: center;
border: 1px solid gainsboro;
border-right: none;
border-bottom: none;
padding: 4px 8px;
width: 0;
overflow: hidden;
}

.row-r {
border: 1px solid gainsboro;
padding: 4px 8px;
display: flex;
flex-direction: column;
border-bottom: none;
width: 0;
overflow: hidden;
position: relative;
}

&:last-child {
.row-l {
border-bottom: 1px solid gainsboro;
}

.row-r {
border-bottom: 1px solid gainsboro;
}
}
}
}

}

.dlg-content {
margin: -30px 0 0 0;
display: flex;
font-size: 12px;

.left-area {
flex: 1;
width: 0;

.tabs {
display: flex;
align-items: center;
margin-bottom: 10px;

.tab {
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: rgba(0, 0, 0, .87);
border: 1px solid rgba(34, 36, 38, .15);
margin-left: -1px;
height: 30px;
padding: 0 6px;
border-left: none;

&.focused {
color: #0087f5;
border-color: #0087f5;
border-left: 1px solid #0087f5 !important;
}

&:first-child {
border-radius: 0.28571429rem 0 0 0.28571429rem;
border-left: 1px solid rgba(34, 36, 38, .15);

&.focus {
border-color: #0087f5;
}
}

&:last-child {
border-radius: 0 0.28571429rem 0.28571429rem 0;
}

&:hover:not(.focused) {
background: rgba(0, 0, 0, .03);
cursor: pointer;
}
}
}

.table-c {
padding-bottom: 2px;
padding-right: 8px;

.table-content {
height: 400px;
overflow: auto;
margin-top: -1px;
padding-bottom: 2px;
}

.row {
display: flex;

&.header {
background-color: #f5f5f6;

.row-l {
justify-content: center;
border-bottom: 1px solid gainsboro;
}

.row-r {
flex-direction: row;
justify-content: center;
border-bottom: 1px solid gainsboro;
}
}

.row-l {
display: flex;
align-items: center;
border: 1px solid gainsboro;
border-right: none;
border-bottom: none;
padding: 4px 8px;
width: 0;
overflow: hidden;
}

.row-r {
border: 1px solid gainsboro;
padding: 4px 8px;
display: flex;
flex-direction: column;
border-bottom: none;
width: 0;
overflow: hidden;
position: relative;

/deep/ .el-checkbox__label {
font-size: 12px;
}

.btn-c {
position: absolute;
top: 5px;
right: 5px;
z-index: 1;

button {
background-color: #409EFF;
color: #FFF;
margin-left: 3px;
outline: none;
cursor: pointer;
border: none;
border-radius: 2px;
padding: 2px 4px;

&:focus,
&:hover {
background: #66b1ff;
}

&:active {
background: #3a8ee6;
}
}
}
}

&:last-child {
.row-l {
border-bottom: 1px solid gainsboro;
}

.row-r {
border-bottom: 1px solid gainsboro;
}
}
}
}

.confirm-btn-c {
margin-top: 15px;
text-align: right;
}
}
}
</style>

+ 112
- 59
web_src/vuepages/pages/resources/scene/index.vue View File

@@ -9,6 +9,9 @@
<el-select class="select" size="medium" v-model="selIsExclusive" @change="selectChange">
<el-option v-for="item in isExclusiveList" :key="item.k" :label="item.v" :value="item.k" />
</el-select>
<el-select class="select" size="medium" v-model="selCluster" @change="selectChange">
<el-option v-for="item in clusterList" :key="item.k" :label="item.v" :value="item.k" />
</el-select>
<el-select class="select" size="medium" v-model="selAiCenter" @change="selectChange">
<el-option v-for="item in aiCenterList" :key="item.k" :label="item.v" :value="item.k" />
</el-select>
@@ -29,8 +32,8 @@
</div>
<div class="table-container">
<div style="min-height:600px;">
<el-table border :data="tableData" style="width: 100%" v-loading="loading" stripe>
<el-table-column prop="ID" label="ID" align="center" header-align="center" width="60"></el-table-column>
<el-table border :data="tableData" style="width:100%" v-loading="loading" stripe :span-method="tableSpanMethod">
<el-table-column prop="_id_" label="ID" align="center" header-align="center" width="60"></el-table-column>
<el-table-column prop="SceneName" :label="$t('resourcesManagement.resSceneName')" align="center"
header-align="center"></el-table-column>
<el-table-column prop="JobTypeStr" :label="$t('resourcesManagement.jobType')" align="center"
@@ -48,29 +51,27 @@
<span>{{ scope.row.IsExclusive ? scope.row.ExclusiveOrg : '--' }}</span>
</template>
</el-table-column>
<el-table-column prop="AiCenterStr" :label="$t('resourcesManagement.aiCenter')" align="center"
header-align="center">
<el-table-column prop="specStr" :label="$t('resourcesManagement.resourceSpecification')" align="left"
header-align="center" min-width="180">
<template slot-scope="scope">
<div v-if="!scope.row.Queues.length">--</div>
<div v-for="item in scope.row.Queues" :key="item.QueueId">
<span>{{ item.AiCenterName }}</span>
</div>
<span v-html="scope.row.specStr"></span>
</template>
</el-table-column>
<el-table-column prop="QueueStr" :label="$t('resourcesManagement.resQueue')" align="center"
header-align="center">
<el-table-column prop="AiCenterStr" :label="$t('resourcesManagement.aiCenter')" align="center"
header-align="center" width="230">
<template slot-scope="scope">
<div v-if="!scope.row.Queues.length">--</div>
<div v-for="item in scope.row.Queues" :key="item.QueueId">
<span>{{ item.QueueStr }}</span>
<div v-if="!scope.row.queues.length">--</div>
<div v-for="item in scope.row.queues " :key="item.key">
<span>{{ item.AiCenterName }}</span>
</div>
</template>
</el-table-column>
<el-table-column prop="SpecsList" :label="$t('resourcesManagement.resourceSpecification')" align="left"
header-align="center" min-width="180">
<el-table-column prop="QueueStr" :label="$t('resourcesManagement.resQueue')" align="center"
header-align="center" width="230">
<template slot-scope="scope">
<div v-for="item in scope.row.SpecsList" :key="item.k">
<span v-html="item.v"></span>
<div v-if="!scope.row.queues.length">--</div>
<div v-for=" item in scope.row.queues " :key="item.key">
<span v-html="item.QueueStr + item.statusStr"></span>
</div>
</template>
</el-table-column>
@@ -119,7 +120,8 @@ export default {
isExclusiveList: [{ k: '', v: this.$t('resourcesManagement.allExclusiveAndCommonUse') }, { k: '1', v: this.$t('resourcesManagement.exclusive') }, { k: '2', v: this.$t('resourcesManagement.commonUse') }],
selQueue: '',
queueList: [{ k: '', v: this.$t('resourcesManagement.allResQueue') }],
clusterList: [...CLUSTERS],
selCluster: '',
clusterList: [{ k: '', v: this.$t('resourcesManagement.allCluster') }, ...CLUSTERS],
selAiCenter: '',
aiCenterList: [{ k: '', v: this.$t('resourcesManagement.allAiCenter') }],
accCardTypeList: [...ACC_CARD_TYPE],
@@ -143,6 +145,21 @@ export default {
},
components: { SceneDialog },
methods: {
tableSpanMethod({ row, column, rowIndex, columnIndex }) {
if ([0, 1, 2, 3, 4, 8].indexOf(columnIndex) > -1) {
if (row.rowspan) {
return {
rowspan: row.rowspan,
colspan: 1,
};
} else {
return {
rowspan: 0,
colspan: 0
};
}
}
},
getAiCenterList() {
getAiCenterList().then(res => {
res = res.data;
@@ -183,6 +200,7 @@ export default {
const params = {
jobType: this.selTaskType,
IsExclusive: this.selIsExclusive,
cluster: this.selCluster,
queue: this.selQueue,
center: this.selAiCenter,
resource: this.selResource,
@@ -196,46 +214,81 @@ export default {
res = res.data;
if (res.Code === 0) {
const list = res.Data.List;
const data = list.map((item) => {
const Specs = item.Specs;
const specsList = [];
const queues = [];
const queueIds = [];
let cluster = '';
for (let i = 0, iLen = Specs.length; i < iLen; i++) {
const Spec = Specs[i];
const NGPU = `${Spec.ComputeResource}:${Spec.AccCardsNum + '*' + getListValueWithKey(this.accCardTypeList, Spec.AccCardType)}`;
const statusStr = Spec.Status != '2' ? `<span style="color:rgb(245, 34, 45)">(${getListValueWithKey(this.statusList, Spec.Status.toString())})</span>` : '';
specsList.push({
k: Spec.ID,
v: `${NGPU}, CPU:${Spec.CpuCores}, ${this.$t('resourcesManagement.gpuMem')}:${Spec.GPUMemGiB}GB, ${this.$t('resourcesManagement.mem')}:${Spec.MemGiB}GB, ${this.$t('resourcesManagement.shareMem')}:${Spec.ShareMemGiB}GB, ${this.$t('resourcesManagement.unitPrice')}:${Spec.UnitPrice}${this.$t('resourcesManagement.point_hr')}${statusStr}`,
});
cluster = Spec.Cluster;
if (queueIds.indexOf(Spec.QueueId) < 0) {
queues.push({
QueueId: Spec.QueueId,
QueueCode: Spec.QueueCode,
AiCenterCode: Spec.AiCenterCode,
AiCenterName: Spec.AiCenterName,
QueueStr: `${Spec.QueueCode}(${getListValueWithKey(this.clusterList, Spec.Cluster)} - ${Spec.AiCenterName})`,
});
queueIds.push(Spec.QueueId);
const data = [];
for (let i = 0, iLen = list.length; i < iLen; i++) {
const item = list[i];
const specs = item.Specs;
const sourceSpecIdMap = {};
for (let j = 0, jLen = specs.length; j < jLen; j++) {
const spec = specs[j];
const NGPU = `${spec.ComputeResource}:${spec.AccCardsNum + '*' + getListValueWithKey(this.accCardTypeList, spec.AccCardType)}`;
const statusStr = spec.Status != '2' ? `<span style="color:rgb(245, 34, 45)">(${getListValueWithKey(this.statusList, spec.Status.toString())})</span>` : '';
spec.specStr = `${NGPU}, CPU:${spec.CpuCores}, ${this.$t('resourcesManagement.gpuMem')}:${spec.GPUMemGiB}GB, ${this.$t('resourcesManagement.mem')}:${spec.MemGiB}GB, ${this.$t('resourcesManagement.shareMem')}:${spec.ShareMemGiB}GB, ${this.$t('resourcesManagement.unitPrice')}:${spec.UnitPrice}${this.$t('resourcesManagement.point_hr')}`;
spec.JobTypeStr = getListValueWithKey(this.taskTypeList, item.JobType);
spec.IsExclusiveStr = getListValueWithKey(this.isExclusiveList, item.IsExclusive ? '1' : '2');
spec.statusStr = statusStr;
spec._id_ = item.ID;
const sourceSpecId = spec.SourceSpecId;
if (sourceSpecIdMap[sourceSpecId]) {
sourceSpecIdMap[sourceSpecId].push({ ...spec });
} else {
sourceSpecIdMap[sourceSpecId] = [{ ...item, ...spec }];
}
}
return {
ID: item.ID,
SceneName: item.SceneName,
JobType: item.JobType,
JobTypeStr: getListValueWithKey(this.taskTypeList, item.JobType),
IsExclusive: item.IsExclusive,
IsExclusiveStr: getListValueWithKey(this.isExclusiveList, item.IsExclusive ? '1' : '2'),
ExclusiveOrg: item.ExclusiveOrg,
Cluster: cluster,
QueueIds: queueIds,
Queues: queues,
SpecsList: specsList,
for (let key in sourceSpecIdMap) {
const _specs = sourceSpecIdMap[key];
if (key) {
const aiCenters = [];
const queues = [];
for (let k = 0, kLen = _specs.length; k < kLen; k++) {
const _spec = _specs[k];
queues.push({
QueueId: _spec.QueueId,
QueueCode: _spec.QueueCode,
AiCenterCode: _spec.AiCenterCode,
AiCenterName: _spec.AiCenterName,
QueueStr: `${_spec.QueueCode}(${getListValueWithKey(this.clusterList, _spec.Cluster)} - ${_spec.AiCenterName})`,
statusStr: _spec.statusStr,
key: Math.random(),
});
}
data.push({ ..._specs[0], aiCenters, queues })
} else {
for (let k = 0, kLen = _specs.length; k < kLen; k++) {
const _spec = _specs[k];
data.push({
..._spec,
queues: [{
QueueId: _spec.QueueId,
QueueCode: _spec.QueueCode,
AiCenterCode: _spec.AiCenterCode,
AiCenterName: _spec.AiCenterName,
QueueStr: `${_spec.QueueCode}(${getListValueWithKey(this.clusterList, _spec.Cluster)} - ${_spec.AiCenterName})`,
statusStr: _spec.statusStr,
key: Math.random(),
}]
})
}
}
}
});
}
let rowspan = 1;
let index = 0;
for (let i = 0, iLen = data.length; i < iLen; i++) {
if (i == 0) {
data[i].rowspan = 1;
continue;
}
if (data[i]._id_ == data[i - 1]._id_) {
data[i].rowspan = 0;
rowspan++;
data[index].rowspan = rowspan;
} else {
index = i;
rowspan = 1;
data[index].rowspan = rowspan;
}
}
this.tableData = data;
this.pageInfo.total = res.Data.TotalSize;
}
@@ -261,7 +314,7 @@ export default {
}).then(() => {
updateResScene({
action: 'delete',
ID: row.ID,
ID: row._id_,
}).then(res => {
res = res.data;
if (res.Code === 0) {
@@ -287,14 +340,14 @@ export default {
showDialog(type, data) {
this.sceneDialogType = type;
this.sceneDialogData = data ? {
ID: data.ID,
ID: data._id_,
SceneName: data.SceneName,
JobType: data.JobType,
IsExclusive: data.IsExclusive ? '1' : '2',
ExclusiveOrg: data.ExclusiveOrg,
Cluster: data.Cluster,
QueueIds: data.QueueIds,
SpecIds: data.SpecsList.map((item) => item.k),
ComputeResource: data.ComputeResource,
SpecIds: data.Specs.map(item => item.ID),
} : {};
this.sceneDialogShow = true;
},


Loading…
Cancel
Save