@@ -0,0 +1,49 @@ | |||
package models | |||
import ( | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/timeutil" | |||
"encoding/json" | |||
) | |||
type AdminOperateLog struct { | |||
ID int64 `xorm:"pk autoincr"` | |||
BizType string | |||
OperateType string | |||
OldValue string `xorm:"TEXT"` | |||
NewValue string `xorm:"TEXT"` | |||
RelatedId string `xorm:"INDEX"` | |||
Comment string | |||
CreatedTime timeutil.TimeStamp `xorm:"created"` | |||
CreatedBy int64 | |||
} | |||
type LogValues struct { | |||
Params []LogValue | |||
} | |||
type LogValue struct { | |||
Key string | |||
Val interface{} | |||
} | |||
func (l *LogValues) Add(key string, val interface{}) *LogValues { | |||
l.Params = append(l.Params, LogValue{Key: key, Val: val}) | |||
return l | |||
} | |||
func (l *LogValues) JsonString() string { | |||
if len(l.Params) == 0 { | |||
return "" | |||
} | |||
b, err := json.Marshal(l) | |||
if err != nil { | |||
log.Error("LogValues JsonString error . %v", err) | |||
return "" | |||
} | |||
return string(b) | |||
} | |||
func InsertAdminOperateLog(log AdminOperateLog) (int64, error) { | |||
return x.Insert(&log) | |||
} |
@@ -111,6 +111,16 @@ const ( | |||
GrampusStatusWaiting = "WAITING" | |||
) | |||
const ( | |||
//cluster | |||
OpenICluster = "OpenI" | |||
C2NetCluster = "C2Net" | |||
//AI center | |||
AICenterOfCloudBrainOne = "OpenIOne" | |||
AICenterOfCloudBrainTwo = "OpenITwo" | |||
) | |||
type Cloudbrain struct { | |||
ID int64 `xorm:"pk autoincr"` | |||
JobID string `xorm:"INDEX NOT NULL"` | |||
@@ -1338,6 +1348,34 @@ type GrampusSpec struct { | |||
Name string `json:"name"` | |||
ProcessorType string `json:"processorType"` | |||
Centers []Center `json:"centers"` | |||
SpecInfo SpecInfo `json:"specInfo"` | |||
} | |||
type GrampusAiCenter struct { | |||
AccDevices []GrampusAccDevice `json:"accDevices"` | |||
Id string `json:"id"` | |||
Name string `json:"name"` | |||
Resource []GrampusCenterResource `json:"resource"` | |||
} | |||
type GrampusAccDevice struct { | |||
Kind string `json:"kind"` //加速卡类别, npu.huawei.com/NPU,nvidia.com/gpu,cambricon.com/mlu | |||
Model string `json:"model"` //加速卡型号 | |||
} | |||
type GrampusCenterResource struct { | |||
Allocated string `json:"allocated"` | |||
Capacity string `json:"capacity"` | |||
Name string `json:"name"` | |||
} | |||
type SpecInfo struct { | |||
AccDeviceKind string `json:"accDeviceKind"` | |||
AccDeviceMemory string `json:"accDeviceMemory"` | |||
AccDeviceModel string `json:"accDeviceModel"` | |||
AccDeviceNum int `json:"accDeviceNum"` | |||
CpuCoreNum int `json:"cpuCoreNum"` | |||
MemorySize string `json:"memorySize"` | |||
} | |||
type GetGrampusResourceSpecsResult struct { | |||
@@ -1345,6 +1383,12 @@ type GetGrampusResourceSpecsResult struct { | |||
Infos []GrampusSpec `json:"resourceSpecs"` | |||
} | |||
type GetGrampusAiCentersResult struct { | |||
GrampusResult | |||
Infos []GrampusAiCenter `json:"aiCenterInfos"` | |||
TotalSize int `json:"totalSize"` | |||
} | |||
type GrampusImage struct { | |||
CreatedAt int64 `json:"createdAt"` | |||
UpdatedAt int64 `json:"updatedAt"` | |||
@@ -145,6 +145,11 @@ func init() { | |||
new(OrgStatistic), | |||
new(SearchRecord), | |||
new(AiModelConvert), | |||
new(ResourceQueue), | |||
new(ResourceSpecification), | |||
new(ResourceScene), | |||
new(ResourceSceneSpec), | |||
new(AdminOperateLog), | |||
new(CloudbrainTemp), | |||
new(DatasetReference), | |||
) | |||
@@ -0,0 +1,349 @@ | |||
package models | |||
import ( | |||
"code.gitea.io/gitea/modules/timeutil" | |||
"errors" | |||
"strconv" | |||
"strings" | |||
"xorm.io/builder" | |||
) | |||
type ResourceQueue struct { | |||
ID int64 `xorm:"pk autoincr"` | |||
QueueCode string | |||
Cluster string `xorm:"notnull"` | |||
AiCenterCode string | |||
AiCenterName string | |||
ComputeResource string | |||
AccCardType string | |||
CardsTotalNum int | |||
IsAutomaticSync bool | |||
Remark string | |||
DeletedTime timeutil.TimeStamp `xorm:"deleted"` | |||
CreatedTime timeutil.TimeStamp `xorm:"created"` | |||
CreatedBy int64 | |||
UpdatedTime timeutil.TimeStamp `xorm:"updated"` | |||
UpdatedBy int64 | |||
} | |||
func (r ResourceQueue) ConvertToRes() *ResourceQueueRes { | |||
return &ResourceQueueRes{ | |||
ID: r.ID, | |||
QueueCode: r.QueueCode, | |||
Cluster: r.Cluster, | |||
AiCenterCode: r.AiCenterCode, | |||
AiCenterName: r.AiCenterName, | |||
ComputeResource: r.ComputeResource, | |||
AccCardType: r.AccCardType, | |||
CardsTotalNum: r.CardsTotalNum, | |||
UpdatedTime: r.UpdatedTime, | |||
Remark: r.Remark, | |||
} | |||
} | |||
type ResourceQueueReq struct { | |||
QueueCode string | |||
Cluster string `binding:"Required"` | |||
AiCenterCode string | |||
ComputeResource string `binding:"Required"` | |||
AccCardType string `binding:"Required"` | |||
CardsTotalNum int | |||
CreatorId int64 | |||
IsAutomaticSync bool | |||
Remark string | |||
} | |||
func (r ResourceQueueReq) ToDTO() ResourceQueue { | |||
q := ResourceQueue{ | |||
QueueCode: r.QueueCode, | |||
Cluster: r.Cluster, | |||
AiCenterCode: r.AiCenterCode, | |||
ComputeResource: strings.ToUpper(r.ComputeResource), | |||
AccCardType: strings.ToUpper(r.AccCardType), | |||
CardsTotalNum: r.CardsTotalNum, | |||
IsAutomaticSync: r.IsAutomaticSync, | |||
Remark: r.Remark, | |||
CreatedBy: r.CreatorId, | |||
UpdatedBy: r.CreatorId, | |||
} | |||
if r.Cluster == OpenICluster { | |||
if r.AiCenterCode == AICenterOfCloudBrainOne { | |||
q.AiCenterName = "云脑一" | |||
} else if r.AiCenterCode == AICenterOfCloudBrainTwo { | |||
q.AiCenterName = "云脑二" | |||
} | |||
} | |||
return q | |||
} | |||
type SearchResourceQueueOptions struct { | |||
ListOptions | |||
Cluster string | |||
AiCenterCode string | |||
ComputeResource string | |||
AccCardType string | |||
} | |||
type ResourceQueueListRes struct { | |||
TotalSize int64 | |||
List []*ResourceQueueRes | |||
} | |||
type ResourceQueueCodesRes struct { | |||
ID int64 | |||
QueueCode string | |||
Cluster string | |||
AiCenterCode string | |||
AiCenterName string | |||
} | |||
func (ResourceQueueCodesRes) TableName() string { | |||
return "resource_queue" | |||
} | |||
type ResourceAiCenterRes struct { | |||
AiCenterCode string | |||
AiCenterName string | |||
} | |||
type GetQueueCodesOptions struct { | |||
Cluster string | |||
} | |||
func NewResourceQueueListRes(totalSize int64, list []ResourceQueue) *ResourceQueueListRes { | |||
resList := make([]*ResourceQueueRes, len(list)) | |||
for i, v := range list { | |||
resList[i] = v.ConvertToRes() | |||
} | |||
return &ResourceQueueListRes{ | |||
TotalSize: totalSize, | |||
List: resList, | |||
} | |||
} | |||
type ResourceQueueRes struct { | |||
ID int64 | |||
QueueCode string | |||
Cluster string | |||
AiCenterCode string | |||
AiCenterName string | |||
ComputeResource string | |||
AccCardType string | |||
CardsTotalNum int | |||
UpdatedTime timeutil.TimeStamp | |||
Remark string | |||
} | |||
func InsertResourceQueue(queue ResourceQueue) (int64, error) { | |||
return x.Insert(&queue) | |||
} | |||
func UpdateResourceQueueById(queueId int64, queue ResourceQueue) (int64, error) { | |||
return x.ID(queueId).Update(&queue) | |||
} | |||
func SearchResourceQueue(opts SearchResourceQueueOptions) (int64, []ResourceQueue, error) { | |||
var cond = builder.NewCond() | |||
if opts.Page <= 0 { | |||
opts.Page = 1 | |||
} | |||
if opts.Cluster != "" { | |||
cond = cond.And(builder.Eq{"cluster": opts.Cluster}) | |||
} | |||
if opts.AiCenterCode != "" { | |||
cond = cond.And(builder.Eq{"ai_center_code": opts.AiCenterCode}) | |||
} | |||
if opts.ComputeResource != "" { | |||
cond = cond.And(builder.Eq{"compute_resource": opts.ComputeResource}) | |||
} | |||
if opts.AccCardType != "" { | |||
cond = cond.And(builder.Eq{"acc_card_type": opts.AccCardType}) | |||
} | |||
n, err := x.Where(cond).Unscoped().Count(&ResourceQueue{}) | |||
if err != nil { | |||
return 0, nil, err | |||
} | |||
r := make([]ResourceQueue, 0) | |||
err = x.Where(cond).Desc("id").Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).Unscoped().Find(&r) | |||
if err != nil { | |||
return 0, nil, err | |||
} | |||
return n, r, nil | |||
} | |||
func GetResourceQueueCodes(opts GetQueueCodesOptions) ([]*ResourceQueueCodesRes, error) { | |||
cond := builder.NewCond() | |||
if opts.Cluster != "" { | |||
cond = cond.And(builder.Eq{"cluster": opts.Cluster}) | |||
} | |||
cond = cond.And(builder.Or(builder.IsNull{"deleted_time"}, builder.Eq{"deleted_time": 0})) | |||
r := make([]*ResourceQueueCodesRes, 0) | |||
err := x.Where(cond).OrderBy("cluster desc,ai_center_code asc").Find(&r) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return r, nil | |||
} | |||
func GetResourceQueue(r *ResourceQueue) (*ResourceQueue, error) { | |||
has, err := x.Get(r) | |||
if err != nil { | |||
return nil, err | |||
} else if !has { | |||
return nil, nil | |||
} | |||
return r, nil | |||
} | |||
func ParseComputeResourceFormGrampus(grampusDeviceKind string) string { | |||
t := strings.Split(grampusDeviceKind, "/") | |||
if len(t) < 2 { | |||
return "" | |||
} | |||
return strings.ToUpper(t[1]) | |||
} | |||
type MemSize struct { | |||
Sizes []string | |||
Hex int | |||
} | |||
var memSize = MemSize{Sizes: []string{"K", "M", "G", "T", "P", "E"}, Hex: 1000} | |||
var iMemSize = MemSize{Sizes: []string{"Ki", "Mi", "Gi", "Ti", "Pi", "Ei"}, Hex: 1024} | |||
func MatchMemSize(memSize MemSize, val string) (int, float32, error) { | |||
for i, v := range memSize.Sizes { | |||
if strings.HasSuffix(val, v) { | |||
s := strings.TrimSuffix(val, v) | |||
f, err := strconv.ParseFloat(s, 32) | |||
if err != nil { | |||
return 0, 0, err | |||
} | |||
return i, float32(f), nil | |||
} | |||
} | |||
return -1, 0, nil | |||
} | |||
//TransferMemSize transfer oldValue format from old index to new index | |||
//eg: memSize.Sizes = []string{"M", "G", "T", "P", "E"}, oldValue = 10 , oldIndex = 1 , newIndex = 0. it means transfer 10G to 10000M | |||
//so it returns 10000 | |||
func TransferMemSize(memSize MemSize, oldValue float32, oldIndex int, newIndex int) float32 { | |||
diff := oldIndex - newIndex | |||
r := oldValue | |||
if diff > 0 { | |||
r = oldValue * float32(diff) * float32(memSize.Hex) | |||
} else if diff < 0 { | |||
r = oldValue / float32(-1*diff) / float32(memSize.Hex) | |||
} | |||
return r | |||
} | |||
//ParseMemSize find the memSize which matches value's format,and parse the number from value | |||
func ParseMemSize(value string, memSize MemSize, newIndex int) (bool, float32, error) { | |||
index, r, err := MatchMemSize(memSize, value) | |||
if err != nil { | |||
return false, 0, err | |||
} | |||
if index < 0 { | |||
return false, 0, nil | |||
} | |||
return true, TransferMemSize(memSize, r, index, newIndex), nil | |||
} | |||
func ParseMemSizeFromGrampus(grampusMemSize string) (float32, error) { | |||
if grampusMemSize == "" { | |||
return 0, nil | |||
} | |||
memflag, memResult, err := ParseMemSize(grampusMemSize, memSize, 2) | |||
if err != nil { | |||
return 0, err | |||
} | |||
if memflag { | |||
return memResult, nil | |||
} | |||
iMemFlag, imemResult, err := ParseMemSize(grampusMemSize, iMemSize, 2) | |||
if err != nil { | |||
return 0, err | |||
} | |||
if iMemFlag { | |||
return imemResult, nil | |||
} | |||
return 0, errors.New("grampus memSize format error") | |||
} | |||
func SyncGrampusQueues(updateList []ResourceQueue, insertList []ResourceQueue, existIds []int64) error { | |||
sess := x.NewSession() | |||
var err error | |||
defer func() { | |||
if err != nil { | |||
sess.Rollback() | |||
} | |||
sess.Close() | |||
}() | |||
//delete queues that no longer exists | |||
deleteQueueIds := make([]int64, 0) | |||
queueCond := builder.NewCond() | |||
queueCond = queueCond.And(builder.NotIn("resource_queue.id", existIds)).And(builder.Eq{"resource_queue.cluster": C2NetCluster}) | |||
if err := sess.Cols("resource_queue.id").Table("resource_queue"). | |||
Where(queueCond).Find(&deleteQueueIds); err != nil { | |||
return err | |||
} | |||
if len(deleteQueueIds) > 0 { | |||
if _, err = sess.In("id", deleteQueueIds).Update(&ResourceQueue{Remark: "自动同步时被下架"}); err != nil { | |||
return err | |||
} | |||
if _, err = sess.In("id", deleteQueueIds).Delete(&ResourceQueue{}); err != nil { | |||
return err | |||
} | |||
//delete specs and scene that no longer exists | |||
deleteSpcIds := make([]int64, 0) | |||
if err := sess.Cols("resource_specification.id").Table("resource_specification"). | |||
In("queue_id", deleteQueueIds).Find(&deleteSpcIds); err != nil { | |||
return err | |||
} | |||
if len(deleteSpcIds) > 0 { | |||
if _, err = sess.In("id", deleteSpcIds).Update(&ResourceSpecification{Status: SpecOffShelf}); err != nil { | |||
return err | |||
} | |||
if _, err = sess.In("spec_id", deleteSpcIds).Delete(&ResourceSceneSpec{}); err != nil { | |||
return err | |||
} | |||
} | |||
} | |||
//update exists specs | |||
if len(updateList) > 0 { | |||
for _, v := range updateList { | |||
if _, err = sess.ID(v.ID).Update(&v); err != nil { | |||
return err | |||
} | |||
} | |||
} | |||
//insert new specs | |||
if len(insertList) > 0 { | |||
if _, err = sess.Insert(insertList); err != nil { | |||
return err | |||
} | |||
} | |||
return sess.Commit() | |||
} | |||
func GetResourceAiCenters() ([]ResourceAiCenterRes, error) { | |||
r := make([]ResourceAiCenterRes, 0) | |||
err := x.SQL("SELECT t.ai_center_code, t.ai_center_name FROM (SELECT DISTINCT ai_center_code, ai_center_name,cluster FROM resource_queue WHERE (deleted_time IS NULL OR deleted_time=0)) t ORDER BY cluster desc,ai_center_code asc").Find(&r) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return r, nil | |||
} |
@@ -0,0 +1,329 @@ | |||
package models | |||
import ( | |||
"code.gitea.io/gitea/modules/timeutil" | |||
"errors" | |||
"xorm.io/builder" | |||
) | |||
const ( | |||
Exclusive = iota + 1 | |||
NotExclusive | |||
) | |||
type ResourceScene struct { | |||
ID int64 `xorm:"pk autoincr"` | |||
SceneName string | |||
JobType string | |||
IsExclusive bool | |||
ExclusiveOrg string | |||
CreatedTime timeutil.TimeStamp `xorm:"created"` | |||
CreatedBy int64 | |||
UpdatedTime timeutil.TimeStamp `xorm:"updated"` | |||
UpdatedBy int64 | |||
DeleteTime timeutil.TimeStamp `xorm:"deleted"` | |||
DeletedBy int64 | |||
} | |||
type ResourceSceneSpec struct { | |||
ID int64 `xorm:"pk autoincr"` | |||
SceneId int64 `xorm:"unique(idx_scene_spec)"` | |||
SpecId int64 `xorm:"unique(idx_scene_spec)"` | |||
CreatedTime timeutil.TimeStamp `xorm:"created"` | |||
} | |||
type ResourceSceneReq struct { | |||
ID int64 | |||
SceneName string | |||
JobType string | |||
IsExclusive bool | |||
ExclusiveOrg string | |||
CreatorId int64 | |||
SpecIds []int64 | |||
} | |||
type SearchResourceSceneOptions struct { | |||
ListOptions | |||
JobType string | |||
IsExclusive int | |||
AiCenterCode string | |||
QueueId int64 | |||
} | |||
type ResourceSceneListRes struct { | |||
TotalSize int64 | |||
List []ResourceSceneRes | |||
} | |||
func NewResourceSceneListRes(totalSize int64, list []ResourceSceneRes) *ResourceSceneListRes { | |||
return &ResourceSceneListRes{ | |||
TotalSize: totalSize, | |||
List: list, | |||
} | |||
} | |||
type ResourceSceneRes struct { | |||
ID int64 | |||
SceneName string | |||
JobType JobType | |||
IsExclusive bool | |||
ExclusiveOrg string | |||
Specs []ResourceSpecWithSceneId | |||
} | |||
func (ResourceSceneRes) TableName() string { | |||
return "resource_scene" | |||
} | |||
type ResourceSceneBriefRes struct { | |||
ID int64 | |||
SceneName string | |||
} | |||
func (ResourceSceneBriefRes) TableName() string { | |||
return "resource_scene" | |||
} | |||
type ResourceSpecWithSceneId struct { | |||
ID int64 | |||
SourceSpecId string | |||
AccCardsNum int | |||
CpuCores int | |||
MemGiB float32 | |||
GPUMemGiB float32 | |||
ShareMemGiB float32 | |||
UnitPrice int | |||
Status int | |||
UpdatedTime timeutil.TimeStamp | |||
SceneId int64 | |||
//queue | |||
Cluster string | |||
AiCenterCode string | |||
AiCenterName string | |||
QueueCode string | |||
QueueId int64 | |||
ComputeResource string | |||
AccCardType string | |||
} | |||
func (ResourceSpecWithSceneId) TableName() string { | |||
return "resource_specification" | |||
} | |||
func InsertResourceScene(r ResourceSceneReq) error { | |||
sess := x.NewSession() | |||
defer sess.Close() | |||
//check | |||
specs := make([]ResourceSpecification, 0) | |||
cond := builder.In("id", r.SpecIds).And(builder.Eq{"status": SpecOnShelf}) | |||
if err := sess.Where(cond).Find(&specs); err != nil { | |||
return err | |||
} | |||
if len(specs) < len(r.SpecIds) { | |||
return errors.New("specIds not correct") | |||
} | |||
rs := ResourceScene{ | |||
SceneName: r.SceneName, | |||
JobType: r.JobType, | |||
IsExclusive: r.IsExclusive, | |||
ExclusiveOrg: r.ExclusiveOrg, | |||
CreatedBy: r.CreatorId, | |||
UpdatedBy: r.CreatorId, | |||
} | |||
_, err := sess.InsertOne(&rs) | |||
if err != nil { | |||
sess.Rollback() | |||
return err | |||
} | |||
if len(r.SpecIds) == 0 { | |||
return sess.Commit() | |||
} | |||
rss := make([]ResourceSceneSpec, len(r.SpecIds)) | |||
for i, v := range r.SpecIds { | |||
rss[i] = ResourceSceneSpec{ | |||
SceneId: rs.ID, | |||
SpecId: v, | |||
} | |||
} | |||
_, err = sess.Insert(&rss) | |||
if err != nil { | |||
sess.Rollback() | |||
return err | |||
} | |||
return sess.Commit() | |||
} | |||
func UpdateResourceScene(r ResourceSceneReq) error { | |||
sess := x.NewSession() | |||
var err error | |||
defer func() { | |||
if err != nil { | |||
sess.Rollback() | |||
} | |||
sess.Close() | |||
}() | |||
// find old scene | |||
old := ResourceScene{} | |||
if has, _ := sess.ID(r.ID).Get(&old); !has { | |||
return errors.New("ResourceScene not exist") | |||
} | |||
//check specification | |||
specs := make([]ResourceSpecification, 0) | |||
cond := builder.In("id", r.SpecIds).And(builder.Eq{"status": SpecOnShelf}) | |||
if err := sess.Where(cond).Find(&specs); err != nil { | |||
return err | |||
} | |||
if len(specs) < len(r.SpecIds) { | |||
return errors.New("specIds not correct") | |||
} | |||
//update scene | |||
rs := ResourceScene{ | |||
SceneName: r.SceneName, | |||
IsExclusive: r.IsExclusive, | |||
ExclusiveOrg: r.ExclusiveOrg, | |||
} | |||
if _, err = sess.ID(r.ID).UseBool("is_exclusive").Update(&rs); err != nil { | |||
return err | |||
} | |||
//delete scene spec relation | |||
if _, err = sess.Where("scene_id = ? ", r.ID).Delete(&ResourceSceneSpec{}); err != nil { | |||
sess.Rollback() | |||
return err | |||
} | |||
if len(r.SpecIds) == 0 { | |||
return sess.Commit() | |||
} | |||
//build new scene spec relation | |||
rss := make([]ResourceSceneSpec, len(r.SpecIds)) | |||
for i, v := range r.SpecIds { | |||
rss[i] = ResourceSceneSpec{ | |||
SceneId: r.ID, | |||
SpecId: v, | |||
} | |||
} | |||
if _, err = sess.Insert(&rss); err != nil { | |||
sess.Rollback() | |||
return err | |||
} | |||
return sess.Commit() | |||
} | |||
func DeleteResourceScene(sceneId int64) error { | |||
sess := x.NewSession() | |||
var err error | |||
defer func() { | |||
if err != nil { | |||
sess.Rollback() | |||
} | |||
sess.Close() | |||
}() | |||
if _, err = sess.ID(sceneId).Delete(&ResourceScene{}); err != nil { | |||
return err | |||
} | |||
if _, err = sess.Where("scene_id = ? ", sceneId).Delete(&ResourceSceneSpec{}); err != nil { | |||
return err | |||
} | |||
return sess.Commit() | |||
} | |||
func SearchResourceScene(opts SearchResourceSceneOptions) (int64, []ResourceSceneRes, error) { | |||
var cond = builder.NewCond() | |||
if opts.Page <= 0 { | |||
opts.Page = 1 | |||
} | |||
if opts.JobType != "" { | |||
cond = cond.And(builder.Eq{"resource_scene.job_type": opts.JobType}) | |||
} | |||
if opts.IsExclusive == Exclusive { | |||
cond = cond.And(builder.Eq{"resource_scene.is_exclusive": 1}) | |||
} else if opts.IsExclusive == NotExclusive { | |||
cond = cond.And(builder.Eq{"resource_scene.is_exclusive": 0}) | |||
} | |||
if opts.AiCenterCode != "" { | |||
cond = cond.And(builder.Eq{"resource_queue.ai_center_code": opts.AiCenterCode}) | |||
} | |||
if opts.QueueId > 0 { | |||
cond = cond.And(builder.Eq{"resource_queue.id": opts.QueueId}) | |||
} | |||
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"} | |||
count, err := x.Where(cond). | |||
Distinct("resource_scene.id"). | |||
Join("INNER", "resource_scene_spec", "resource_scene_spec.scene_id = resource_scene.id"). | |||
Join("INNER", "resource_specification", "resource_specification.id = resource_scene_spec.spec_id"). | |||
Join("INNER", "resource_queue", "resource_queue.id = resource_specification.queue_id"). | |||
Count(&ResourceSceneRes{}) | |||
if err != nil { | |||
return 0, nil, err | |||
} | |||
r := make([]ResourceSceneRes, 0) | |||
if err = x.Where(cond).Distinct(cols...). | |||
Join("INNER", "resource_scene_spec", "resource_scene_spec.scene_id = resource_scene.id"). | |||
Join("INNER", "resource_specification", "resource_specification.id = resource_scene_spec.spec_id"). | |||
Join("INNER", "resource_queue", "resource_queue.id = resource_specification.queue_id"). | |||
Desc("resource_scene.id"). | |||
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize). | |||
Find(&r); err != nil { | |||
return 0, nil, err | |||
} | |||
if len(r) == 0 { | |||
return 0, r, err | |||
} | |||
//find related specs | |||
sceneIds := make([]int64, 0, len(r)) | |||
for _, v := range r { | |||
sceneIds = append(sceneIds, v.ID) | |||
} | |||
specs := make([]ResourceSpecWithSceneId, 0) | |||
if err := x.Cols("resource_specification.id", "resource_specification.source_spec_id", | |||
"resource_specification.acc_cards_num", "resource_specification.cpu_cores", | |||
"resource_specification.mem_gi_b", "resource_specification.gpu_mem_gi_b", | |||
"resource_specification.share_mem_gi_b", "resource_specification.unit_price", | |||
"resource_specification.status", "resource_specification.updated_time", | |||
"resource_scene_spec.scene_id", "resource_queue.cluster", | |||
"resource_queue.ai_center_code", "resource_queue.acc_card_type", | |||
"resource_queue.id as queue_id", "resource_queue.compute_resource", | |||
"resource_queue.queue_code", "resource_queue.ai_center_name", | |||
).In("resource_scene_spec.scene_id", sceneIds). | |||
Join("INNER", "resource_scene_spec", "resource_scene_spec.spec_id = resource_specification.id"). | |||
Join("INNER", "resource_queue", "resource_queue.ID = resource_specification.queue_id"). | |||
OrderBy("resource_specification.acc_cards_num"). | |||
Find(&specs); err != nil { | |||
return 0, nil, err | |||
} | |||
specsMap := make(map[int64][]ResourceSpecWithSceneId, 0) | |||
for _, v := range specs { | |||
if _, ok := specsMap[v.SceneId]; !ok { | |||
specsMap[v.SceneId] = []ResourceSpecWithSceneId{v} | |||
} else { | |||
specsMap[v.SceneId] = append(specsMap[v.SceneId], v) | |||
} | |||
} | |||
for i, v := range r { | |||
s := specsMap[v.ID] | |||
if s == nil { | |||
s = make([]ResourceSpecWithSceneId, 0) | |||
} | |||
r[i].Specs = s | |||
} | |||
return count, r, nil | |||
} |
@@ -0,0 +1,285 @@ | |||
package models | |||
import ( | |||
"code.gitea.io/gitea/modules/timeutil" | |||
"xorm.io/builder" | |||
) | |||
const ( | |||
SpecNotVerified int = iota + 1 | |||
SpecOnShelf | |||
SpecOffShelf | |||
) | |||
type ResourceSpecification struct { | |||
ID int64 `xorm:"pk autoincr"` | |||
QueueId int64 `xorm:"INDEX"` | |||
SourceSpecId string `xorm:"INDEX"` | |||
AccCardsNum int | |||
CpuCores int | |||
MemGiB float32 | |||
GPUMemGiB float32 | |||
ShareMemGiB float32 | |||
UnitPrice int | |||
Status int | |||
IsAutomaticSync bool | |||
CreatedTime timeutil.TimeStamp `xorm:"created"` | |||
CreatedBy int64 | |||
UpdatedTime timeutil.TimeStamp `xorm:"updated"` | |||
UpdatedBy int64 | |||
} | |||
func (r ResourceSpecification) ConvertToRes() *ResourceSpecificationRes { | |||
return &ResourceSpecificationRes{ | |||
ID: r.ID, | |||
SourceSpecId: r.SourceSpecId, | |||
AccCardsNum: r.AccCardsNum, | |||
CpuCores: r.CpuCores, | |||
MemGiB: r.MemGiB, | |||
ShareMemGiB: r.ShareMemGiB, | |||
GPUMemGiB: r.GPUMemGiB, | |||
UnitPrice: r.UnitPrice, | |||
Status: r.Status, | |||
UpdatedTime: r.UpdatedTime, | |||
} | |||
} | |||
type ResourceSpecificationReq struct { | |||
QueueId int64 `binding:"Required"` | |||
SourceSpecId string | |||
AccCardsNum int | |||
CpuCores int | |||
MemGiB float32 | |||
GPUMemGiB float32 | |||
ShareMemGiB float32 | |||
UnitPrice int | |||
Status int | |||
IsAutomaticSync bool | |||
CreatorId int64 | |||
} | |||
func (r ResourceSpecificationReq) ToDTO() ResourceSpecification { | |||
return ResourceSpecification{ | |||
QueueId: r.QueueId, | |||
SourceSpecId: r.SourceSpecId, | |||
AccCardsNum: r.AccCardsNum, | |||
CpuCores: r.CpuCores, | |||
MemGiB: r.MemGiB, | |||
GPUMemGiB: r.GPUMemGiB, | |||
ShareMemGiB: r.ShareMemGiB, | |||
UnitPrice: r.UnitPrice, | |||
Status: r.Status, | |||
IsAutomaticSync: r.IsAutomaticSync, | |||
CreatedBy: r.CreatorId, | |||
UpdatedBy: r.CreatorId, | |||
} | |||
} | |||
type SearchResourceSpecificationOptions struct { | |||
ListOptions | |||
QueueId int64 | |||
Status int | |||
Cluster string | |||
} | |||
type SearchResourceBriefSpecificationOptions struct { | |||
QueueId int64 | |||
Cluster string | |||
} | |||
type ResourceSpecAndQueueListRes struct { | |||
TotalSize int64 | |||
List []*ResourceSpecAndQueueRes | |||
} | |||
func NewResourceSpecAndQueueListRes(totalSize int64, list []ResourceSpecAndQueue) *ResourceSpecAndQueueListRes { | |||
resList := make([]*ResourceSpecAndQueueRes, len(list)) | |||
for i, v := range list { | |||
resList[i] = v.ConvertToRes() | |||
} | |||
return &ResourceSpecAndQueueListRes{ | |||
TotalSize: totalSize, | |||
List: resList, | |||
} | |||
} | |||
type ResourceSpecificationRes struct { | |||
ID int64 | |||
SourceSpecId string | |||
AccCardsNum int | |||
CpuCores int | |||
MemGiB float32 | |||
GPUMemGiB float32 | |||
ShareMemGiB float32 | |||
UnitPrice int | |||
Status int | |||
UpdatedTime timeutil.TimeStamp | |||
} | |||
func (ResourceSpecificationRes) TableName() string { | |||
return "resource_specification" | |||
} | |||
type ResourceSpecAndQueueRes struct { | |||
Spec *ResourceSpecificationRes | |||
Queue *ResourceQueueRes | |||
} | |||
type ResourceSpecAndQueue struct { | |||
ResourceSpecification `xorm:"extends"` | |||
ResourceQueue `xorm:"extends"` | |||
} | |||
func (*ResourceSpecAndQueue) TableName() string { | |||
return "resource_specification" | |||
} | |||
func (r ResourceSpecAndQueue) ConvertToRes() *ResourceSpecAndQueueRes { | |||
return &ResourceSpecAndQueueRes{ | |||
Spec: r.ResourceSpecification.ConvertToRes(), | |||
Queue: r.ResourceQueue.ConvertToRes(), | |||
} | |||
} | |||
func InsertResourceSpecification(r ResourceSpecification) (int64, error) { | |||
return x.Insert(&r) | |||
} | |||
func UpdateResourceSpecificationById(queueId int64, spec ResourceSpecification) (int64, error) { | |||
return x.ID(queueId).Update(&spec) | |||
} | |||
func UpdateSpecUnitPriceById(id int64, unitPrice int) error { | |||
_, err := x.Exec("update resource_specification set unit_price = ? ,updated_time = ? where id = ?", unitPrice, timeutil.TimeStampNow(), id) | |||
return err | |||
} | |||
func SearchResourceSpecification(opts SearchResourceSpecificationOptions) (int64, []ResourceSpecAndQueue, error) { | |||
var cond = builder.NewCond() | |||
if opts.Page <= 0 { | |||
opts.Page = 1 | |||
} | |||
if opts.QueueId > 0 { | |||
cond = cond.And(builder.Eq{"resource_specification.queue_id": opts.QueueId}) | |||
} | |||
if opts.Status > 0 { | |||
cond = cond.And(builder.Eq{"resource_specification.status": opts.Status}) | |||
} | |||
if opts.Cluster != "" { | |||
cond = cond.And(builder.Eq{"resource_queue.cluster": opts.Cluster}) | |||
} | |||
//cond = cond.And(builder.Or(builder.Eq{"resource_queue.deleted_time": 0}).Or(builder.IsNull{"resource_queue.deleted_time"})) | |||
n, err := x.Where(cond).Join("INNER", "resource_queue", "resource_queue.ID = resource_specification.queue_id"). | |||
Unscoped().Count(&ResourceSpecAndQueue{}) | |||
if err != nil { | |||
return 0, nil, err | |||
} | |||
r := make([]ResourceSpecAndQueue, 0) | |||
err = x.Where(cond). | |||
Join("INNER", "resource_queue", "resource_queue.ID = resource_specification.queue_id"). | |||
Desc("resource_specification.id"). | |||
Limit(opts.PageSize, (opts.Page-1)*opts.PageSize). | |||
Unscoped().Find(&r) | |||
if err != nil { | |||
return 0, nil, err | |||
} | |||
return n, r, nil | |||
} | |||
func GetSpecScenes(specId int64) ([]ResourceSceneBriefRes, error) { | |||
r := make([]ResourceSceneBriefRes, 0) | |||
err := x.Where("resource_scene_spec.spec_id = ?", specId). | |||
Join("INNER", "resource_scene_spec", "resource_scene_spec.scene_id = resource_scene.id"). | |||
Find(&r) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return r, nil | |||
} | |||
func ResourceSpecOnShelf(id int64, unitPrice int) error { | |||
_, err := x.Exec("update resource_specification set unit_price = ?,updated_time = ?,status = ? where id = ?", unitPrice, timeutil.TimeStampNow(), SpecOnShelf, id) | |||
return err | |||
} | |||
func ResourceSpecOffShelf(id int64) (int64, error) { | |||
sess := x.NewSession() | |||
var err error | |||
defer func() { | |||
if err != nil { | |||
sess.Rollback() | |||
} | |||
sess.Close() | |||
}() | |||
//delete scene spec relation | |||
if _, err = sess.Where("spec_id = ?", id).Delete(&ResourceSceneSpec{}); err != nil { | |||
return 0, err | |||
} | |||
param := ResourceSpecification{ | |||
Status: SpecOffShelf, | |||
} | |||
n, err := sess.Where("id = ? and status = ?", id, SpecOnShelf).Update(¶m) | |||
if err != nil { | |||
return 0, err | |||
} | |||
sess.Commit() | |||
return n, err | |||
} | |||
func GetResourceSpecification(r *ResourceSpecification) (*ResourceSpecification, error) { | |||
has, err := x.Get(r) | |||
if err != nil { | |||
return nil, err | |||
} else if !has { | |||
return nil, nil | |||
} | |||
return r, nil | |||
} | |||
func SyncGrampusSpecs(updateList []ResourceSpecification, insertList []ResourceSpecification, existIds []int64) error { | |||
sess := x.NewSession() | |||
var err error | |||
defer func() { | |||
if err != nil { | |||
sess.Rollback() | |||
} | |||
sess.Close() | |||
}() | |||
//delete specs and scene that no longer exists | |||
deleteIds := make([]int64, 0) | |||
cond := builder.NewCond() | |||
cond = cond.And(builder.NotIn("resource_specification.id", existIds)).And(builder.Eq{"resource_queue.cluster": C2NetCluster}) | |||
if err := sess.Cols("resource_specification.id").Table("resource_specification"). | |||
Where(cond).Join("INNER", "resource_queue", "resource_queue.id = resource_specification.queue_id"). | |||
Find(&deleteIds); err != nil { | |||
return err | |||
} | |||
if len(deleteIds) > 0 { | |||
if _, err = sess.In("id", deleteIds).Update(&ResourceSpecification{Status: SpecOffShelf}); err != nil { | |||
return err | |||
} | |||
if _, err = sess.In("spec_id", deleteIds).Delete(&ResourceSceneSpec{}); err != nil { | |||
return err | |||
} | |||
} | |||
//update exists specs | |||
if len(updateList) > 0 { | |||
for _, v := range updateList { | |||
if _, err = sess.ID(v.ID).Update(&v); err != nil { | |||
return err | |||
} | |||
} | |||
} | |||
//insert new specs | |||
if len(insertList) > 0 { | |||
if _, err = sess.Insert(insertList); err != nil { | |||
return err | |||
} | |||
} | |||
return sess.Commit() | |||
} |
@@ -5,6 +5,7 @@ | |||
package cron | |||
import ( | |||
"code.gitea.io/gitea/services/cloudbrain/resource" | |||
"code.gitea.io/gitea/modules/modelarts" | |||
"context" | |||
"time" | |||
@@ -208,6 +209,17 @@ func registerSyncCloudbrainStatus() { | |||
}) | |||
} | |||
func registerSyncResourceSpecs() { | |||
RegisterTaskFatal("sync_grampus_specs", &BaseConfig{ | |||
Enabled: true, | |||
RunAtStart: true, | |||
Schedule: "0 0 1 * * ?", | |||
}, func(ctx context.Context, _ *models.User, _ Config) error { | |||
resource.SyncGrampusQueueAndSpecs() | |||
return nil | |||
}) | |||
} | |||
func registerSyncModelArtsTempJobs() { | |||
RegisterTaskFatal("sync_model_arts_temp_jobs", &BaseConfig{ | |||
Enabled: true, | |||
@@ -239,5 +251,6 @@ func initBasicTasks() { | |||
registerSyncCloudbrainStatus() | |||
registerHandleOrgStatistic() | |||
registerSyncResourceSpecs() | |||
registerSyncModelArtsTempJobs() | |||
} |
@@ -23,6 +23,7 @@ const ( | |||
urlGetToken = urlOpenApiV1 + "token" | |||
urlTrainJob = urlOpenApiV1 + "trainjob" | |||
urlGetResourceSpecs = urlOpenApiV1 + "resourcespec" | |||
urlGetAiCenter = urlOpenApiV1 + "sharescreen/aicenter" | |||
urlGetImages = urlOpenApiV1 + "image" | |||
errorIllegalToken = 1005 | |||
@@ -275,3 +276,35 @@ sendjob: | |||
return &result, nil | |||
} | |||
func GetAiCenters(pageIndex, pageSize int) (*models.GetGrampusAiCentersResult, error) { | |||
checkSetting() | |||
client := getRestyClient() | |||
var result models.GetGrampusAiCentersResult | |||
retry := 0 | |||
sendjob: | |||
_, err := client.R(). | |||
SetAuthToken(TOKEN). | |||
SetResult(&result). | |||
Get(HOST + urlGetAiCenter + "?pageIndex=" + fmt.Sprint(pageIndex) + "&pageSize=" + fmt.Sprint(pageSize)) | |||
if err != nil { | |||
return nil, fmt.Errorf("resty GetAiCenters: %v", err) | |||
} | |||
if result.ErrorCode == errorIllegalToken && retry < 1 { | |||
retry++ | |||
log.Info("retry get token") | |||
_ = getToken() | |||
goto sendjob | |||
} | |||
if result.ErrorCode != 0 { | |||
log.Error("GetAiCenters failed(%d): %s", result.ErrorCode, result.ErrorMsg) | |||
return &result, fmt.Errorf("GetAiCenters failed(%d): %s", result.ErrorCode, result.ErrorMsg) | |||
} | |||
return &result, nil | |||
} |
@@ -3024,6 +3024,13 @@ notices.desc = Description | |||
notices.op = Op. | |||
notices.delete_success = The system notices have been deleted. | |||
user_management = User Management | |||
resource_management = Resource Management | |||
resource_pool = Resource Pool(queue) | |||
resource_price = Resource Price | |||
application_scenario = Application Scenario | |||
system_configuration = System Configuration | |||
[action] | |||
create_repo = created repository <a href="%s">%s</a> | |||
rename_repo = renamed repository from <code>%[1]s</code> to <a href="%[2]s">%[3]s</a> | |||
@@ -3040,6 +3040,13 @@ notices.desc=提示描述 | |||
notices.op=操作 | |||
notices.delete_success=系统通知已被删除。 | |||
user_management = 用户管理 | |||
resource_management = 资源管理 | |||
resource_pool = 资源池(队列) | |||
resource_price = 资源规格单价 | |||
application_scenario = 应用场景 | |||
system_configuration = 系统配置 | |||
[action] | |||
create_repo=创建了项目 <a href="%s">%s</a> | |||
rename_repo=重命名项目 <code>%[1]s</code> 为 <a href="%[2]s">%[3]s</a> | |||
@@ -54,6 +54,7 @@ | |||
"vue": "2.6.11", | |||
"vue-bar-graph": "1.2.0", | |||
"vue-calendar-heatmap": "0.8.4", | |||
"vue-i18n": "6.1.3", | |||
"vue-loader": "15.9.2", | |||
"vue-router": "3.3.4", | |||
"vue-template-compiler": "2.6.11", | |||
@@ -0,0 +1,248 @@ | |||
package admin | |||
import ( | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/base" | |||
"code.gitea.io/gitea/modules/context" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/routers/response" | |||
"code.gitea.io/gitea/services/cloudbrain/resource" | |||
"net/http" | |||
) | |||
const ( | |||
tplResourceQueue base.TplName = "admin/resources/queue" | |||
tplResourceSpecification base.TplName = "admin/resources/specification" | |||
tplResourceScene base.TplName = "admin/resources/scene" | |||
) | |||
func GetQueuePage(ctx *context.Context) { | |||
ctx.Data["PageIsAdmin"] = true | |||
ctx.Data["PageIsAdminResources"] = true | |||
ctx.Data["PageIsAdminResourcesQueue"] = true | |||
ctx.HTML(200, tplResourceQueue) | |||
} | |||
func GetSpecificationPage(ctx *context.Context) { | |||
ctx.Data["PageIsAdmin"] = true | |||
ctx.Data["PageIsAdminResources"] = true | |||
ctx.Data["PageIsAdminResourcesSpecification"] = true | |||
ctx.HTML(200, tplResourceSpecification) | |||
} | |||
func GetScenePage(ctx *context.Context) { | |||
ctx.Data["PageIsAdmin"] = true | |||
ctx.Data["PageIsAdminResources"] = true | |||
ctx.Data["PageIsAdminResourcesScene"] = true | |||
ctx.HTML(200, tplResourceScene) | |||
} | |||
func GetResourceQueueList(ctx *context.Context) { | |||
page := ctx.QueryInt("page") | |||
cluster := ctx.Query("cluster") | |||
aiCenterCode := ctx.Query("center") | |||
computeResource := ctx.Query("resource") | |||
accCardType := ctx.Query("card") | |||
list, err := resource.GetResourceQueueList(models.SearchResourceQueueOptions{ | |||
ListOptions: models.ListOptions{Page: page, PageSize: 10}, | |||
Cluster: cluster, | |||
AiCenterCode: aiCenterCode, | |||
ComputeResource: computeResource, | |||
AccCardType: accCardType, | |||
}) | |||
if err != nil { | |||
log.Error("GetResourceQueueList error.%v", err) | |||
ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, response.SuccessWithData(list)) | |||
} | |||
func GetResourceQueueCodes(ctx *context.Context) { | |||
cluster := ctx.Query("cluster") | |||
list, err := resource.GetResourceQueueCodes(models.GetQueueCodesOptions{Cluster: cluster}) | |||
if err != nil { | |||
log.Error("GetResourceQueueCodes error.%v", err) | |||
ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, response.SuccessWithData(list)) | |||
} | |||
func GetResourceAiCenters(ctx *context.Context) { | |||
list, err := resource.GetResourceAiCenters() | |||
if err != nil { | |||
log.Error("GetResourceAiCenters error.%v", err) | |||
ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, response.SuccessWithData(list)) | |||
} | |||
func AddResourceQueue(ctx *context.Context, req models.ResourceQueueReq) { | |||
req.IsAutomaticSync = false | |||
req.CreatorId = ctx.User.ID | |||
err := resource.AddResourceQueue(req) | |||
if err != nil { | |||
log.Error("AddResourceQueue error. %v", err) | |||
ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, response.Success()) | |||
} | |||
func UpdateResourceQueue(ctx *context.Context, req models.ResourceQueueReq) { | |||
queueId := ctx.ParamsInt64(":id") | |||
//only CardsTotalNum permitted to change | |||
err := resource.UpdateResourceQueue(queueId, req) | |||
if err != nil { | |||
log.Error("UpdateResourceQueue error. %v", err) | |||
ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, response.Success()) | |||
} | |||
func SyncGrampusQueue(ctx *context.Context) { | |||
err := resource.SyncGrampusQueue(ctx.User.ID) | |||
if err != nil { | |||
log.Error("AddResourceQueue error. %v", err) | |||
ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, response.Success()) | |||
} | |||
func GetResourceSpecificationList(ctx *context.Context) { | |||
page := ctx.QueryInt("page") | |||
queue := ctx.QueryInt64("queue") | |||
status := ctx.QueryInt("status") | |||
cluster := ctx.Query("cluster") | |||
list, err := resource.GetResourceSpecificationList(models.SearchResourceSpecificationOptions{ | |||
ListOptions: models.ListOptions{Page: page, PageSize: 10}, | |||
QueueId: queue, | |||
Status: status, | |||
Cluster: cluster, | |||
}) | |||
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)) | |||
} | |||
func GetResourceSpecificationScenes(ctx *context.Context) { | |||
specId := ctx.ParamsInt64(":id") | |||
list, err := resource.GetResourceSpecificationScenes(specId) | |||
if err != nil { | |||
log.Error("GetResourceSpecificationScenes error.%v", err) | |||
ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
return | |||
} | |||
r := make(map[string]interface{}) | |||
r["List"] = list | |||
ctx.JSON(http.StatusOK, response.SuccessWithData(r)) | |||
} | |||
func AddResourceSpecification(ctx *context.Context, req models.ResourceSpecificationReq) { | |||
req.IsAutomaticSync = false | |||
req.CreatorId = ctx.User.ID | |||
err := resource.AddResourceSpecification(ctx.User.ID, req) | |||
if err != nil { | |||
log.Error("AddResourceQueue error. %v", err) | |||
ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, response.Success()) | |||
} | |||
func UpdateResourceSpecification(ctx *context.Context, req models.ResourceSpecificationReq) { | |||
id := ctx.ParamsInt64(":id") | |||
action := ctx.Query("action") | |||
var err *response.BizError | |||
switch action { | |||
case "edit": | |||
if req.UnitPrice < 0 { | |||
ctx.JSON(http.StatusOK, response.ServerError("param error")) | |||
return | |||
} | |||
//only UnitPrice and permitted to change | |||
err = resource.UpdateSpecUnitPrice(ctx.User.ID, id, req.UnitPrice) | |||
case "on-shelf": | |||
err = resource.ResourceSpecOnShelf(ctx.User.ID, id, req.UnitPrice) | |||
case "off-shelf": | |||
err = resource.ResourceSpecOffShelf(ctx.User.ID, id) | |||
} | |||
if err != nil { | |||
log.Error("UpdateResourceSpecification error. %v", err) | |||
ctx.JSON(http.StatusOK, response.ResponseError(err)) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, response.Success()) | |||
} | |||
func SyncGrampusSpecs(ctx *context.Context) { | |||
err := resource.SyncGrampusSpecs(ctx.User.ID) | |||
if err != nil { | |||
log.Error("AddResourceQueue error. %v", err) | |||
ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, response.Success()) | |||
} | |||
func GetResourceSceneList(ctx *context.Context) { | |||
page := ctx.QueryInt("page") | |||
jobType := ctx.Query("jobType") | |||
aiCenterCode := ctx.Query("center") | |||
queueId := ctx.QueryInt64("queue") | |||
isExclusive := ctx.QueryInt("IsExclusive") | |||
list, err := resource.GetResourceSceneList(models.SearchResourceSceneOptions{ | |||
ListOptions: models.ListOptions{Page: page, PageSize: 10}, | |||
JobType: jobType, | |||
IsExclusive: isExclusive, | |||
AiCenterCode: aiCenterCode, | |||
QueueId: queueId, | |||
}) | |||
if err != nil { | |||
log.Error("GetResourceSceneList error.%v", err) | |||
ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, response.SuccessWithData(list)) | |||
} | |||
func AddResourceScene(ctx *context.Context, req models.ResourceSceneReq) { | |||
req.CreatorId = ctx.User.ID | |||
err := resource.AddResourceScene(req) | |||
if err != nil { | |||
log.Error("AddResourceScene error. %v", err) | |||
ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, response.Success()) | |||
} | |||
func UpdateResourceScene(ctx *context.Context, req models.ResourceSceneReq) { | |||
id := ctx.ParamsInt64(":id") | |||
action := ctx.Query("action") | |||
req.ID = id | |||
var err error | |||
switch action { | |||
case "edit": | |||
err = resource.UpdateResourceScene(req) | |||
case "delete": | |||
err = resource.DeleteResourceScene(id) | |||
} | |||
if err != nil { | |||
log.Error("UpdateResourceScene error. %v", err) | |||
ctx.JSON(http.StatusOK, response.ServerError(err.Error())) | |||
return | |||
} | |||
ctx.JSON(http.StatusOK, response.Success()) | |||
} |
@@ -0,0 +1,14 @@ | |||
package response | |||
type BizError struct { | |||
Code int | |||
Err string | |||
} | |||
func (b BizError) Error() string { | |||
return b.Err | |||
} | |||
func NewBizError(err error) *BizError { | |||
return &BizError{Code: RESPONSE_CODE_ERROR_DEFAULT, Err: err.Error()} | |||
} |
@@ -24,8 +24,12 @@ func ServerError(msg string) *AiforgeResponse { | |||
return &AiforgeResponse{Code: RESPONSE_CODE_ERROR_DEFAULT, Msg: msg} | |||
} | |||
func ResponseError(err *BizError) *AiforgeResponse { | |||
return &AiforgeResponse{Code: err.Code, Msg: err.Err} | |||
} | |||
func SuccessWithData(data interface{}) *AiforgeResponse { | |||
return &AiforgeResponse{Code: RESPONSE_CODE_ERROR_DEFAULT, Msg: RESPONSE_MSG_SUCCESS, Data: data} | |||
return &AiforgeResponse{Code: RESPONSE_CODE_SUCCESS, Msg: RESPONSE_MSG_SUCCESS, Data: data} | |||
} | |||
func ErrorWithData(code int, msg string, data interface{}) *AiforgeResponse { | |||
return &AiforgeResponse{Code: code, Msg: msg, Data: data} | |||
@@ -0,0 +1,4 @@ | |||
package response | |||
var RESOURCE_QUEUE_NOT_AVAILABLE = &BizError{Code: 1001, Err: "resource queue not available"} | |||
var SPECIFICATION_NOT_EXIST = &BizError{Code: 1002, Err: "specification not exist"} |
@@ -605,6 +605,32 @@ func RegisterRoutes(m *macaron.Macaron) { | |||
m.Post("/delete", admin.DeleteNotices) | |||
m.Post("/empty", admin.EmptyNotices) | |||
}) | |||
m.Group("/resources", func() { | |||
m.Group("/queue", func() { | |||
m.Get("", admin.GetQueuePage) | |||
m.Get("/list", admin.GetResourceQueueList) | |||
m.Post("/grampus/sync", admin.SyncGrampusQueue) | |||
m.Get("/codes", admin.GetResourceQueueCodes) | |||
m.Get("/centers", admin.GetResourceAiCenters) | |||
m.Post("/add", binding.Bind(models.ResourceQueueReq{}), admin.AddResourceQueue) | |||
m.Post("/update/:id", binding.BindIgnErr(models.ResourceQueueReq{}), admin.UpdateResourceQueue) | |||
}) | |||
m.Group("/specification", func() { | |||
m.Get("", admin.GetSpecificationPage) | |||
m.Get("/list", admin.GetResourceSpecificationList) | |||
m.Get("/scenes/:id", admin.GetResourceSpecificationScenes) | |||
m.Post("/grampus/sync", admin.SyncGrampusSpecs) | |||
m.Post("/add", binding.Bind(models.ResourceSpecificationReq{}), admin.AddResourceSpecification) | |||
m.Post("/update/:id", binding.BindIgnErr(models.ResourceSpecificationReq{}), admin.UpdateResourceSpecification) | |||
}) | |||
m.Group("/scene", func() { | |||
m.Get("", admin.GetScenePage) | |||
m.Get("/list", admin.GetResourceSceneList) | |||
m.Post("/add", binding.Bind(models.ResourceSceneReq{}), admin.AddResourceScene) | |||
m.Post("/update/:id", binding.BindIgnErr(models.ResourceSceneReq{}), admin.UpdateResourceScene) | |||
}) | |||
}) | |||
}, adminReq) | |||
// ***** END: Admin ***** | |||
@@ -0,0 +1,14 @@ | |||
package operate_log | |||
import ( | |||
"code.gitea.io/gitea/models" | |||
) | |||
func Log(log models.AdminOperateLog) error { | |||
_, err := models.InsertAdminOperateLog(log) | |||
return err | |||
} | |||
func NewLogValues() *models.LogValues { | |||
return &models.LogValues{Params: make([]models.LogValue, 0)} | |||
} |
@@ -0,0 +1,122 @@ | |||
package resource | |||
import ( | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/grampus" | |||
"code.gitea.io/gitea/modules/log" | |||
"fmt" | |||
"strings" | |||
) | |||
func AddResourceQueue(req models.ResourceQueueReq) error { | |||
if _, err := models.InsertResourceQueue(req.ToDTO()); err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
func UpdateResourceQueue(queueId int64, req models.ResourceQueueReq) error { | |||
if _, err := models.UpdateResourceQueueById(queueId, models.ResourceQueue{ | |||
CardsTotalNum: req.CardsTotalNum, | |||
Remark: req.Remark, | |||
}); err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
func GetResourceQueueList(opts models.SearchResourceQueueOptions) (*models.ResourceQueueListRes, error) { | |||
n, r, err := models.SearchResourceQueue(opts) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return models.NewResourceQueueListRes(n, r), nil | |||
} | |||
func GetResourceQueueCodes(opts models.GetQueueCodesOptions) ([]*models.ResourceQueueCodesRes, error) { | |||
r, err := models.GetResourceQueueCodes(opts) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return r, nil | |||
} | |||
func GetResourceAiCenters() ([]models.ResourceAiCenterRes, error) { | |||
r, err := models.GetResourceAiCenters() | |||
if err != nil { | |||
return nil, err | |||
} | |||
return r, nil | |||
} | |||
func SyncGrampusQueue(doerId int64) error { | |||
r, err := grampus.GetAiCenters(1, 100) | |||
if err != nil { | |||
return err | |||
} | |||
log.Info("SyncGrampusQueue result = %+v", r) | |||
queueUpdateList := make([]models.ResourceQueue, 0) | |||
queueInsertList := make([]models.ResourceQueue, 0) | |||
existIds := make([]int64, 0) | |||
for _, center := range r.Infos { | |||
for _, device := range center.AccDevices { | |||
computeResource := models.ParseComputeResourceFormGrampus(device.Kind) | |||
accCardType := strings.ToUpper(device.Model) | |||
if computeResource == "" { | |||
continue | |||
} | |||
//Determine if this quque already exists.if exist,update params | |||
//if not exist,insert a new record | |||
oldQueue, err := models.GetResourceQueue(&models.ResourceQueue{ | |||
Cluster: models.C2NetCluster, | |||
AiCenterCode: center.Id, | |||
ComputeResource: computeResource, | |||
AccCardType: accCardType, | |||
}) | |||
if err != nil { | |||
return err | |||
} | |||
if oldQueue == nil { | |||
queueInsertList = append(queueInsertList, models.ResourceQueue{ | |||
Cluster: models.C2NetCluster, | |||
AiCenterCode: center.Id, | |||
AiCenterName: center.Name, | |||
ComputeResource: computeResource, | |||
AccCardType: accCardType, | |||
IsAutomaticSync: true, | |||
CreatedBy: doerId, | |||
UpdatedBy: doerId, | |||
}) | |||
} else { | |||
existIds = append(existIds, oldQueue.ID) | |||
queueUpdateList = append(queueUpdateList, models.ResourceQueue{ | |||
ID: oldQueue.ID, | |||
ComputeResource: computeResource, | |||
AiCenterName: center.Name, | |||
AccCardType: accCardType, | |||
UpdatedBy: doerId, | |||
}) | |||
} | |||
} | |||
} | |||
return models.SyncGrampusQueues(queueUpdateList, queueInsertList, existIds) | |||
} | |||
func SyncGrampusQueueAndSpecs() { | |||
defer func() { | |||
if err := recover(); err != nil { | |||
combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2)) | |||
log.Error("PANIC:", combinedErr) | |||
} | |||
}() | |||
log.Info("start to sync grampus queue and specs") | |||
SyncGrampusQueue(0) | |||
SyncGrampusSpecs(0) | |||
log.Info("sync grampus queue and specs finished") | |||
} |
@@ -0,0 +1,35 @@ | |||
package resource | |||
import ( | |||
"code.gitea.io/gitea/models" | |||
) | |||
func AddResourceScene(req models.ResourceSceneReq) error { | |||
if err := models.InsertResourceScene(req); err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
func UpdateResourceScene(req models.ResourceSceneReq) error { | |||
if err := models.UpdateResourceScene(req); err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
func DeleteResourceScene(id int64) error { | |||
if err := models.DeleteResourceScene(id); err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
func GetResourceSceneList(opts models.SearchResourceSceneOptions) (*models.ResourceSceneListRes, error) { | |||
n, r, err := models.SearchResourceScene(opts) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return models.NewResourceSceneListRes(n, r), nil | |||
} |
@@ -0,0 +1,186 @@ | |||
package resource | |||
import ( | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/grampus" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/routers/response" | |||
"code.gitea.io/gitea/services/admin/operate_log" | |||
"fmt" | |||
"strings" | |||
) | |||
func AddResourceSpecification(doerId int64, req models.ResourceSpecificationReq) error { | |||
if req.Status == 0 { | |||
req.Status = models.SpecNotVerified | |||
} | |||
spec := req.ToDTO() | |||
if _, err := models.InsertResourceSpecification(spec); err != nil { | |||
return err | |||
} | |||
return nil | |||
} | |||
func UpdateSpecUnitPrice(doerId int64, specId int64, unitPrice int) *response.BizError { | |||
oldSpec, err := models.GetResourceSpecification(&models.ResourceSpecification{ID: specId}) | |||
if err != nil { | |||
return response.NewBizError(err) | |||
} | |||
if oldSpec == nil { | |||
return response.SPECIFICATION_NOT_EXIST | |||
} | |||
err = models.UpdateSpecUnitPriceById(specId, unitPrice) | |||
if err != nil { | |||
return response.NewBizError(err) | |||
} | |||
if oldSpec.UnitPrice != unitPrice { | |||
AddSpecOperateLog(doerId, "edit", operate_log.NewLogValues().Add("unitPrice", unitPrice), operate_log.NewLogValues().Add("unitPrice", oldSpec.UnitPrice), specId, fmt.Sprintf("修改资源规格单价从%d积分到%d积分", oldSpec.UnitPrice, unitPrice)) | |||
} | |||
return nil | |||
} | |||
func SyncGrampusSpecs(doerId int64) error { | |||
r, err := grampus.GetResourceSpecs("") | |||
if err != nil { | |||
return err | |||
} | |||
log.Info("SyncGrampusSpecs result = %+v", r) | |||
specUpdateList := make([]models.ResourceSpecification, 0) | |||
specInsertList := make([]models.ResourceSpecification, 0) | |||
existIds := make([]int64, 0) | |||
for _, spec := range r.Infos { | |||
for _, c := range spec.Centers { | |||
computeResource := models.ParseComputeResourceFormGrampus(spec.SpecInfo.AccDeviceKind) | |||
if computeResource == "" { | |||
continue | |||
} | |||
accCardType := strings.ToUpper(spec.SpecInfo.AccDeviceModel) | |||
memGiB, err := models.ParseMemSizeFromGrampus(spec.SpecInfo.MemorySize) | |||
gpuMemGiB, err := models.ParseMemSizeFromGrampus(spec.SpecInfo.AccDeviceMemory) | |||
if err != nil { | |||
log.Error("ParseMemSizeFromGrampus error. MemorySize=%s AccDeviceMemory=%s", spec.SpecInfo.MemorySize, spec.SpecInfo.AccDeviceMemory) | |||
} | |||
// get resource queue.if queue not exist,skip it | |||
r, err := models.GetResourceQueue(&models.ResourceQueue{ | |||
Cluster: models.C2NetCluster, | |||
AiCenterCode: c.ID, | |||
ComputeResource: computeResource, | |||
AccCardType: accCardType, | |||
}) | |||
if err != nil || r == nil { | |||
continue | |||
} | |||
//Determine if this specification already exists.if exist,update params | |||
//if not exist,insert a new record and status is SpecNotVerified | |||
oldSpec, err := models.GetResourceSpecification(&models.ResourceSpecification{ | |||
QueueId: r.ID, | |||
SourceSpecId: spec.ID, | |||
}) | |||
if err != nil { | |||
return err | |||
} | |||
if oldSpec == nil { | |||
specInsertList = append(specInsertList, models.ResourceSpecification{ | |||
QueueId: r.ID, | |||
SourceSpecId: spec.ID, | |||
AccCardsNum: spec.SpecInfo.AccDeviceNum, | |||
CpuCores: spec.SpecInfo.CpuCoreNum, | |||
MemGiB: memGiB, | |||
GPUMemGiB: gpuMemGiB, | |||
Status: models.SpecNotVerified, | |||
IsAutomaticSync: true, | |||
CreatedBy: doerId, | |||
UpdatedBy: doerId, | |||
}) | |||
} else { | |||
existIds = append(existIds, oldSpec.ID) | |||
specUpdateList = append(specUpdateList, models.ResourceSpecification{ | |||
ID: oldSpec.ID, | |||
AccCardsNum: spec.SpecInfo.AccDeviceNum, | |||
CpuCores: spec.SpecInfo.CpuCoreNum, | |||
MemGiB: memGiB, | |||
GPUMemGiB: gpuMemGiB, | |||
UpdatedBy: doerId, | |||
}) | |||
} | |||
} | |||
} | |||
return models.SyncGrampusSpecs(specUpdateList, specInsertList, existIds) | |||
} | |||
//GetResourceSpecificationList returns specification and queue | |||
func GetResourceSpecificationList(opts models.SearchResourceSpecificationOptions) (*models.ResourceSpecAndQueueListRes, error) { | |||
n, r, err := models.SearchResourceSpecification(opts) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return models.NewResourceSpecAndQueueListRes(n, r), nil | |||
} | |||
func GetResourceSpecificationScenes(specId int64) ([]models.ResourceSceneBriefRes, error) { | |||
r, err := models.GetSpecScenes(specId) | |||
if err != nil { | |||
return nil, err | |||
} | |||
return r, nil | |||
} | |||
func ResourceSpecOnShelf(doerId int64, id int64, unitPrice int) *response.BizError { | |||
spec, err := models.GetResourceSpecification(&models.ResourceSpecification{ID: id}) | |||
if err != nil { | |||
return response.NewBizError(err) | |||
} | |||
if spec == nil { | |||
return response.SPECIFICATION_NOT_EXIST | |||
} | |||
if q, err := models.GetResourceQueue(&models.ResourceQueue{ID: spec.QueueId}); err != nil || q == nil { | |||
return response.RESOURCE_QUEUE_NOT_AVAILABLE | |||
} | |||
err = models.ResourceSpecOnShelf(id, unitPrice) | |||
if err != nil { | |||
return response.NewBizError(err) | |||
} | |||
if spec.UnitPrice != unitPrice { | |||
AddSpecOperateLog(doerId, "on-shelf", operate_log.NewLogValues().Add("UnitPrice", unitPrice), operate_log.NewLogValues().Add("UnitPrice", spec.UnitPrice), id, fmt.Sprintf("定价上架资源规格,单价为%d", unitPrice)) | |||
} else { | |||
AddSpecOperateLog(doerId, "on-shelf", nil, nil, id, "上架资源规格") | |||
} | |||
return nil | |||
} | |||
func ResourceSpecOffShelf(doerId int64, id int64) *response.BizError { | |||
_, err := models.ResourceSpecOffShelf(id) | |||
if err != nil { | |||
return response.NewBizError(err) | |||
} | |||
AddSpecOperateLog(doerId, "off-shelf", nil, nil, id, "下架资源规格") | |||
return nil | |||
} | |||
func AddSpecOperateLog(doerId int64, operateType string, newValue, oldValue *models.LogValues, specId int64, comment string) { | |||
var newString = "" | |||
var oldString = "" | |||
if newValue != nil { | |||
newString = newValue.JsonString() | |||
} | |||
if oldValue != nil { | |||
oldString = oldValue.JsonString() | |||
} | |||
operate_log.Log(models.AdminOperateLog{ | |||
BizType: "SpecOperate", | |||
OperateType: operateType, | |||
OldValue: oldString, | |||
NewValue: newString, | |||
RelatedId: fmt.Sprint(specId), | |||
CreatedBy: doerId, | |||
Comment: comment, | |||
}) | |||
} |
@@ -13,10 +13,9 @@ | |||
<div class="alert"></div> | |||
<div class="admin user"> | |||
{{template "admin/navbar" .}} | |||
<div id="images-admin"> | |||
</div> | |||
<div class="ui container"> | |||
<div id="images-admin"></div> | |||
</div> | |||
</div> | |||
<!-- 确认模态框 --> | |||
<div> | |||
@@ -22,19 +22,19 @@ | |||
data-all-compute="{{.i18n.Tr "admin.cloudbrain.all_computing_resources"}}" | |||
data-all-status="{{.i18n.Tr "admin.cloudbrain.all_status"}}"></div> | |||
{{template "admin/navbar" .}} | |||
<div class="ui container" style="width: 95%;"> | |||
<div class="ui container"> | |||
{{template "base/alert" .}} | |||
<div class="ui grid"> | |||
<div class="row" style="border: 1px solid #d4d4d5;margin-top: 15px;padding-top: 0;"> | |||
<div class="ui grid" style="margin:0"> | |||
<div class="row" style="border: 1px solid #d4d4d5;margin-top: 0px;padding-top: 0;"> | |||
{{template "admin/cloudbrain/search" .}} | |||
<div class="ui six wide column right aligned" style="margin: 1rem 0;"> | |||
<a class="ui compact blue basic icon button" style="box-shadow: none !important; padding: 0.8em;" | |||
href="/admin/cloudbrains/download"><i | |||
class="ri-download-line middle aligned icon"></i>{{.i18n.Tr "admin.cloudbrain.download_report"}}</a> | |||
</div> | |||
<div class="ui sixteen wide column"> | |||
<div class="ui sixteen wide column" style="overflow-x:auto;"> | |||
<!-- 任务展示 --> | |||
<div class="dataset list"> | |||
<div class="dataset list" style="min-width:2100px;margin-top:15px;margin-bottom:15px;"> | |||
<!-- 表头 --> | |||
<div class="ui grid stackable" style="background: #f0f0f0;;"> | |||
<div class="row"> | |||
@@ -412,17 +412,16 @@ | |||
</div> | |||
{{end}} | |||
{{end}} | |||
<div id="app" style="margin-top: 2rem;"> | |||
<div class="center"> | |||
<el-pagination background @current-change="handleCurrentChange" :current-page="page" | |||
:page-sizes="[10]" :page-size="10" layout="total, sizes, prev, pager, next, jumper" | |||
:total="{{.Page.Paginater.Total}}"> | |||
</el-pagination> | |||
</div> | |||
</div> | |||
{{end}} | |||
</div> | |||
</div> | |||
<div id="app" style="margin-top: 2rem;width:100%;"> | |||
<div class="center"> | |||
<el-pagination background @current-change="handleCurrentChange" :current-page="page" | |||
:page-sizes="[10]" :page-size="10" layout="total, sizes, prev, pager, next, jumper" | |||
:total="{{.Page.Paginater.Total}}"> | |||
</el-pagination> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
@@ -1,44 +1,64 @@ | |||
<div class="ui secondary pointing tabular top attached borderless menu stackable new-menu navbar"> | |||
<a class="{{if .PageIsAdminDashboard}}active{{end}} item" href="{{AppSubUrl}}/admin"> | |||
{{.i18n.Tr "admin.dashboard"}} | |||
</a> | |||
<a class="{{if .PageIsAdminUsers}}active{{end}} item" href="{{AppSubUrl}}/admin/users"> | |||
{{.i18n.Tr "admin.users"}} | |||
</a> | |||
<a class="{{if .PageIsAdminOrganizations}}active{{end}} item" href="{{AppSubUrl}}/admin/orgs"> | |||
{{.i18n.Tr "admin.organizations"}} | |||
</a> | |||
<a class="{{if .PageIsAdminRepositories}}active{{end}} item" href="{{AppSubUrl}}/admin/repos"> | |||
{{.i18n.Tr "admin.repositories"}} | |||
</a> | |||
<a class="{{if .PageIsAdminDatasets}}active{{end}} item" href="{{AppSubUrl}}/admin/datasets"> | |||
{{.i18n.Tr "admin.datasets"}} | |||
</a> | |||
<a class="{{if .PageIsAdminCloudBrains}}active{{end}} item" href="{{AppSubUrl}}/admin/cloudbrains"> | |||
{{.i18n.Tr "repo.cloudbrain.task"}} | |||
</a> | |||
<a class="{{if .PageIsAdminImages}}active{{end}} item" href="{{AppSubUrl}}/admin/images"> | |||
{{.i18n.Tr "explore.images"}} | |||
</a> | |||
<a class="{{if .PageIsAdminHooks}}active{{end}} item" href="{{AppSubUrl}}/admin/hooks"> | |||
{{.i18n.Tr "admin.hooks"}} | |||
</a> | |||
<a class="{{if .PageIsAdminSystemHooks}}active{{end}} item" href="{{AppSubUrl}}/admin/system-hooks"> | |||
{{.i18n.Tr "admin.systemhooks"}} | |||
</a> | |||
<a class="{{if .PageIsAdminAuthentications}}active{{end}} item" href="{{AppSubUrl}}/admin/auths"> | |||
{{.i18n.Tr "admin.authentication"}} | |||
</a> | |||
<a class="{{if .PageIsAdminEmails}}active{{end}} item" href="{{AppSubUrl}}/admin/emails"> | |||
{{.i18n.Tr "admin.emails"}} | |||
</a> | |||
<a class="{{if .PageIsAdminConfig}}active{{end}} item" href="{{AppSubUrl}}/admin/config"> | |||
{{.i18n.Tr "admin.config"}} | |||
</a> | |||
<a class="{{if .PageIsAdminNotices}}active{{end}} item" href="{{AppSubUrl}}/admin/notices"> | |||
{{.i18n.Tr "admin.notices"}} | |||
</a> | |||
<a class="{{if .PageIsAdminMonitor}}active{{end}} item" href="{{AppSubUrl}}/admin/monitor"> | |||
{{.i18n.Tr "admin.monitor"}} | |||
</a> | |||
<div class="ui secondary pointing tabular top attached borderless menu stackable new-menu navbar" style="margin-top:0"> | |||
<div class="item-container"> | |||
<a class="{{if .PageIsAdminDashboard}}active{{end}} item" href="{{AppSubUrl}}/admin"> | |||
{{.i18n.Tr "admin.dashboard"}} | |||
</a> | |||
<a class="item item-first" href="javascript:void(0);"> | |||
{{.i18n.Tr "admin.user_management"}} | |||
</a> | |||
<a class="{{if .PageIsAdminUsers}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/users"> | |||
{{.i18n.Tr "admin.users"}} | |||
</a> | |||
<a class="{{if .PageIsAdminEmails}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/emails"> | |||
{{.i18n.Tr "admin.emails"}} | |||
</a> | |||
<a class="{{if .PageIsAdminOrganizations}}active{{end}} item" href="{{AppSubUrl}}/admin/orgs"> | |||
{{.i18n.Tr "admin.organizations"}} | |||
</a> | |||
<a class="{{if .PageIsAdminRepositories}}active{{end}} item" href="{{AppSubUrl}}/admin/repos"> | |||
{{.i18n.Tr "admin.repositories"}} | |||
</a> | |||
<a class="{{if .PageIsAdminDatasets}}active{{end}} item" href="{{AppSubUrl}}/admin/datasets"> | |||
{{.i18n.Tr "admin.datasets"}} | |||
</a> | |||
<a class="{{if .PageIsAdminCloudBrains}}active{{end}} item" href="{{AppSubUrl}}/admin/cloudbrains"> | |||
{{.i18n.Tr "repo.cloudbrain.task"}} | |||
</a> | |||
<a class="{{if .PageIsAdminImages}}active{{end}} item" href="{{AppSubUrl}}/admin/images"> | |||
{{.i18n.Tr "explore.images"}} | |||
</a> | |||
<a class="item item-first" href="javascript:void(0);"> | |||
{{.i18n.Tr "admin.resource_management"}} | |||
</a> | |||
<a class="{{if .PageIsAdminResourcesQueue}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/resources/queue"> | |||
{{.i18n.Tr "admin.resource_pool"}} | |||
</a> | |||
<a class="{{if .PageIsAdminResourcesSpecification}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/resources/specification"> | |||
{{.i18n.Tr "admin.resource_price"}} | |||
</a> | |||
<a class="{{if .PageIsAdminResourcesScene}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/resources/scene"> | |||
{{.i18n.Tr "admin.application_scenario"}} | |||
</a> | |||
<a class="item item-first" href="javascript:void(0);"> | |||
{{.i18n.Tr "admin.system_configuration"}} | |||
</a> | |||
<a class="{{if .PageIsAdminMonitor}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/monitor"> | |||
{{.i18n.Tr "admin.monitor"}} | |||
</a> | |||
<a class="{{if .PageIsAdminHooks}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/hooks"> | |||
{{.i18n.Tr "admin.hooks"}} | |||
</a> | |||
<a class="{{if .PageIsAdminSystemHooks}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/system-hooks"> | |||
{{.i18n.Tr "admin.systemhooks"}} | |||
</a> | |||
<a class="{{if .PageIsAdminAuthentications}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/auths"> | |||
{{.i18n.Tr "admin.authentication"}} | |||
</a> | |||
<a class="{{if .PageIsAdminConfig}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/config"> | |||
{{.i18n.Tr "admin.config"}} | |||
</a> | |||
<a class="{{if .PageIsAdminNotices}}active{{end}} item item-next" href="{{AppSubUrl}}/admin/notices"> | |||
{{.i18n.Tr "admin.notices"}} | |||
</a> | |||
</div> | |||
</div> |
@@ -0,0 +1,10 @@ | |||
{{template "base/head" .}} | |||
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-resources-queue.css?v={{MD5 AppVer}}" /> | |||
<div class="admin resource"> | |||
{{template "admin/navbar" .}} | |||
<div class="ui container"> | |||
<div id="__vue-root"></div> | |||
</duv> | |||
</div> | |||
<script src="{{StaticUrlPrefix}}/js/vp-resources-queue.js?v={{MD5 AppVer}}"></script> | |||
{{template "base/footer" .}} |
@@ -0,0 +1,10 @@ | |||
{{template "base/head" .}} | |||
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-resources-scene.css?v={{MD5 AppVer}}" /> | |||
<div class="admin resource"> | |||
{{template "admin/navbar" .}} | |||
<div class="ui container"> | |||
<div id="__vue-root"></div> | |||
</duv> | |||
</div> | |||
<script src="{{StaticUrlPrefix}}/js/vp-resources-scene.js?v={{MD5 AppVer}}"></script> | |||
{{template "base/footer" .}} |
@@ -0,0 +1,10 @@ | |||
{{template "base/head" .}} | |||
<link rel="stylesheet" href="{{StaticUrlPrefix}}/css/vp-resources-specification.css?v={{MD5 AppVer}}" /> | |||
<div class="admin resource"> | |||
{{template "admin/navbar" .}} | |||
<div class="ui container"> | |||
<div id="__vue-root"></div> | |||
</duv> | |||
</div> | |||
<script src="{{StaticUrlPrefix}}/js/vp-resources-specification.js?v={{MD5 AppVer}}"></script> | |||
{{template "base/footer" .}} |
@@ -1,9 +1,8 @@ | |||
<template> | |||
<div> | |||
<div class="ui container" style="width: 80%;"> | |||
<div class="ui grid"> | |||
<div class="row" style="border: 1px solid #d4d4d5;margin-top: 15px;padding-top: 0;"> | |||
<div class="ui container" style="width: 100% !important;padding-right: 0;"> | |||
<div class="ui grid" style="margin: 0 !important"> | |||
<div class="row" style="border: 1px solid #d4d4d5;margin-top:0px;padding-top: 0;"> | |||
<div class="ui attached segment"> | |||
<div class="ui form ignore-dirty"> | |||
<div class="ui fluid action input"> | |||
@@ -31,8 +30,8 @@ | |||
<div class="ui six wide column right aligned" style="margin: 1rem 0;"> | |||
<a class="ui blue small button" href="/admin/images/commit_image">创建云脑镜像</a> | |||
</div> | |||
<div class="ui sixteen wide column" style="padding: 0;"> | |||
<el-table :data="tableDataCustom" style="width: 100%" :header-cell-style="tableHeaderStyle"> | |||
<div class="ui sixteen wide column" style="padding: 0;overflow-x: auto;"> | |||
<el-table :data="tableDataCustom" style="width: 100%;min-width:1700px;" :header-cell-style="tableHeaderStyle"> | |||
<el-table-column label="镜像Tag" min-width="19%" align="left" prop="tag"> | |||
<template slot-scope="scope"> | |||
<div style="display: flex;align-items: center;"> | |||
@@ -1,5 +1,5 @@ | |||
.admin { | |||
padding-top: 15px; | |||
padding-top: 15px !important; | |||
.table.segment { | |||
padding: 0; | |||
@@ -75,4 +75,58 @@ | |||
white-space: pre-wrap; | |||
word-wrap: break-word; | |||
} | |||
display: flex; | |||
.new-menu.navbar { | |||
width: 230px !important; | |||
display: flex; | |||
flex-direction: column; | |||
justify-content: flex-start !important; | |||
border-bottom: none !important; | |||
background-color: transparent !important; | |||
.item-container { | |||
display: flex; | |||
flex-direction: column; | |||
padding-top: 8px; | |||
padding-bottom: 8px; | |||
margin-left: 10px !important; | |||
margin-right: 10px !important; | |||
border: 1px solid #d4d4d5; | |||
border-radius: 4px; | |||
box-shadow: 0 1px 2px 0 rgb(34 36 38 / 15%); | |||
background-color: #fafafa !important; | |||
.item { | |||
align-self: flex-start !important; | |||
width: 100%; | |||
padding-left: 20px; | |||
&.active { | |||
color: #40a9ff !important; | |||
border-right: 4px solid #40a9ff; | |||
border-radius: 0 !important; | |||
border-bottom: none !important; | |||
background-color: rgb(255, 255, 255); | |||
} | |||
&:hover { | |||
background-color: #ffffff !important; | |||
} | |||
&:active { | |||
border-color: transparent !important; | |||
} | |||
&.item-next { | |||
padding-left: 45px; | |||
} | |||
&.item-first { | |||
color: rgba(0,0,0,.87); | |||
&:hover { | |||
background-color: transparent !important; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
>.ui.container { | |||
flex: 1 !important; | |||
padding-right: 10px; | |||
} | |||
} |
@@ -0,0 +1,41 @@ | |||
import service from '../service'; | |||
// 算力积分概要 | |||
export const getPointAccount = () => { | |||
return service({ | |||
url: '/reward/point/account', | |||
method: 'get', | |||
params: {}, | |||
}); | |||
} | |||
// 算力积分获取、消耗明细 | |||
// operate-INCREASE 表示获取明细 DECREASE表示消耗明细, page-当前页, pageSize-每页条数 | |||
export const getPointList = (params) => { | |||
return service({ | |||
url: '/reward/point/record/list', | |||
method: 'get', | |||
params, | |||
}); | |||
} | |||
// 管理员充值、扣减用户积分 | |||
// TargetUserId, OperateType-INCREASE,DECREASE, Amount, Remark, RewardType-POINT | |||
export const setPointOperate = (data) => { | |||
return service({ | |||
url: '/operation/reward/point/account/operate', | |||
method: 'post', | |||
data, | |||
params: {} | |||
}); | |||
} | |||
// 算力积分页面 | |||
export const getPoint = () => { | |||
return service({ | |||
url: '/reward/point', | |||
method: 'get', | |||
params: {}, | |||
data: {}, | |||
}); | |||
} |
@@ -0,0 +1,174 @@ | |||
import service from '../service'; | |||
// 查询智算列表 | |||
export const getAiCenterList = () => { | |||
return service({ | |||
url: '/admin/resources/queue/centers', | |||
method: 'get', | |||
params: {}, | |||
data: {}, | |||
}); | |||
} | |||
// 查询资源队列列表 | |||
// page 当前页数,从1开始 | |||
// cluster 所属集群 :OpenI 启智集群,C2Net 智算集群 | |||
// center 智算中心:OpenIOne 云脑一,OpenITwo 云脑二, chendu 成都人工智能计算中心, pclcci 鹏城云计算所 ,hefei 合肥类脑类脑智能开放平台, xuchang 中原人工智能计算中心 | |||
// resource 计算资源: GPU NPU | |||
// card XPU类型: T4、A100、V100、Ascend 910 | |||
export const getResQueueList = (params) => { | |||
return service({ | |||
url: '/admin/resources/queue/list', | |||
method: 'get', | |||
params, | |||
}); | |||
} | |||
// 新增资源队列 | |||
export const addResQueue = (data) => { // Cluster,QueueCode,AiCenterCode,ComputeResource,AccCardType,CardsTotalNum,Remark | |||
return service({ | |||
url: '/admin/resources/queue/add', | |||
method: 'post', | |||
params: {}, | |||
data, | |||
}); | |||
} | |||
// 更新资源队列 | |||
export const updateResQueue = (data) => { // CardsTotalNum,Remark | |||
return service({ | |||
url: `/admin/resources/queue/update/${data.ID}`, | |||
method: 'post', | |||
params: {}, | |||
data, | |||
}); | |||
} | |||
// 查询所有资源队列名称列表 | |||
export const getResQueueCode = (params) => { // cluster | |||
return service({ | |||
url: '/admin/resources/queue/codes', | |||
method: 'get', | |||
params, | |||
data: {}, | |||
}); | |||
} | |||
// 同步智算网络资源池(队列) | |||
export const syncResQueue = () => { | |||
return service({ | |||
url: '/admin/resources/queue/grampus/sync', | |||
method: 'post', | |||
params: {}, | |||
data: {}, | |||
}); | |||
} | |||
// 新增资源规格 | |||
export const addResSpecification = (data) => { | |||
return service({ | |||
url: '/admin/resources/specification/add', | |||
method: 'post', | |||
params: {}, | |||
data, | |||
}); | |||
} | |||
// 查询资源规格所属场景 - 下架时提醒 | |||
export const getResSpecificationScenes = (data) => { // data => { ID: 1 } | |||
return service({ | |||
url: `/admin/resources/specification/scenes/${data.ID}`, | |||
method: 'get', | |||
params: {}, | |||
data: {} | |||
}); | |||
} | |||
// 更新资源规格 | |||
// params: action edit-编辑 on-shelf 上架 off-shelf 下架 | |||
// data: UnitPrice | |||
export const updateResSpecification = (data) => { // data => { ID: 1, action: 'edit|on-shelf|off-shelf', UnitPrice: 1 | undefined } | |||
return service({ | |||
url: `/admin/resources/specification/update/${data.ID}`, | |||
method: 'post', | |||
params: { action: data.action }, | |||
data: { UnitPrice: data.action === 'edit' || data.action === 'on-shelf' ? data.UnitPrice : undefined } | |||
}); | |||
} | |||
// 查询资源规格列表 | |||
// page | |||
// cluster 所属集群 :OpenI 启智集群,C2Net 智算集群 | |||
// queue 所属队列id | |||
// status 状态 : 1 待审核 2已上架 3已下架 | |||
export const getResSpecificationList = (params) => { | |||
return service({ | |||
url: '/admin/resources/specification/list', | |||
method: 'get', | |||
params, | |||
data: {}, | |||
}); | |||
} | |||
// 同步智算网络资源池(队列) | |||
export const syncResSpecification = () => { | |||
return service({ | |||
url: '/admin/resources/specification/grampus/sync', | |||
method: 'post', | |||
params: {}, | |||
data: {}, | |||
}); | |||
} | |||
// 新增资源应用场景 | |||
/* | |||
{ | |||
"SceneName":"启智集群调试任务", //应用场景名 | |||
"JobType":"TRAIN", //任务类型 DEBUG调试任务 BENCHMARK 评测任务 TRAIN 训练 INFERENCE 推理 | |||
"IsExclusive":true, //是否专属 | |||
"ExclusiveOrg":"123,456", //专属组织 | |||
"SpecIds":[2,3] // 资源规格id | |||
} | |||
*/ | |||
export const addResScene = (data) => { | |||
return service({ | |||
url: '/admin/resources/scene/add', | |||
method: 'post', | |||
params: {}, | |||
data, | |||
}); | |||
} | |||
// 更新资源应用场景 | |||
// params: action:edit-编辑 delete-删除, | |||
// data: { | |||
// "SceneName":"启智集群调试任务", //应用场景名 | |||
// "IsExclusive":true, //是否专属 | |||
// "ExclusiveOrg":"123,456", //专属组织 | |||
// "SpecIds":[2,3] // 资源规格id | |||
//} | |||
export const updateResScene = (data) => { | |||
return service({ | |||
url: `/admin/resources/scene/update/${data.ID}`, | |||
method: 'post', | |||
params: { action: data.action }, | |||
data: { | |||
...data | |||
}, | |||
}); | |||
} | |||
// 查询资源应用场景 | |||
// page | |||
// jobType | |||
// center | |||
// queue 所属队列 | |||
// IsExclusive 是否专属 1 专属 2 非专属 | |||
export const getResSceneList = (params) => { | |||
return service({ | |||
url: '/admin/resources/scene/list', | |||
method: 'get', | |||
params, | |||
data: {}, | |||
}); | |||
} |
@@ -0,0 +1,26 @@ | |||
import axios from 'axios'; | |||
const service = axios.create({ | |||
baseURL: '/', | |||
timeout: 20000, | |||
}); | |||
service.interceptors.request.use((config) => { | |||
config.data && Object.assign(config.data, { | |||
_csrf: window.config ? window.config.csrf : '', | |||
}); | |||
config.params && Object.assign(config.params, { | |||
_csrf: window.config ? window.config.csrf : '', | |||
}); | |||
return config; | |||
}, (error) => { | |||
return Promise.reject(error); | |||
}); | |||
service.interceptors.response.use((response) => { | |||
return response; | |||
}, (error) => { | |||
return Promise.reject(error); | |||
}); | |||
export default service; |
@@ -0,0 +1,99 @@ | |||
<template> | |||
<div class="base-dlg"> | |||
<el-dialog :visible.sync="dialogShow" :title="title" :width="width" :fullscreen="fullscreen" :top="top" | |||
:modal="modal" :modal-append-to-body="modalAppendToBody" :append-to-body="appendToBody" :lock-scroll="lockScroll" | |||
:custom-class="customClass" :close-on-click-modal="closeOnClickModal" :close-on-press-escape="closeOnPressEscape" | |||
:show-close="showClose" :center="center" :destroy-on-close="destroyOnClose" :before-close="beforeClose" | |||
@open="open" @opened="opened" @close="close" @closed="closed"> | |||
<template v-slot:title> | |||
<slot name="title"></slot> | |||
</template> | |||
<template v-slot:default> | |||
<slot name="default"></slot> | |||
</template> | |||
<template v-slot:footer> | |||
<slot name="footer"></slot> | |||
</template> | |||
</el-dialog> | |||
</div> | |||
</template> | |||
<script> | |||
export default { | |||
name: "BaseDialog", | |||
props: { | |||
visible: { type: Boolean, default: false }, | |||
title: { type: String, default: "" }, | |||
width: { type: String, default: "" }, | |||
fullscreen: { type: Boolean, default: false }, | |||
top: { type: String }, | |||
modal: { type: Boolean, default: true }, | |||
modalAppendToBody: { type: Boolean, default: true }, | |||
appendToBody: { type: Boolean, default: false }, | |||
lockScroll: { type: Boolean, default: false }, | |||
customClass: { type: String, default: "" }, | |||
closeOnClickModal: { type: Boolean, default: false }, | |||
closeOnPressEscape: { type: Boolean, default: true }, | |||
showClose: { type: Boolean, default: true }, | |||
beforeClose: { type: Function }, | |||
center: { type: Boolean, default: false }, | |||
destroyOnClose: { type: Boolean, default: false }, | |||
}, | |||
data() { | |||
return { | |||
dialogShow: false, | |||
}; | |||
}, | |||
watch: { | |||
visible: function (val) { | |||
this.dialogShow = val; | |||
}, | |||
}, | |||
methods: { | |||
open() { | |||
this.$emit("open"); | |||
}, | |||
opened() { | |||
this.$emit("opened"); | |||
}, | |||
close() { | |||
this.$emit("close"); | |||
}, | |||
closed() { | |||
this.$emit("closed"); | |||
this.$emit("update:visible", false); | |||
}, | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.base-dlg { | |||
/deep/ .el-dialog__header { | |||
text-align: left; | |||
height: 45px; | |||
background: rgb(240, 240, 240); | |||
border-radius: 5px 5px 0px 0px; | |||
border-bottom: 1px solid rgb(212, 212, 213); | |||
padding: 0 15px; | |||
display: flex; | |||
align-items: center; | |||
font-weight: 500; | |||
font-size: 16px; | |||
color: rgb(16, 16, 16); | |||
.el-dialog__title { | |||
font-weight: 500; | |||
font-size: 16px; | |||
color: rgb(16, 16, 16); | |||
} | |||
.el-dialog__headerbtn { | |||
top: 15px; | |||
right: 15px; | |||
} | |||
} | |||
/deep/ .el-dialog__body { | |||
padding: 15px 15px; | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,16 @@ | |||
import { i18n } from '~/langs'; | |||
export const SOURCE_TYPE = [{ k: 'ACCOMPLISH_TASK', v: i18n.t('accomplishTask') }, { k: 'ADMIN_OPERATE', v: i18n.t('adminOperate') }, { k: 'RUN_CLOUDBRAIN_TASK', v: i18n.t('runCloudBrainTask') }]; | |||
export const CONSUME_STATUS = [{ k: 'OPERATING', v: i18n.t('operating') }, { k: 'SUCCEEDED', v: i18n.t('succeeded') }]; | |||
export const POINT_ACTIONS = [ | |||
{ k: 1, v: i18n.t('createPublicProject') }, { k: 6, v: i18n.t('dailyPutforwardTasks') }, { k: 7, v: i18n.t('dailyPR') }, { k: 10, v: i18n.t('comment') }, { k: 24, v: i18n.t('uploadDatasetFile') }, { k: 30, v: i18n.t('importNewModel') }, { k: 34, v: i18n.t('completeWechatCodeScanningVerification') }, | |||
{ k: 35, v: i18n.t('dailyRunCloudbrainTasks') }, { k: 36, v: i18n.t('datasetRecommendedByThePlatform') }, { k: 37, v: i18n.t('submitNewPublicImage') }, { k: 38, v: i18n.t('imageRecommendedByThePlatform') }, { k: 39, v: i18n.t('firstChangeofAvatar') }, { k: 40, v: i18n.t('dailyCommit') }, | |||
]; | |||
export const JOB_TYPE = [{ k: 'DEBUG', v: i18n.t('debugTask') }, { k: 'TRAIN', v: i18n.t('trainTask') }, { k: 'INFERENCE', v: i18n.t('inferenceTask') }, { k: 'BENCHMARK', v: i18n.t('benchmarkTask') }]; | |||
// 资源管理 | |||
export const CLUSTERS = [{ k: 'OpenI', v: i18n.t('resourcesManagement.OpenI') }, { k: 'C2Net', v: i18n.t('resourcesManagement.C2Net') }]; | |||
export const AI_CENTER = [{ k: 'OpenIOne', v: i18n.t('resourcesManagement.OpenIOne') }, { k: 'OpenITwo', v: i18n.t('resourcesManagement.OpenITwo') }, { k: 'chendu', v: i18n.t('resourcesManagement.chenduCenter') }, { k: 'pclcci', v: i18n.t('resourcesManagement.pclcci') }, { k: 'hefei', v: i18n.t('resourcesManagement.hefeiCenter') }, { k: 'xuchang', v: i18n.t('resourcesManagement.xuchangCenter') }]; | |||
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') }]; |
@@ -0,0 +1,156 @@ | |||
const en = { | |||
loading: 'Loading...', | |||
noData: 'No Data', | |||
date: 'Date', | |||
confirm: 'Confirm', | |||
cancel: 'Cancel', | |||
confirm1: 'Confirm', | |||
pleaseCompleteTheInformationFirst: 'Please Complete the Information first!', | |||
submittedSuccessfully: 'Submitted Successfully!', | |||
submittedFailed: 'Submitted Failed!', | |||
operation: 'Operation', | |||
edit: 'Edit', | |||
delete: 'Delete', | |||
tips: 'Tips', | |||
accomplishTask: 'Accomplish Task', | |||
adminOperate: 'Administrator Operation', | |||
runCloudBrainTask: 'Run CloudBrain Task', | |||
operating: 'Operating', | |||
succeeded: 'Succeeded', | |||
debugTask: 'Debug Task', | |||
trainTask: 'Train Task', | |||
inferenceTask: 'Inference Task', | |||
benchmarkTask: 'Benchmark Task', | |||
createPublicProject: 'Create Public Projects', | |||
dailyPutforwardTasks: 'Daily Put Forward Tasks', | |||
dailyPR: 'Daily PR', | |||
comment: 'Comment', | |||
uploadDatasetFile: 'Upload Dataset Files', | |||
importNewModel: 'Import New Models', | |||
completeWechatCodeScanningVerification: 'Complete Wechat Code Scanning Verification', | |||
dailyRunCloudbrainTasks: 'Daily Run Cloudbrain Tasks', | |||
datasetRecommendedByThePlatform: 'Dataset Recommended by the Platform', | |||
submitNewPublicImage: 'Submit New Public Images', | |||
imageRecommendedByThePlatform: 'Image Recommended by the Platform', | |||
firstChangeofAvatar: 'First Change of Avatar', | |||
dailyCommit: 'Daily Commit', | |||
calcPointDetails: 'Calculation Points Details', | |||
calcPointAcquisitionInstructions: 'Calculation Points Acquisition Instructions', | |||
CurrAvailableCalcPoints: 'Currently Available Calculation Points', | |||
totalGainCalcPoints: 'Total Gain of Calculation Points', | |||
totalConsumeCalcPoints: 'Total Consume of Calculation Points', | |||
gainDetail: 'Gain Detail', | |||
consumeDetail: 'Consume Detail', | |||
serialNumber: 'Serial Number', | |||
time: 'Time', | |||
scene: 'Scene', | |||
behaviorOfPoint: 'Behavior Of Point', | |||
explanation: 'Explanation', | |||
points: 'Points', | |||
status: 'Status', | |||
runTime: 'Run Time', | |||
taskName: 'Task Name', | |||
createdRepository: 'created repository ', | |||
openedIssue: 'opened issue ', | |||
createdPullRequest: 'created pull request ', | |||
commentedOnIssue: 'commented on issue ', | |||
uploadDataset: 'upload dataset ', | |||
createdNewModel: 'created new model ', | |||
firstBindingWechatRewards: 'first binding wechat rewards', | |||
created: 'created ', | |||
type: ' type ', | |||
dataset: 'dataset ', | |||
setAsRecommendedDataset: ' was set as recommended dataset', | |||
committedImage: 'committed image ', | |||
image: 'image ', | |||
setAsRecommendedImage: ' was set as recommended image', | |||
updatedAvatar: 'updated avatar', | |||
pushedBranch: 'pushed to {branch} at ', | |||
dailyMaxTips: `can't get full points when reach the daily upper limit`, | |||
memory: 'Memory', | |||
sharedMemory: 'Shared Memory', | |||
';': ', ', | |||
noPointGainRecord: 'No Point Earn Record Yet', | |||
noPointConsumeRecord: 'No Point Consume Record Yet', | |||
resourcesManagement: { | |||
OpenI: 'OpenI', | |||
C2Net: 'C2Net', | |||
OpenIOne: 'OpenI One', | |||
OpenITwo: 'OpenI Two', | |||
chenduCenter: 'ChenDu AI Center', | |||
pclcci: 'PCL Cloud Computer Institute', | |||
hefeiCenter: 'HeFei AI Center', | |||
xuchangCenter: 'XuChang AI Center', | |||
willOnShelf: 'To Be On Shelf', | |||
onShelf: 'On Shelf', | |||
offShelf: 'Off Shelf', | |||
toOnShelf: 'To On Shelf', | |||
toOffShelf: 'To Off Shelf', | |||
toSetPriceAndOnShelf: 'To Set Price and On Shelf', | |||
status: 'Status', | |||
allStatus: 'All Status', | |||
syncAiNetwork: 'Sync AI Network', | |||
resQueue: 'Resources Queue', | |||
allResQueue: 'All Resources Queues', | |||
addResQueue: 'Add Resources Queue', | |||
addResQueueBtn: 'Add Resources Queue', | |||
editResQueue: 'Edit Resources Queue', | |||
resQueueName: 'Resources Queue Name', | |||
whichCluster: 'Cluster', | |||
allCluster: 'All Clusters', | |||
aiCenter: 'AI Center', | |||
aiCenterID: 'AI Center ID', | |||
allAiCenter: 'All AI Centers', | |||
computeResource: 'Compute Resource', | |||
allComputeResource: 'All Compute Resources', | |||
accCardType: 'Acc Card Type', | |||
allAccCardType: 'All Acc Card Type', | |||
cardsTotalNum: 'Cards Total Number', | |||
accCardsNum: 'Acc Cards Number', | |||
remark: 'Remark', | |||
pleaseEnterRemark: 'Please Enter Remark(The maximum length shall not exceed 255)', | |||
pleaseEnterPositiveIntegerCardsTotalNum: 'Please Enter Positive Integer Cards Total Number!', | |||
addResSpecificationAndPriceInfo: 'Add Resources Specification and Price Info', | |||
addResSpecificationBtn: 'Add Resources Specification', | |||
editResSpecificationAndPriceInfo: 'Edit Resources Specification and Price Info', | |||
resSpecificationAndPriceManagement: 'Resources Specification and Price Management', | |||
sourceSpecCode: 'Source Specification Code', | |||
sourceSpecCodeTips: 'OpenI Two Should Enter the Source Specification Code', | |||
sourceSpecId: 'Source Specification ID', | |||
cpuNum: 'CPU Number', | |||
gpuMem: 'GPU Memory', | |||
mem: 'Memory', | |||
shareMem: 'Share Memory', | |||
unitPrice: 'Unit Price', | |||
point_hr: 'Point/hr', | |||
onShelfConfirm: 'Are you sure to on shelf the resources specification?', | |||
offShelfConfirm: 'Are you sure to off shelf the resources specification?', | |||
onShelfCode1001: 'On shelf failed, the resources queues not available.', | |||
offShelfDlgTip1: 'The resources specification has already used in scene:', | |||
offShelfDlgTip2: 'Please confirm to off shelf?', | |||
resSceneManagement: 'Resources Scene Management', | |||
addResScene: 'Add Resources Scene', | |||
addResSceneBtn: 'Add Resources Scene', | |||
editResScene: 'Edit Resources Scene', | |||
resSceneName: 'Resources Scene Name', | |||
jobType: 'Job Type', | |||
allJobType: 'All Job Type', | |||
isExclusive: 'Is Exclusive?', | |||
allExclusiveAndCommonUse: 'All Exclusive and Common Use', | |||
exclusive: 'Exclusive', | |||
commonUse: 'Common Use', | |||
exclusiveOrg: 'Exclusive Organization', | |||
exclusiveOrgTips: 'Multiple organization names are separated by semicolons', | |||
computeCluster: 'Compute Cluster', | |||
resourceSpecification: 'Resource Specification', | |||
lastUpdateTime: 'Last Update Time', | |||
resSceneDeleteConfirm: 'Are you sure to delete the current Resource Scene?', | |||
}, | |||
} | |||
export default en; |
@@ -0,0 +1,156 @@ | |||
const zh = { | |||
loading: '加载中...', | |||
noData: '暂无数据', | |||
date: '日期', | |||
confirm: '确定', | |||
cancel: '取消', | |||
confirm1: '确认', | |||
pleaseCompleteTheInformationFirst: '请先完善信息!', | |||
submittedSuccessfully: '提交成功!', | |||
submittedFailed: '提交失败!', | |||
operation: '操作', | |||
edit: '修改', | |||
delete: '删除', | |||
tips: '提示', | |||
accomplishTask: '积分任务', | |||
adminOperate: '管理员操作', | |||
runCloudBrainTask: '运行云脑任务', | |||
operating: '消耗中', | |||
succeeded: '已完成', | |||
debugTask: '调试任务', | |||
trainTask: '训练任务', | |||
inferenceTask: '推理任务', | |||
benchmarkTask: '评测任务', | |||
createPublicProject: '创建公开项目', | |||
dailyPutforwardTasks: '每日提出任务', | |||
dailyPR: '每日提出PR', | |||
comment: '发表评论', | |||
uploadDatasetFile: '上传数据集文件', | |||
importNewModel: '导入新模型', | |||
completeWechatCodeScanningVerification: '完成微信扫码验证', | |||
dailyRunCloudbrainTasks: '每日运行云脑任务', | |||
datasetRecommendedByThePlatform: '数据集被平台推荐', | |||
submitNewPublicImage: '提交新公开镜像', | |||
imageRecommendedByThePlatform: '镜像被平台推荐', | |||
firstChangeofAvatar: '首次更换头像', | |||
dailyCommit: '每日commit', | |||
calcPointDetails: '算力积分明细', | |||
calcPointAcquisitionInstructions: '积分获取说明', | |||
CurrAvailableCalcPoints: '当前可用算力积分(分)', | |||
totalGainCalcPoints: '总获取算力积分(分)', | |||
totalConsumeCalcPoints: '总消耗算力积分(分)', | |||
gainDetail: '获取明细', | |||
consumeDetail: '消耗明细', | |||
serialNumber: '流水号', | |||
time: '时间', | |||
scene: '场景', | |||
behaviorOfPoint: '积分行为', | |||
explanation: '说明', | |||
points: '积分', | |||
status: '状态', | |||
runTime: '运行时长', | |||
taskName: '任务名称', | |||
createdRepository: '创建了项目', | |||
openedIssue: '创建了任务', | |||
createdPullRequest: '创建了合并请求', | |||
commentedOnIssue: '评论了任务', | |||
uploadDataset: '上传了数据集文件', | |||
createdNewModel: '导入了新模型', | |||
firstBindingWechatRewards: '首次绑定微信奖励', | |||
created: '创建了', | |||
type: '类型', | |||
dataset: '数据集', | |||
setAsRecommendedDataset: '被设置为推荐数据集', | |||
committedImage: '提交了镜像', | |||
image: '镜像', | |||
setAsRecommendedImage: '被设置为推荐镜像', | |||
updatedAvatar: '更新了头像', | |||
pushedBranch: '推送了{branch}分支代码到', | |||
dailyMaxTips: '达到每日上限积分,不能拿满分', | |||
memory: '内存', | |||
sharedMemory: '共享内存', | |||
';': ';', | |||
noPointGainRecord: '还没有积分获取记录', | |||
noPointConsumeRecord: '还没有积分消耗记录', | |||
resourcesManagement: { | |||
OpenI: '启智集群', | |||
C2Net: '智算集群', | |||
OpenIOne: '云脑一', | |||
OpenITwo: '云脑二', | |||
chenduCenter: '成都人工智能计算中心', | |||
pclcci: '鹏城云计算所', | |||
hefeiCenter: '合肥类脑类脑智能开放平台', | |||
xuchangCenter: '中原人工智能计算中心', | |||
willOnShelf: '待上架', | |||
onShelf: '已上架', | |||
offShelf: '已下架', | |||
toOnShelf: '上架', | |||
toOffShelf: '下架', | |||
toSetPriceAndOnShelf: '定价上架', | |||
status: '状态', | |||
allStatus: '全部状态', | |||
syncAiNetwork: '同步智算网络', | |||
resQueue: '资源池(队列)', | |||
allResQueue: '全部资源池(队列)', | |||
addResQueue: '新建资源池(队列)', | |||
addResQueueBtn: '新增资源池', | |||
editResQueue: '修改资源池(队列)', | |||
resQueueName: '资源池(队列)名称', | |||
whichCluster: '所属集群', | |||
allCluster: '全部集群', | |||
aiCenter: '智算中心', | |||
aiCenterID: '智算中心ID', | |||
allAiCenter: '全部智算中心', | |||
computeResource: '计算资源', | |||
allComputeResource: '全部计算资源', | |||
accCardType: '卡类型', | |||
allAccCardType: '全部卡类型', | |||
cardsTotalNum: '卡数', | |||
accCardsNum: '卡数', | |||
remark: '备注', | |||
pleaseEnterRemark: '请输入备注(最大长度不超过255)', | |||
pleaseEnterPositiveIntegerCardsTotalNum: '请输入正整数的卡数!', | |||
addResSpecificationAndPriceInfo: '新增资源规格和单价信息', | |||
addResSpecificationBtn: '新增资源规格', | |||
editResSpecificationAndPriceInfo: '修改资源规格和单价信息', | |||
resSpecificationAndPriceManagement: '资源规格单价管理', | |||
sourceSpecCode: '对应资源编码', | |||
sourceSpecCodeTips: '云脑II需要填写对应的资源编码', | |||
sourceSpecId: '智算网络资源规格ID', | |||
cpuNum: 'CPU数', | |||
gpuMem: '显存', | |||
mem: '内存', | |||
shareMem: '共享内存', | |||
unitPrice: '单价', | |||
point_hr: '积分/时', | |||
onShelfConfirm: '请确认上架该规格?', | |||
offShelfConfirm: '请确认下架该规格?', | |||
onShelfCode1001: '上架失败,资源池(队列)不可用。', | |||
offShelfDlgTip1: '当前资源规格已在以下场景中使用:', | |||
offShelfDlgTip2: '请确认进行下架操作?', | |||
resSceneManagement: '算力资源应用场景管理', | |||
addResScene: '新建算力资源应用场景', | |||
addResSceneBtn: '新增应用场景', | |||
editResScene: '修改算力资源应用场景', | |||
resSceneName: '应用场景名称', | |||
jobType: '任务类型', | |||
allJobType: '全部任务类型', | |||
isExclusive: '是否专属', | |||
allExclusiveAndCommonUse: '全部专属和通用', | |||
exclusive: '专属', | |||
commonUse: '通用', | |||
exclusiveOrg: '专属组织', | |||
exclusiveOrgTips: '多个组织名之间用英文分号隔开', | |||
computeCluster: '算力集群', | |||
resourceSpecification: '资源规格', | |||
lastUpdateTime: '最后更新时间', | |||
resSceneDeleteConfirm: '是否确认删除当前应用场景?', | |||
}, | |||
} | |||
export default zh; |
@@ -0,0 +1,16 @@ | |||
import Vue from 'vue'; | |||
import VueI18n from 'vue-i18n'; | |||
import jsCookie from 'js-cookie'; | |||
import zh from './config/zh-CN'; | |||
import en from './config/en-US'; | |||
Vue.use(VueI18n); | |||
export const lang = jsCookie.get('lang') || 'zh-CN'; | |||
export const i18n = new VueI18n({ | |||
locale: lang, | |||
messages: { | |||
'zh-CN': zh, | |||
'en-US': en | |||
}, | |||
}); |
@@ -0,0 +1,259 @@ | |||
<template> | |||
<div class="base-dlg"> | |||
<BaseDialog :visible.sync="dialogShow" :width="`750px`" | |||
:title="type === 'add' ? $t('resourcesManagement.addResQueue') : $t('resourcesManagement.editResQueue')" | |||
@open="open" @opened="opened" @close="close" @closed="closed"> | |||
<div class="dlg-content"> | |||
<div class="form"> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.resQueueName') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.QueueCode" placeholder="" :disabled="type === 'edit'" maxlength="255"></el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.whichCluster') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.Cluster" :disabled="type === 'edit'"> | |||
<el-option v-for="item in clusterList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.aiCenter') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.AiCenterCode" :disabled="type === 'edit'"> | |||
<el-option v-for="item in computingCenterList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.computeResource') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.ComputeResource" :disabled="type === 'edit'"> | |||
<el-option v-for="item in computingTypeList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.accCardType') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.AccCardType" :disabled="type === 'edit'"> | |||
<el-option v-for="item in cardTypeList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.cardsTotalNum') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.CardsTotalNum" type="number" placeholder=""></el-input> | |||
</div> | |||
</div> | |||
<div class="form-row" style="margin-top: 10px"> | |||
<div class="title"><span>{{ $t('resourcesManagement.remark') }}</span></div> | |||
<div class="content" style="width: 400px"> | |||
<el-input type="textarea" :autosize="{ minRows: 3, maxRows: 4 }" maxlength="255" | |||
:placeholder="$t('resourcesManagement.pleaseEnterRemark')" v-model="dataInfo.Remark"> | |||
</el-input> | |||
</div> | |||
</div> | |||
<div class="form-row" style="margin-top: 20px"> | |||
<div class="title"></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> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</BaseDialog> | |||
</div> | |||
</template> | |||
<script> | |||
import BaseDialog from '~/components/BaseDialog.vue'; | |||
import { addResQueue, updateResQueue } from '~/apis/modules/resources'; | |||
import { CLUSTERS, AI_CENTER, COMPUTER_RESOURCES, ACC_CARD_TYPE } from '~/const'; | |||
export default { | |||
name: "QueueDialog", | |||
props: { | |||
visible: { type: Boolean, default: false }, | |||
title: { type: String, default: '' }, | |||
type: { type: String, defalut: 'add' }, | |||
data: { type: Object, default: () => ({}) }, | |||
}, | |||
components: { | |||
BaseDialog | |||
}, | |||
data() { | |||
return { | |||
dialogShow: false, | |||
clusterList: [CLUSTERS[0]], | |||
computingCenterList: [AI_CENTER[0], AI_CENTER[1]], | |||
computingTypeList: [...COMPUTER_RESOURCES], | |||
cardTypeList: [...ACC_CARD_TYPE], | |||
dataInfo: {}, | |||
}; | |||
}, | |||
watch: { | |||
visible: function (val) { | |||
this.dialogShow = val; | |||
}, | |||
}, | |||
methods: { | |||
resetDataInfo() { | |||
this.dataInfo = { | |||
ID: '', | |||
QueueCode: '', | |||
Cluster: '', | |||
AiCenterCode: '', | |||
ComputeResource: '', | |||
AccCardType: '', | |||
CardsTotalNum: '', | |||
Remark: '', | |||
} | |||
}, | |||
open() { | |||
this.resetDataInfo(); | |||
if (this.type === 'add') { | |||
// | |||
} else if (this.type === 'edit') { | |||
this.dataInfo = Object.assign(this.dataInfo, { ...this.data }); | |||
} | |||
this.$emit("open"); | |||
}, | |||
opened() { | |||
this.$emit("opened"); | |||
}, | |||
close() { | |||
this.$emit("close"); | |||
}, | |||
closed() { | |||
this.$emit("closed"); | |||
this.$emit("update:visible", false); | |||
}, | |||
confirm() { | |||
if (!this.dataInfo.QueueCode || !this.dataInfo.Cluster || !this.dataInfo.AiCenterCode || !this.dataInfo.ComputeResource || !this.dataInfo.AccCardType || !this.dataInfo.CardsTotalNum) { | |||
this.$message({ | |||
type: 'info', | |||
message: this.$t('pleaseCompleteTheInformationFirst'), | |||
}); | |||
return; | |||
} | |||
if (parseInt(this.dataInfo.CardsTotalNum) != Number(this.dataInfo.CardsTotalNum)) { | |||
this.$message({ | |||
type: 'info', | |||
message: this.$t('pleaseEnterPositiveIntegerCardsTotalNum') | |||
}); | |||
return; | |||
} | |||
const setApi = this.type === 'add' ? addResQueue : updateResQueue; | |||
setApi({ ...this.dataInfo, CardsTotalNum: Number(this.dataInfo.CardsTotalNum) }).then(res => { | |||
res = res.data; | |||
if (res.Code === 0) { | |||
this.$message({ | |||
type: 'success', | |||
message: this.$t('submittedSuccessfully') | |||
}); | |||
this.$emit("confirm"); | |||
} else { | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
}) | |||
}, | |||
cancel() { | |||
this.dialogShow = false; | |||
this.$emit("update:visible", false); | |||
} | |||
}, | |||
mounted() { | |||
this.resetDataInfo(); | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.dlg-content { | |||
margin: 20px 0 25px 0; | |||
display: flex; | |||
justify-content: center; | |||
.form { | |||
width: 600px; | |||
.form-row { | |||
display: flex; | |||
min-height: 42px; | |||
margin-bottom: 4px; | |||
.title { | |||
width: 160px; | |||
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: 300px; | |||
display: flex; | |||
align-items: center; | |||
/deep/ .el-select { | |||
width: 100%; | |||
} | |||
} | |||
} | |||
} | |||
.btn { | |||
color: rgb(2, 0, 4); | |||
background-color: rgb(194, 199, 204); | |||
border-color: rgb(194, 199, 204); | |||
&.confirm-btn { | |||
color: #fff; | |||
background-color: rgb(56, 158, 13); | |||
border-color: rgb(56, 158, 13); | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,366 @@ | |||
<template> | |||
<div class="base-dlg"> | |||
<BaseDialog :visible.sync="dialogShow" :width="`750px`" | |||
:title="type === 'add' ? $t('resourcesManagement.addResScene') : $t('resourcesManagement.editResScene')" | |||
@open="open" @opened="opened" @close="close" @closed="closed"> | |||
<div class="dlg-content"> | |||
<div class="form"> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.resSceneName') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.SceneName" placeholder="" maxlength="255"></el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.jobType') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.JobType" :disabled="type === 'edit'"> | |||
<el-option v-for="item in taskTypeList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.isExclusive') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.IsExclusive" @change="changeIsExclusive"> | |||
<el-option v-for="item in isExclusiveList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
</div> | |||
<div class="form-row" v-if="dataInfo.IsExclusive === '1'"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.exclusiveOrg') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.ExclusiveOrg" :placeholder="$t('resourcesManagement.exclusiveOrgTips')" | |||
maxlength="255"> | |||
</el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.computeCluster') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.Cluster" @change="changeCluster" :disabled="type === 'edit'"> | |||
<el-option v-for="item in clusterList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</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> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.SpecIds" multiple collapse-tags class="specSel"> | |||
<el-option v-for="item in specsList" :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> | |||
<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> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</BaseDialog> | |||
</div> | |||
</template> | |||
<script> | |||
import BaseDialog from '~/components/BaseDialog.vue'; | |||
import { getResQueueCode, getResSpecificationList, addResScene, updateResScene } from '~/apis/modules/resources'; | |||
import { JOB_TYPE, CLUSTERS, AI_CENTER, ACC_CARD_TYPE, SPECIFICATION_STATUS } from '~/const'; | |||
import { getListValueWithKey } from '~/utils'; | |||
export default { | |||
name: "SceneDialog", | |||
props: { | |||
visible: { type: Boolean, default: false }, | |||
title: { type: String, default: '' }, | |||
type: { type: String, defalut: 'add' }, | |||
data: { type: Object, default: () => ({}) }, | |||
}, | |||
components: { | |||
BaseDialog | |||
}, | |||
data() { | |||
return { | |||
dialogShow: false, | |||
dataInfo: {}, | |||
taskTypeList: [...JOB_TYPE], | |||
clusterList: [...CLUSTERS], | |||
accCardTypeList: [...ACC_CARD_TYPE], | |||
statusList: [...SPECIFICATION_STATUS], | |||
isExclusiveList: [{ k: '2', v: this.$t('resourcesManagement.commonUse') }, { k: '1', v: this.$t('resourcesManagement.exclusive') }], | |||
queueList: [], | |||
specsList: [], | |||
}; | |||
}, | |||
watch: { | |||
visible: function (val) { | |||
this.dialogShow = val; | |||
}, | |||
}, | |||
methods: { | |||
resetDataInfo() { | |||
this.dataInfo = { | |||
SceneName: '', | |||
JobType: '', | |||
IsExclusive: '2', | |||
ExclusiveOrg: '', | |||
Cluster: '', | |||
QueueId: '', | |||
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, | |||
queue: this.dataInfo.QueueId === '-1' ? '' : this.dataInfo.QueueId, | |||
status: 2, | |||
page: 1, | |||
}; | |||
return getResSpecificationList(params).then(res => { | |||
res = res.data; | |||
if (res.Code === 0) { | |||
const list = res.Data.List; | |||
const data = list.map((item) => { | |||
const Queue = item.Queue; | |||
const Spec = item.Spec; | |||
// const NGPU = `${Queue.ComputeResource}:${Spec.AccCardsNum === 0 ? '0' : Spec.AccCardsNum + '*' + getListValueWithKey(this.accCardTypeList, Queue.AccCardType)}`; | |||
const NGPU = `${Queue.ComputeResource}:${Spec.AccCardsNum + '*' + getListValueWithKey(this.accCardTypeList, Queue.AccCardType)}`; | |||
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')}`, | |||
} | |||
}); | |||
this.specsList.splice(0, Infinity, ...data); | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
}); | |||
}, | |||
changeIsExclusive() { | |||
this.dataInfo.ExclusiveOrg = ''; | |||
}, | |||
changeCluster() { | |||
this.dataInfo.QueueId = ''; | |||
this.dataInfo.SpecIds = []; | |||
this.queueList.splice(0, Infinity); | |||
this.specsList.splice(0, Infinity); | |||
this.getQueueList(true); | |||
}, | |||
changeQueue() { | |||
this.dataInfo.SpecIds = []; | |||
this.specsList.splice(0, Infinity); | |||
this.getResSpecificationList(); | |||
}, | |||
open() { | |||
this.resetDataInfo(); | |||
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); | |||
} | |||
this.$emit("open"); | |||
}, | |||
opened() { | |||
this.$emit("opened"); | |||
}, | |||
close() { | |||
this.$emit("close"); | |||
}, | |||
closed() { | |||
this.$emit("closed"); | |||
this.$emit("update:visible", false); | |||
}, | |||
confirm() { | |||
if (!this.dataInfo.SceneName || !this.dataInfo.JobType || !this.dataInfo.SpecIds.length || (this.dataInfo.IsExclusive === '1' && !this.dataInfo.ExclusiveOrg)) { | |||
this.$message({ | |||
type: 'info', | |||
message: this.$t('pleaseCompleteTheInformationFirst') | |||
}); | |||
return; | |||
} | |||
const setApi = this.type === 'add' ? addResScene : updateResScene; | |||
setApi({ | |||
...this.dataInfo, | |||
action: this.type === 'edit' ? 'edit' : undefined, | |||
IsExclusive: this.dataInfo.IsExclusive === '1', | |||
}).then(res => { | |||
res = res.data; | |||
if (res.Code === 0) { | |||
this.$message({ | |||
type: 'success', | |||
message: this.$t('submittedSuccessfully') | |||
}); | |||
this.$emit("confirm"); | |||
} else { | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
}) | |||
}, | |||
cancel() { | |||
this.dialogShow = false; | |||
this.$emit("update:visible", false); | |||
} | |||
}, | |||
mounted() { | |||
this.resetDataInfo(); | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.dlg-content { | |||
margin: 20px 0 25px 0; | |||
display: flex; | |||
justify-content: center; | |||
.form { | |||
width: 600px; | |||
.form-row { | |||
display: flex; | |||
min-height: 42px; | |||
margin-bottom: 4px; | |||
.title { | |||
width: 160px; | |||
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: 300px; | |||
display: flex; | |||
align-items: center; | |||
/deep/ .el-select { | |||
width: 100%; | |||
} | |||
} | |||
.specSel { | |||
/deep/ .el-tag.el-tag--info { | |||
max-width: 81%; | |||
display: flex; | |||
align-items: center; | |||
.el-select__tags-text { | |||
overflow: hidden; | |||
text-overflow: ellipsis; | |||
} | |||
.el-tag__close { | |||
flex-shrink: 0; | |||
right: -5px; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
.btn { | |||
color: rgb(2, 0, 4); | |||
background-color: rgb(194, 199, 204); | |||
border-color: rgb(194, 199, 204); | |||
&.confirm-btn { | |||
color: #fff; | |||
background-color: rgb(56, 158, 13); | |||
border-color: rgb(56, 158, 13); | |||
} | |||
} | |||
} | |||
.el-select-dropdown__item { | |||
padding-left: 26px !important; | |||
} | |||
.el-select-dropdown__item.selected::after { | |||
right: 0; | |||
left: 6px; | |||
} | |||
</style> |
@@ -0,0 +1,336 @@ | |||
<template> | |||
<div class="base-dlg"> | |||
<BaseDialog :visible.sync="dialogShow" :width="`700px`" | |||
:title="type === 'add' ? $t('resourcesManagement.addResSpecificationAndPriceInfo') : $t('resourcesManagement.editResSpecificationAndPriceInfo')" | |||
@open="open" @opened="opened" @close="close" @closed="closed"> | |||
<div class="dlg-content"> | |||
<div class="form"> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.resQueue') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.QueueId" :disabled="type === 'edit'"> | |||
<el-option v-for="item in this.queueList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title"> | |||
<span>{{ $t('resourcesManagement.sourceSpecCode') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.SourceSpecId" :placeholder="$t('resourcesManagement.sourceSpecCodeTips')" maxlength="255" | |||
:disabled="type === 'edit'"> | |||
</el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.accCardsNum') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.AccCardsNum" type="number" placeholder="" :disabled="type === 'edit'"> | |||
</el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.cpuNum') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.CpuCores" type="number" placeholder="" :disabled="type === 'edit'"></el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.gpuMem') }}(GB)</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.GPUMemGiB" type="number" placeholder="" :disabled="type === 'edit'"> | |||
</el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.mem') }}(GB)</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.MemGiB" type="number" placeholder="" :disabled="type === 'edit'"></el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.shareMem') }}(GB)</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.ShareMemGiB" type="number" placeholder="" :disabled="type === 'edit'"> | |||
</el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('resourcesManagement.unitPrice') }}({{ $t('resourcesManagement.point_hr') }})</span> | |||
</div> | |||
<div class="content"> | |||
<el-input v-model="dataInfo.UnitPrice" type="number" placeholder=""></el-input> | |||
</div> | |||
</div> | |||
<div class="form-row"> | |||
<div class="title required"> | |||
<span>{{ $t('status') }}</span> | |||
</div> | |||
<div class="content"> | |||
<el-select v-model="dataInfo.Status" :disabled="type === 'edit'"> | |||
<el-option v-for="item in this.statusList" :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> | |||
<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> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</BaseDialog> | |||
</div> | |||
</template> | |||
<script> | |||
import BaseDialog from '~/components/BaseDialog.vue'; | |||
import { getResQueueCode, addResSpecification, updateResSpecification, getAiCenterList } from '~/apis/modules/resources'; | |||
import { SPECIFICATION_STATUS, CLUSTERS } from '~/const'; | |||
import { getListValueWithKey } from '~/utils'; | |||
export default { | |||
name: "SpecificationDialog", | |||
props: { | |||
visible: { type: Boolean, default: false }, | |||
title: { type: String, default: '' }, | |||
type: { type: String, defalut: 'add' }, | |||
editOr: { type: Boolean, defalut: false }, | |||
data: { type: Object, default: () => ({}) }, | |||
}, | |||
components: { | |||
BaseDialog | |||
}, | |||
data() { | |||
return { | |||
dialogShow: false, | |||
dataInfo: {}, | |||
queueList: [], | |||
statusList: [...SPECIFICATION_STATUS], | |||
clusterList: [...CLUSTERS], | |||
aiCenterList: [], | |||
}; | |||
}, | |||
watch: { | |||
visible: function (val) { | |||
this.dialogShow = val; | |||
}, | |||
}, | |||
methods: { | |||
resetDataInfo() { | |||
this.dataInfo = { | |||
QueueId: '', | |||
AccCardsNum: '', | |||
CpuCores: '', | |||
MemGiB: '', | |||
ShareMemGiB: '', | |||
GPUMemGiB: '', | |||
UnitPrice: '', | |||
Status: '1', | |||
} | |||
}, | |||
getAiCenterList() { | |||
getAiCenterList().then(res => { | |||
res = res.data; | |||
if (res.Code === 0) { | |||
const list = res.Data; | |||
const data = list.map(item => { | |||
return { | |||
k: item.AiCenterCode, | |||
v: item.AiCenterName | |||
}; | |||
}); | |||
this.aiCenterList.splice(0, Infinity, ...data); | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
}); | |||
}, | |||
getQueueList() { | |||
getResQueueCode({ cluster: this.type === 'add' ? 'OpenI' : undefined }).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})`, | |||
}); | |||
} | |||
this.queueList.splice(0, Infinity, ...list); | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
}); | |||
}, | |||
open() { | |||
this.resetDataInfo(); | |||
this.getQueueList(); | |||
this.getAiCenterList(); | |||
if (this.type === 'add') { | |||
// | |||
} else if (this.type === 'edit') { | |||
this.dataInfo = Object.assign(this.dataInfo, { ...this.data, Status: '2' }); | |||
} | |||
this.$emit("open"); | |||
}, | |||
opened() { | |||
this.$emit("opened"); | |||
}, | |||
close() { | |||
this.$emit("close"); | |||
}, | |||
closed() { | |||
this.$emit("closed"); | |||
this.$emit("update:visible", false); | |||
}, | |||
confirm() { | |||
if (this.dataInfo.AccCardsNum === '' || this.dataInfo.CpuCores === '' || this.dataInfo.MemGiB === '' || this.dataInfo.ShareMemGiB === '' || this.dataInfo.GPUMemGiB === '' | |||
|| this.dataInfo.UnitPrice === '' || !this.dataInfo.Status | |||
) { | |||
this.$message({ | |||
type: 'info', | |||
message: this.$t('pleaseCompleteTheInformationFirst') | |||
}); | |||
return; | |||
} | |||
if (parseInt(this.dataInfo.AccCardsNum) != Number(this.dataInfo.AccCardsNum)) { | |||
this.$message({ | |||
type: 'info', | |||
message: this.$t('pleaseEnterPositiveIntegerCardsTotalNum') | |||
}); | |||
return; | |||
} | |||
const setApi = this.type === 'add' ? addResSpecification : updateResSpecification; | |||
const action = this.editOr ? 'edit' : this.type === 'edit' ? 'on-shelf' : undefined; | |||
setApi({ | |||
...this.dataInfo, | |||
action: action, | |||
AccCardsNum: Number(this.dataInfo.AccCardsNum), | |||
CpuCores: Number(this.dataInfo.CpuCores), | |||
MemGiB: Number(this.dataInfo.MemGiB), | |||
ShareMemGiB: Number(this.dataInfo.ShareMemGiB), | |||
GPUMemGiB: Number(this.dataInfo.GPUMemGiB), | |||
UnitPrice: Number(this.dataInfo.UnitPrice), | |||
Status: Number(this.dataInfo.Status), | |||
}).then(res => { | |||
res = res.data; | |||
if (res.Code === 0) { | |||
this.$message({ | |||
type: 'success', | |||
message: this.$t('submittedSuccessfully') | |||
}); | |||
this.$emit("confirm"); | |||
} else { | |||
if (action === 'on-shelf' && res.Code === 1001) { | |||
this.$message({ | |||
type: 'info', | |||
message: this.$t('resourcesManagement.onShelfCode1001') | |||
}); | |||
} else { | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
} | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
}) | |||
}, | |||
cancel() { | |||
this.dialogShow = false; | |||
this.$emit("update:visible", false); | |||
} | |||
}, | |||
mounted() { | |||
this.resetDataInfo(); | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.dlg-content { | |||
margin: 20px 0 25px 0; | |||
display: flex; | |||
justify-content: center; | |||
.form { | |||
width: 600px; | |||
.form-row { | |||
display: flex; | |||
min-height: 42px; | |||
margin-bottom: 4px; | |||
.title { | |||
width: 160px; | |||
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: 300px; | |||
display: flex; | |||
align-items: center; | |||
/deep/ .el-select { | |||
width: 100%; | |||
} | |||
} | |||
} | |||
} | |||
.btn { | |||
color: rgb(2, 0, 4); | |||
background-color: rgb(194, 199, 204); | |||
border-color: rgb(194, 199, 204); | |||
&.confirm-btn { | |||
color: #fff; | |||
background-color: rgb(56, 158, 13); | |||
border-color: rgb(56, 158, 13); | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,284 @@ | |||
<template> | |||
<div> | |||
<div class="title"><span>{{ $t('resourcesManagement.resQueue') }}</span></div> | |||
<div class="tools-bar"> | |||
<div> | |||
<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="selComputingCenter" @change="selectChange"> | |||
<el-option v-for="item in computingCenterList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
<el-select class="select" size="medium" v-model="selComputingType" @change="selectChange"> | |||
<el-option v-for="item in computingTypeList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
<el-select class="select" size="medium" v-model="selCardType" @change="selectChange"> | |||
<el-option v-for="item in cardTypeList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
<div> | |||
<el-button size="medium" icon="el-icon-refresh" @click="syncComputerNetwork" v-loading="syncLoading"> | |||
{{ $t('resourcesManagement.syncAiNetwork') }}</el-button> | |||
<el-button type="primary" icon="el-icon-plus" size="medium" @click="showDialog('add')"> | |||
{{ $t('resourcesManagement.addResQueueBtn') }}</el-button> | |||
</div> | |||
</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="80"></el-table-column> | |||
<el-table-column prop="QueueCode" :label="$t('resourcesManagement.resQueueName')" align="center" | |||
header-align="center"></el-table-column> | |||
<el-table-column prop="ClusterName" :label="$t('resourcesManagement.whichCluster')" align="center" | |||
header-align="center"> | |||
<template slot-scope="scope"> | |||
<span :title="scope.row.Cluster">{{ scope.row.ClusterName }}</span> | |||
</template> | |||
</el-table-column> | |||
<el-table-column prop="AiCenterCode" :label="$t('resourcesManagement.aiCenterID')" align="center" | |||
header-align="center"></el-table-column> | |||
<el-table-column prop="AiCenterName" :label="$t('resourcesManagement.aiCenter')" align="center" | |||
header-align="center"></el-table-column> | |||
<el-table-column prop="ComputeResourceName" :label="$t('resourcesManagement.computeResource')" align="center" | |||
header-align="center"> | |||
</el-table-column> | |||
<el-table-column prop="AccCardTypeName" :label="$t('resourcesManagement.accCardType')" align="center" | |||
header-align="center"></el-table-column> | |||
<el-table-column prop="CardsTotalNum" :label="$t('resourcesManagement.cardsTotalNum')" align="center" | |||
header-align="center"></el-table-column> | |||
<el-table-column prop="UpdatedTimeStr" :label="$t('resourcesManagement.lastUpdateTime')" align="center" | |||
header-align="center"></el-table-column> | |||
<el-table-column prop="Remark" :label="$t('resourcesManagement.remark')" align="left" header-align="center" | |||
min-width="160"> | |||
</el-table-column> | |||
<el-table-column :label="$t('operation')" align="center" header-align="center" width="80"> | |||
<template slot-scope="scope"> | |||
<span v-if="scope.row.Cluster !== 'C2Net'" class="op-btn" @click="showDialog('edit', scope.row)">{{ | |||
$t('edit') | |||
}}</span> | |||
<span v-else class="op-btn" style="color:rgb(187, 187, 187);cursor:not-allowed">{{ | |||
$t('edit') | |||
}}</span> | |||
</template> | |||
</el-table-column> | |||
<template slot="empty"> | |||
<span style="font-size: 12px">{{ | |||
loading ? $t('loading') : $t('noData') | |||
}}</span> | |||
</template> | |||
</el-table> | |||
</div> | |||
<div class="__r_p_pagination"> | |||
<div style="margin-top: 2rem"> | |||
<div class="center"> | |||
<el-pagination background @current-change="currentChange" :current-page="pageInfo.curpage" | |||
:page-sizes="pageInfo.pageSizes" :page-size="pageInfo.pageSize" | |||
layout="total, sizes, prev, pager, next, jumper" :total="pageInfo.total"> | |||
</el-pagination> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<QueueDialog :visible.sync="queueDialogShow" :type="queueDialogType" :data="queueDialogData" | |||
@confirm="queueDialogConfirm"></QueueDialog> | |||
</div> | |||
</template> | |||
<script> | |||
import QueueDialog from '../components/QueueDialog.vue'; | |||
import { getAiCenterList, getResQueueList, addResQueue, updateResQueue, syncResQueue } from '~/apis/modules/resources'; | |||
import { CLUSTERS, COMPUTER_RESOURCES, ACC_CARD_TYPE } from '~/const'; | |||
import { getListValueWithKey } from '~/utils'; | |||
import { formatDate } from 'element-ui/lib/utils/date-util'; | |||
export default { | |||
data() { | |||
return { | |||
selCluster: '', | |||
clusterList: [{ k: '', v: this.$t('resourcesManagement.allCluster') }, ...CLUSTERS], | |||
selComputingCenter: '', | |||
computingCenterList: [{ k: '', v: this.$t('resourcesManagement.allAiCenter') }], | |||
selComputingType: '', | |||
computingTypeList: [{ k: '', v: this.$t('resourcesManagement.allComputeResource') }, ...COMPUTER_RESOURCES], | |||
selCardType: '', | |||
cardTypeList: [{ k: '', v: this.$t('resourcesManagement.allAccCardType') }, ...ACC_CARD_TYPE], | |||
syncLoading: false, | |||
loading: false, | |||
tableData: [], | |||
pageInfo: { | |||
curpage: 1, | |||
pageSize: 10, | |||
pageSizes: [10], | |||
total: 0, | |||
}, | |||
queueDialogShow: false, | |||
queueDialogType: 'add', | |||
queueDialogData: {}, | |||
}; | |||
}, | |||
components: { QueueDialog }, | |||
methods: { | |||
getAiCenterList() { | |||
getAiCenterList().then(res => { | |||
res = res.data; | |||
if (res.Code === 0) { | |||
const list = res.Data; | |||
const data = list.map(item => { | |||
return { | |||
k: item.AiCenterCode, | |||
v: item.AiCenterName | |||
}; | |||
}); | |||
this.computingCenterList.splice(1, Infinity, ...data); | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
}); | |||
}, | |||
getTableData() { | |||
const params = { | |||
cluster: this.selCluster, | |||
center: this.selComputingCenter, | |||
resource: this.selComputingType, | |||
card: this.selCardType, | |||
page: this.pageInfo.curpage, | |||
pagesize: this.pageInfo.pageSize, | |||
}; | |||
this.loading = true; | |||
getResQueueList(params).then(res => { | |||
this.loading = false; | |||
res = res.data; | |||
if (res.Code === 0) { | |||
const list = res.Data.List; | |||
const data = list.map((item) => { | |||
return { | |||
...item, | |||
QueueCode: item.QueueCode || '--', | |||
ClusterName: getListValueWithKey(this.clusterList, item.Cluster), | |||
ComputeResourceName: getListValueWithKey(this.computingTypeList, item.ComputeResource), | |||
AccCardTypeName: getListValueWithKey(this.cardTypeList, item.AccCardType), | |||
UpdatedTimeStr: formatDate(new Date(item.UpdatedTime * 1000), 'yyyy-MM-dd HH:mm:ss'), | |||
} | |||
}); | |||
this.tableData = data; | |||
this.pageInfo.total = res.Data.TotalSize; | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
this.loading = false; | |||
}); | |||
}, | |||
syncComputerNetwork() { | |||
this.syncLoading = true; | |||
syncResQueue().then(res => { | |||
this.syncLoading = false; | |||
res = res.data; | |||
if (res.Code === 0) { | |||
this.$message({ | |||
type: 'success', | |||
message: this.$t('submittedSuccessfully') | |||
}); | |||
this.getAiCenterList(); | |||
this.getTableData(); | |||
} else { | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
this.syncLoading = false; | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
}); | |||
}, | |||
selectChange() { | |||
this.pageInfo.curpage = 1; | |||
this.getTableData(); | |||
}, | |||
currentChange(val) { | |||
this.pageInfo.curpage = val; | |||
this.getTableData(); | |||
}, | |||
showDialog(type, data) { | |||
this.queueDialogType = type; | |||
this.queueDialogData = data ? { ...data } : {}; | |||
this.queueDialogShow = true; | |||
}, | |||
queueDialogConfirm() { | |||
this.queueDialogShow = false; | |||
this.getAiCenterList(); | |||
this.getTableData(); | |||
} | |||
}, | |||
mounted() { | |||
this.getAiCenterList(); | |||
this.getTableData(); | |||
}, | |||
beforeDestroy() { | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.title { | |||
height: 30px; | |||
display: flex; | |||
align-items: center; | |||
margin-bottom: 5px; | |||
span { | |||
font-weight: 700; | |||
font-size: 16px; | |||
color: rgb(16, 16, 16); | |||
} | |||
} | |||
.tools-bar { | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
margin-bottom: 10px; | |||
.select { | |||
margin-right: 10px; | |||
/deep/ .el-input__inner { | |||
border-radius: 0; | |||
} | |||
} | |||
} | |||
.table-container { | |||
margin-bottom: 16px; | |||
/deep/ .el-table__header { | |||
th { | |||
background: rgb(245, 245, 246); | |||
font-size: 12px; | |||
color: rgb(36, 36, 36); | |||
} | |||
} | |||
/deep/ .el-table__body { | |||
td { | |||
font-size: 12px; | |||
} | |||
} | |||
.op-btn { | |||
cursor: pointer; | |||
font-size: 12px; | |||
color: rgb(25, 103, 252); | |||
margin: 0 5px; | |||
} | |||
} | |||
.center { | |||
display: flex; | |||
justify-content: center; | |||
} | |||
</style> |
@@ -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 './index.vue'; | |||
Vue.use(ElementUI, { | |||
locale: lang === 'zh-CN' ? localeZh : localeEn, | |||
size: 'small', | |||
}); | |||
new Vue({ | |||
i18n, | |||
render: (h) => h(App), | |||
}).$mount('#__vue-root'); |
@@ -0,0 +1,361 @@ | |||
<template> | |||
<div> | |||
<div class="title"><span>{{ $t('resourcesManagement.resSceneManagement') }}</span></div> | |||
<div class="tools-bar"> | |||
<div> | |||
<el-select class="select" size="medium" v-model="selTaskType" @change="selectChange"> | |||
<el-option v-for="item in taskTypeList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
<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="selAiCenter" @change="selectChange"> | |||
<el-option v-for="item in aiCenterList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
<el-select class="select" size="medium" v-model="selQueue" @change="selectChange"> | |||
<el-option v-for="item in queueList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
<div> | |||
<el-button type="primary" icon="el-icon-plus" size="medium" @click="showDialog('add')"> | |||
{{ $t('resourcesManagement.addResSceneBtn') }}</el-button> | |||
</div> | |||
</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-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" | |||
header-align="center" width="120"> | |||
</el-table-column> | |||
<el-table-column prop="IsExclusiveStr" :label="$t('resourcesManagement.isExclusive')" align="center" | |||
header-align="center" width="120"> | |||
<template slot-scope="scope"> | |||
<span :style="{ color: scope.row.IsExclusive ? 'red' : '' }">{{ scope.row.IsExclusiveStr }}</span> | |||
</template> | |||
</el-table-column> | |||
<el-table-column prop="ExclusiveOrg" :label="$t('resourcesManagement.exclusiveOrg')" align="center" | |||
header-align="center"> | |||
<template slot-scope="scope"> | |||
<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"> | |||
<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> | |||
</template> | |||
</el-table-column> | |||
<el-table-column prop="QueueStr" :label="$t('resourcesManagement.resQueue')" align="center" | |||
header-align="center"> | |||
<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> | |||
</template> | |||
</el-table-column> | |||
<el-table-column prop="SpecsList" :label="$t('resourcesManagement.resourceSpecification')" align="left" | |||
header-align="center" min-width="180"> | |||
<template slot-scope="scope"> | |||
<div v-for="item in scope.row.SpecsList" :key="item.k"> | |||
<span>{{ item.v }}</span> | |||
</div> | |||
</template> | |||
</el-table-column> | |||
<el-table-column :label="$t('operation')" align="center" header-align="center" width="100"> | |||
<template slot-scope="scope"> | |||
<span class="op-btn" @click="showDialog('edit', scope.row)">{{ $t('edit') }}</span> | |||
<span class="op-btn" style="color:red" @click="deleteRow(scope.row)">{{ $t('delete') }}</span> | |||
</template> | |||
</el-table-column> | |||
<template slot="empty"> | |||
<span style="font-size: 12px">{{ | |||
loading ? $t('loading') : $t('noData') | |||
}}</span> | |||
</template> | |||
</el-table> | |||
</div> | |||
<div class="__r_p_pagination"> | |||
<div style="margin-top: 2rem"> | |||
<div class="center"> | |||
<el-pagination background @current-change="currentChange" :current-page="pageInfo.curpage" | |||
:page-sizes="pageInfo.pageSizes" :page-size="pageInfo.pageSize" | |||
layout="total, sizes, prev, pager, next, jumper" :total="pageInfo.total"> | |||
</el-pagination> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<SceneDialog :visible.sync="sceneDialogShow" :type="sceneDialogType" :data="sceneDialogData" | |||
@confirm="sceneDialogConfirm"></SceneDialog> | |||
</div> | |||
</template> | |||
<script> | |||
import SceneDialog from '../components/SceneDialog.vue'; | |||
import { getQueueList, getResQueueCode, getResSceneList, updateResScene, getAiCenterList } from '~/apis/modules/resources'; | |||
import { JOB_TYPE, CLUSTERS, ACC_CARD_TYPE } from '~/const'; | |||
import { getListValueWithKey } from '~/utils'; | |||
import { formatDate } from 'element-ui/lib/utils/date-util'; | |||
export default { | |||
data() { | |||
return { | |||
selTaskType: '', | |||
taskTypeList: [{ k: '', v: this.$t('resourcesManagement.allJobType') }, ...JOB_TYPE], | |||
selIsExclusive: '', | |||
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], | |||
selAiCenter: '', | |||
aiCenterList: [{ k: '', v: this.$t('resourcesManagement.allAiCenter') }], | |||
accCardTypeList: [...ACC_CARD_TYPE], | |||
loading: false, | |||
tableData: [], | |||
pageInfo: { | |||
curpage: 1, | |||
pageSize: 10, | |||
pageSizes: [10], | |||
total: 0, | |||
}, | |||
sceneDialogShow: false, | |||
sceneDialogType: 'add', | |||
sceneDialogData: {}, | |||
}; | |||
}, | |||
components: { SceneDialog }, | |||
methods: { | |||
getAiCenterList() { | |||
getAiCenterList().then(res => { | |||
res = res.data; | |||
if (res.Code === 0) { | |||
const list = res.Data; | |||
const data = list.map(item => { | |||
return { | |||
k: item.AiCenterCode, | |||
v: item.AiCenterName | |||
}; | |||
}); | |||
this.aiCenterList.splice(1, Infinity, ...data); | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
}); | |||
}, | |||
getQueueList() { | |||
getResQueueCode().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})`, | |||
}); | |||
} | |||
this.queueList.push(...list); | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
}); | |||
}, | |||
getTableData() { | |||
const params = { | |||
jobType: this.selTaskType, | |||
IsExclusive: this.selIsExclusive, | |||
queue: this.selQueue, | |||
center: this.selAiCenter, | |||
page: this.pageInfo.curpage, | |||
pagesize: this.pageInfo.pageSize, | |||
}; | |||
this.loading = true; | |||
getResSceneList(params).then(res => { | |||
this.loading = false; | |||
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 === 0 ? '0' : Spec.AccCardsNum + '*' + getListValueWithKey(this.accCardTypeList, Spec.AccCardType)}`; | |||
const NGPU = `${Spec.ComputeResource}:${Spec.AccCardsNum + '*' + getListValueWithKey(this.accCardTypeList, Spec.AccCardType)}`; | |||
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')}`, | |||
}); | |||
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); | |||
} | |||
} | |||
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, | |||
} | |||
}); | |||
this.tableData = data; | |||
this.pageInfo.total = res.Data.TotalSize; | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
this.loading = false; | |||
}); | |||
}, | |||
selectChange() { | |||
this.pageInfo.curpage = 1; | |||
this.getTableData(); | |||
}, | |||
currentChange(val) { | |||
this.pageInfo.curpage = val; | |||
this.getTableData(); | |||
}, | |||
deleteRow(row) { | |||
this.$confirm(this.$t('resourcesManagement.resSceneDeleteConfirm'), this.$t('tips'), { | |||
confirmButtonText: this.$t('confirm1'), | |||
cancelButtonText: this.$t('cancel'), | |||
type: 'warning', | |||
lockScroll: false, | |||
}).then(() => { | |||
updateResScene({ | |||
action: 'delete', | |||
ID: row.ID, | |||
}).then(res => { | |||
res = res.data; | |||
if (res.Code === 0) { | |||
this.$message({ | |||
type: 'success', | |||
message: this.$t('submittedSuccessfully') | |||
}); | |||
this.getTableData(); | |||
} else { | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
} | |||
}).catch(err => { | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
}); | |||
}).catch(() => { }); | |||
}, | |||
showDialog(type, data) { | |||
this.sceneDialogType = type; | |||
this.sceneDialogData = data ? { | |||
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), | |||
} : {}; | |||
this.sceneDialogShow = true; | |||
}, | |||
sceneDialogConfirm() { | |||
this.sceneDialogShow = false; | |||
this.getTableData(); | |||
} | |||
}, | |||
mounted() { | |||
this.getAiCenterList(); | |||
this.getQueueList(); | |||
this.getTableData(); | |||
}, | |||
beforeDestroy() { | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.title { | |||
height: 30px; | |||
display: flex; | |||
align-items: center; | |||
margin-bottom: 5px; | |||
span { | |||
font-weight: 700; | |||
font-size: 16px; | |||
color: rgb(16, 16, 16); | |||
} | |||
} | |||
.tools-bar { | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
margin-bottom: 10px; | |||
.select { | |||
margin-right: 10px; | |||
/deep/ .el-input__inner { | |||
border-radius: 0; | |||
} | |||
} | |||
} | |||
.table-container { | |||
margin-bottom: 16px; | |||
/deep/ .el-table__header { | |||
th { | |||
background: rgb(245, 245, 246); | |||
font-size: 12px; | |||
color: rgb(36, 36, 36); | |||
} | |||
} | |||
/deep/ .el-table__body { | |||
td { | |||
font-size: 12px; | |||
} | |||
} | |||
.op-btn { | |||
cursor: pointer; | |||
font-size: 12px; | |||
color: rgb(25, 103, 252); | |||
margin: 0 5px; | |||
} | |||
} | |||
.center { | |||
display: flex; | |||
justify-content: center; | |||
} | |||
</style> |
@@ -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 './index.vue'; | |||
Vue.use(ElementUI, { | |||
locale: lang === 'zh-CN' ? localeZh : localeEn, | |||
size: 'small', | |||
}); | |||
new Vue({ | |||
i18n, | |||
render: (h) => h(App), | |||
}).$mount('#__vue-root'); |
@@ -0,0 +1,451 @@ | |||
<template> | |||
<div> | |||
<div class="title"><span>{{ $t('resourcesManagement.resSpecificationAndPriceManagement') }}</span></div> | |||
<div class="tools-bar"> | |||
<div> | |||
<el-select class="select" size="medium" v-model="selQueue" @change="selectChange"> | |||
<el-option v-for="item in queueList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
<el-select class="select" size="medium" v-model="selStatus" @change="selectChange"> | |||
<el-option v-for="item in statusList" :key="item.k" :label="item.v" :value="item.k" /> | |||
</el-select> | |||
</div> | |||
<div> | |||
<el-button size="medium" icon="el-icon-refresh" @click="syncComputerNetwork" v-loading="syncLoading"> | |||
{{ $t('resourcesManagement.syncAiNetwork') }}</el-button> | |||
<el-button type="primary" icon="el-icon-plus" size="medium" @click="showDialog('add')"> | |||
{{ $t('resourcesManagement.addResSpecificationBtn') }}</el-button> | |||
</div> | |||
</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-column prop="SpecStr" :label="$t('resourcesManagement.resourceSpecification')" align="left" | |||
header-align="center" min-width="160"> | |||
</el-table-column> | |||
<el-table-column prop="QueueInfo" :label="$t('resourcesManagement.resQueue')" align="center" | |||
header-align="center" min-width="100"> | |||
</el-table-column> | |||
<el-table-column prop="SourceSpecId" :label="$t('resourcesManagement.sourceSpecCode')" align="center" | |||
header-align="center"> | |||
</el-table-column> | |||
<el-table-column prop="AccCardsNum" :label="$t('resourcesManagement.accCardsNum')" align="center" | |||
header-align="center"></el-table-column> | |||
<el-table-column prop="CpuCores" :label="$t('resourcesManagement.cpuNum')" align="center" | |||
header-align="center"></el-table-column> | |||
<el-table-column prop="GPUMemGiB" :label="`${$t('resourcesManagement.gpuMem')}(GB)`" align="center" | |||
header-align="center"></el-table-column> | |||
<el-table-column prop="MemGiB" :label="`${$t('resourcesManagement.mem')}(GB)`" align="center" | |||
header-align="center"></el-table-column> | |||
<el-table-column prop="ShareMemGiB" :label="`${$t('resourcesManagement.shareMem')}(GB)`" align="center" | |||
header-align="center"></el-table-column> | |||
<el-table-column prop="UpdatedTimeStr" :label="$t('resourcesManagement.lastUpdateTime')" align="center" | |||
header-align="center"></el-table-column> | |||
<el-table-column prop="UnitPrice" | |||
:label="`${$t('resourcesManagement.unitPrice')}(${$t('resourcesManagement.point_hr')})`" align="center" | |||
header-align="center"> | |||
<template slot-scope="scope"> | |||
<span style="font-weight:600;font-size:14px;">{{ scope.row.UnitPrice }}</span> | |||
</template> | |||
</el-table-column> | |||
<el-table-column prop="StatusStr" :label="$t('resourcesManagement.status')" align="center" | |||
header-align="center" width="100"> | |||
<template slot-scope="scope"> | |||
<span :style="{ color: scope.row.Status == '2' ? 'rgb(82, 196, 26)' : 'rgb(245, 34, 45)' }">{{ | |||
scope.row.StatusStr | |||
}}</span> | |||
</template> | |||
</el-table-column> | |||
<el-table-column :label="$t('operation')" align="center" header-align="center" width="100"> | |||
<template slot-scope="scope"> | |||
<span v-if="scope.row.Status == '1' && !scope.row.UnitPrice"> | |||
<span class="op-btn" @click="showDialog('edit', scope.row)">{{ | |||
$t('resourcesManagement.toSetPriceAndOnShelf') | |||
}}</span> | |||
</span> | |||
<span v-if="scope.row.Status == '2'"> | |||
<span class="op-btn" @click="showDialog('edit', scope.row, true)">{{ $t('edit') }}</span> | |||
<span class="op-btn" @click="offShelfPrev(scope.row)">{{ | |||
$t('resourcesManagement.toOffShelf') | |||
}}</span> | |||
</span> | |||
<span v-if="scope.row.Status == '3' || scope.row.Status == '1' && scope.row.UnitPrice"> | |||
<span class="op-btn" @click="onShelf(scope.row)">{{ | |||
$t('resourcesManagement.toOnShelf') | |||
}}</span> | |||
</span> | |||
</template> | |||
</el-table-column> | |||
<template slot="empty"> | |||
<span style="font-size: 12px">{{ | |||
loading ? $t('loading') : $t('noData') | |||
}}</span> | |||
</template> | |||
</el-table> | |||
</div> | |||
<div class="__r_p_pagination"> | |||
<div style="margin-top: 2rem"> | |||
<div class="center"> | |||
<el-pagination background @current-change="currentChange" :current-page="pageInfo.curpage" | |||
:page-sizes="pageInfo.pageSizes" :page-size="pageInfo.pageSize" | |||
layout="total, sizes, prev, pager, next, jumper" :total="pageInfo.total"> | |||
</el-pagination> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<SpecificationDialog :visible.sync="specificationDialogShow" :type="specificationDialogType" | |||
:editOr="specificationDialogEditOr" :data="specificationDialogData" @confirm="specificationDialogConfirm"> | |||
</SpecificationDialog> | |||
<BaseDialog :visible.sync="offShelfDialogShow" :width="`600px`" :title="$t('tips')"> | |||
<div class="form"> | |||
<div class="form-row" style="flex-direction:column;"> | |||
<div class="content" style="margin:8px 0">{{ $t('resourcesManagement.offShelfDlgTip1') }}</div> | |||
<div class="content" style="margin:8px 0;font-weight: bold;">{{ offSelfDialogContent }}</div> | |||
<div class="content" style="margin:8px 0">{{ $t('resourcesManagement.offShelfDlgTip2') }}</div> | |||
</div> | |||
<div class="form-row" style="margin-top: 20px"> | |||
<div class="content"> | |||
<el-button type="primary" class="btn confirm-btn" @click="offShelf">{{ $t('confirm') }}</el-button> | |||
<el-button class="btn" @click="offShelfDialogShow = false">{{ $t('cancel') }}</el-button> | |||
</div> | |||
</div> | |||
</div> | |||
</BaseDialog> | |||
</div> | |||
</template> | |||
<script> | |||
import SpecificationDialog from '../components/SpecificationDialog.vue'; | |||
import BaseDialog from '~/components/BaseDialog.vue'; | |||
import { getResQueueCode, getResSpecificationList, updateResSpecification, syncResSpecification, getResSpecificationScenes } from '~/apis/modules/resources'; | |||
import { SPECIFICATION_STATUS, CLUSTERS, ACC_CARD_TYPE } from '~/const'; | |||
import { getListValueWithKey } from '~/utils'; | |||
import { formatDate } from 'element-ui/lib/utils/date-util'; | |||
export default { | |||
data() { | |||
return { | |||
selQueue: '', | |||
queueList: [{ k: '', v: this.$t('resourcesManagement.allResQueue') }], | |||
selStatus: '', | |||
statusList: [{ k: '', v: this.$t('resourcesManagement.allStatus') }, ...SPECIFICATION_STATUS], | |||
clusterList: [...CLUSTERS], | |||
accCardTypeList: [...ACC_CARD_TYPE], | |||
syncLoading: false, | |||
loading: false, | |||
tableData: [], | |||
pageInfo: { | |||
curpage: 1, | |||
pageSize: 10, | |||
pageSizes: [10], | |||
total: 0, | |||
}, | |||
specificationDialogShow: false, | |||
specificationDialogType: 'add', | |||
specificationDialogEditOr: false, | |||
specificationDialogData: {}, | |||
offShelfDialogShow: false, | |||
offShelfDialogData: {}, | |||
offSelfDialogContent: '', | |||
}; | |||
}, | |||
components: { BaseDialog, SpecificationDialog }, | |||
methods: { | |||
getQueueList() { | |||
getResQueueCode().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})`, | |||
}); | |||
} | |||
this.queueList.push(...list); | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
}); | |||
}, | |||
getTableData() { | |||
const params = { | |||
queue: this.selQueue, | |||
status: this.selStatus, | |||
page: this.pageInfo.curpage, | |||
pagesize: this.pageInfo.pageSize, | |||
}; | |||
this.loading = true; | |||
getResSpecificationList(params).then(res => { | |||
this.loading = false; | |||
res = res.data; | |||
if (res.Code === 0) { | |||
const list = res.Data.List; | |||
const data = list.map((item) => { | |||
const Queue = item.Queue; | |||
const Spec = item.Spec; | |||
// const NGPU = `${Queue.ComputeResource}:${Spec.AccCardsNum === 0 ? '0' : Spec.AccCardsNum + '*' + getListValueWithKey(this.accCardTypeList, Queue.AccCardType)}`; | |||
const NGPU = `${Queue.ComputeResource}:${Spec.AccCardsNum + '*' + getListValueWithKey(this.accCardTypeList, Queue.AccCardType)}`; | |||
return { | |||
...Spec, | |||
SourceSpecId: Spec.SourceSpecId || '--', | |||
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`, | |||
QueueId: Queue.ID, | |||
QueueInfo: `${Queue.QueueCode}(${getListValueWithKey(this.clusterList, Queue.Cluster)} - ${Queue.AiCenterName})`, | |||
UpdatedTimeStr: formatDate(new Date(Spec.UpdatedTime * 1000), 'yyyy-MM-dd HH:mm:ss'), | |||
Status: Spec.Status.toString(), | |||
StatusStr: getListValueWithKey(this.statusList, Spec.Status.toString()), | |||
} | |||
}); | |||
this.tableData = data; | |||
this.pageInfo.total = res.Data.TotalSize; | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
this.loading = false; | |||
}); | |||
}, | |||
syncComputerNetwork() { | |||
this.syncLoading = true; | |||
syncResSpecification().then(res => { | |||
this.syncLoading = false; | |||
res = res.data; | |||
if (res.Code === 0) { | |||
this.$message({ | |||
type: 'success', | |||
message: this.$t('submittedSuccessfully') | |||
}); | |||
this.getTableData(); | |||
} else { | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
this.syncLoading = false; | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
}); | |||
}, | |||
selectChange() { | |||
this.pageInfo.curpage = 1; | |||
this.getTableData(); | |||
}, | |||
currentChange(val) { | |||
this.pageInfo.curpage = val; | |||
this.getTableData(); | |||
}, | |||
showDialog(type, data, editOr) { | |||
this.specificationDialogType = type; | |||
this.specificationDialogEditOr = !!editOr; | |||
this.specificationDialogData = data ? { ...data } : {}; | |||
this.specificationDialogShow = true; | |||
}, | |||
specificationDialogConfirm() { | |||
this.specificationDialogShow = false; | |||
this.getTableData(); | |||
}, | |||
onShelf(data) { | |||
const type = 'on-shelf'; | |||
this.$confirm(type === 'on-shelf' ? this.$t('resourcesManagement.onShelfConfirm') : this.$t('resourcesManagement.offShelfConfirm'), this.$t('tips'), { | |||
confirmButtonText: this.$t('confirm1'), | |||
cancelButtonText: this.$t('cancel'), | |||
type: 'warning', | |||
lockScroll: false, | |||
}).then(() => { | |||
updateResSpecification({ | |||
ID: data.ID, | |||
action: type | |||
}).then(res => { | |||
res = res.data; | |||
if (res.Code === 0) { | |||
this.$message({ | |||
type: 'success', | |||
message: this.$t('submittedSuccessfully') | |||
}); | |||
this.getTableData(); | |||
} else { | |||
if (type === 'on-shelf' && res.Code === 1001) { | |||
this.$message({ | |||
type: 'info', | |||
message: this.$t('resourcesManagement.onShelfCode1001') | |||
}); | |||
} else { | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
} | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
}); | |||
}).catch(() => { }); | |||
}, | |||
offShelfPrev(data) { | |||
this.$confirm(this.$t('resourcesManagement.offShelfConfirm'), this.$t('tips'), { | |||
confirmButtonText: this.$t('confirm1'), | |||
cancelButtonText: this.$t('cancel'), | |||
type: 'warning', | |||
lockScroll: false, | |||
}).then(() => { | |||
this.offShelfDialogData = data; | |||
getResSpecificationScenes({ ID: data.ID }).then(res => { | |||
res = res.data; | |||
if (res.Code === 0) { | |||
if (res.Data.List.length) { | |||
this.offShelfDialogShow = true; | |||
this.offSelfDialogContent = res.Data.List.map((item) => `[${item.ID}]${item.SceneName}`).join(', '); | |||
} else { | |||
this.offShelf(); | |||
} | |||
} else { | |||
console.log(res); | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
}); | |||
}).catch(() => { }); | |||
}, | |||
offShelf() { | |||
updateResSpecification({ | |||
ID: this.offShelfDialogData.ID, | |||
action: 'off-shelf' | |||
}).then(res => { | |||
res = res.data; | |||
if (res.Code === 0) { | |||
this.$message({ | |||
type: 'success', | |||
message: this.$t('submittedSuccessfully') | |||
}); | |||
this.offShelfDialogShow = false; | |||
this.getTableData(); | |||
} else { | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
this.$message({ | |||
type: 'error', | |||
message: this.$t('submittedFailed') | |||
}); | |||
}); | |||
}, | |||
}, | |||
mounted: function () { | |||
this.getQueueList(); | |||
this.getTableData(); | |||
}, | |||
beforeDestroy: function () { | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.title { | |||
height: 30px; | |||
display: flex; | |||
align-items: center; | |||
margin-bottom: 5px; | |||
span { | |||
font-weight: 700; | |||
font-size: 16px; | |||
color: rgb(16, 16, 16); | |||
} | |||
} | |||
.tools-bar { | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
margin-bottom: 10px; | |||
.select { | |||
margin-right: 10px; | |||
/deep/ .el-input__inner { | |||
border-radius: 0; | |||
} | |||
} | |||
} | |||
.table-container { | |||
margin-bottom: 16px; | |||
/deep/ .el-table__header { | |||
th { | |||
background: rgb(245, 245, 246); | |||
font-size: 12px; | |||
color: rgb(36, 36, 36); | |||
} | |||
} | |||
/deep/ .el-table__body { | |||
td { | |||
font-size: 12px; | |||
} | |||
} | |||
.op-btn { | |||
cursor: pointer; | |||
font-size: 12px; | |||
color: rgb(25, 103, 252); | |||
margin-right: 4px; | |||
} | |||
} | |||
.center { | |||
display: flex; | |||
justify-content: center; | |||
} | |||
.form { | |||
margin: 5px 0 5px 0; | |||
display: flex; | |||
justify-content: center; | |||
flex-direction: column; | |||
align-items: center; | |||
} | |||
.form-row { | |||
display: flex; | |||
min-height: 42px; | |||
margin-bottom: 4px; | |||
.content { | |||
width: 500px; | |||
display: flex; | |||
align-items: center; | |||
} | |||
} | |||
.btn { | |||
color: rgb(2, 0, 4); | |||
background-color: rgb(194, 199, 204); | |||
border-color: rgb(194, 199, 204); | |||
&.confirm-btn { | |||
color: #fff; | |||
background-color: rgb(56, 158, 13); | |||
border-color: rgb(56, 158, 13); | |||
} | |||
} | |||
</style> |
@@ -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 './index.vue'; | |||
Vue.use(ElementUI, { | |||
locale: lang === 'zh-CN' ? localeZh : localeEn, | |||
size: 'small', | |||
}); | |||
new Vue({ | |||
i18n, | |||
render: (h) => h(App), | |||
}).$mount('#__vue-root'); |
@@ -0,0 +1,148 @@ | |||
import { formatDate } from 'element-ui/lib/utils/date-util'; | |||
import { SOURCE_TYPE, CONSUME_STATUS, POINT_ACTIONS, JOB_TYPE } from '~/const'; | |||
import { i18n } from '~/langs'; | |||
const getSourceType = (key) => { | |||
const find = SOURCE_TYPE.filter(item => item.k === key); | |||
return find.length ? find[0].v : key; | |||
}; | |||
const getConsumeStatus = (key) => { | |||
const find = CONSUME_STATUS.filter(item => item.k === key); | |||
return find.length ? find[0].v : key; | |||
}; | |||
const getPointAction = (key) => { | |||
const find = POINT_ACTIONS.filter(item => item.k === key); | |||
return find.length ? find[0].v : key; | |||
}; | |||
const getJobType = (key) => { | |||
const find = JOB_TYPE.filter(item => item.k === key); | |||
return find.length ? find[0].v : key; | |||
}; | |||
const getJobTypeLink = (record, type) => { | |||
let link = type === 'INCREASE' ? record.Action.RepoLink : '/' + record.Cloudbrain.RepoFullName; | |||
const cloudbrain = type === 'INCREASE' ? record.Action?.Cloudbrain : record.Cloudbrain; | |||
switch (cloudbrain?.JobType) { | |||
case 'DEBUG': | |||
if (cloudbrain.ComputeResource === 'CPU/GPU') { | |||
link += `/cloudbrain/${cloudbrain.ID}`; | |||
} else { | |||
link += `/modelarts/notebook/${cloudbrain.ID}`; | |||
} | |||
break; | |||
case 'TRAIN': | |||
if (cloudbrain.Type === 1) { | |||
link += `/modelarts/train-job/${cloudbrain.JobID}`; | |||
} else if (cloudbrain.Type === 0) { | |||
link += `/cloudbrain/train-job/${cloudbrain.JobID}`; | |||
} else if (cloudbrain.Type === 2) { | |||
link += `/grampus/train-job/${cloudbrain.JobID}`; | |||
} | |||
break; | |||
case 'INFERENCE': | |||
link += `/modelarts/inference-job/${cloudbrain.JobID}`; | |||
break; | |||
case 'BENCHMARK': | |||
link += `/cloudbrain/benchmark/${cloudbrain.ID}`; | |||
break; | |||
default: | |||
break; | |||
}; | |||
return link; | |||
}; | |||
export const getRewardPointRecordInfo = (record) => { | |||
const out = { | |||
sn: record.SerialNo, | |||
date: formatDate(new Date(record.LastOperateDate * 1000), 'yyyy-MM-dd HH:mm:ss'), | |||
_status: record.Status, | |||
status: getConsumeStatus(record.Status) || '--', | |||
statusColor: record.Status === 'OPERATING' ? 'rgb(33, 186, 69)' : '', | |||
_sourceType: record.SourceType, | |||
sourceType: getSourceType(record.SourceType), | |||
duration: record?.Cloudbrain?.Duration || '--', | |||
taskName: record?.Cloudbrain?.DisplayJobName || '--', | |||
taskId: record?.Cloudbrain?.ID, | |||
action: record?.Action?.OpType ? getPointAction(record.Action.OpType) : '--', | |||
remark: record.Remark, | |||
amount: record.Amount, | |||
}; | |||
if (record.OperateType === 'INCREASE') { | |||
if (record.SourceType === 'ADMIN_OPERATE') { | |||
out.remark = record.Remark; | |||
} else if (record.SourceType === 'ACCOMPLISH_TASK') { | |||
switch (record?.Action?.OpType) { | |||
case 1: // 创建公开项目 - 创建了项目OpenI/aiforge | |||
out.remark = `${i18n.t('createdRepository')}<a href="${record.Action.RepoLink}" rel="nofollow">${record.Action.ShortRepoFullDisplayName}</a>`; | |||
break; | |||
case 6: // 每日提出任务 - 创建了任务PCL-Platform.Intelligence/AISynergy#19 | |||
out.remark = `${i18n.t('openedIssue')}<a href="${record.Action.RepoLink}/issues/${record.Action.IssueInfos[0]}" rel="nofollow">${record.Action.ShortRepoFullDisplayName}#${record.Action.IssueInfos[0]}</a>`; | |||
break; | |||
case 7: // 每日提出PR - 创建了合并请求OpenI/aiforge#1 | |||
out.remark = `${i18n.t('createdPullRequest')}<a href="${record.Action.RepoLink}/pulls/${record.Action.IssueInfos[0]}" rel="nofollow">${record.Action.ShortRepoFullDisplayName}#${record.Action.IssueInfos[0]}</a>`; | |||
break; | |||
case 10: // 发表评论 - 评论了任务PCL-Platform.Intelligence/AISynergy#19 | |||
out.remark = `${i18n.t('commentedOnIssue')}<a href="${record.Action.CommentLink}" rel="nofollow">${record.Action.ShortRepoFullDisplayName}#${record.Action.IssueInfos[0]}</a>`; | |||
break; | |||
case 24: // 上传数据集文件 - 上传了数据集文件MMISTData.zip | |||
out.remark = `${i18n.t('uploadDataset')}<a href="${record.Action.RepoLink}/datasets" rel="nofollow">${record.Action.RefName}</a>`; | |||
break; | |||
case 30: // 导入新模型 - 导入了新模型resnet50_qx7l | |||
out.remark = `${i18n.t('createdNewModel')}<a href="${record.Action.RepoLink}/modelmanage/show_model_info?name=${record.Action.RefName}" rel="nofollow">${record.Action.RefName}</a>`; | |||
break; | |||
case 34: // 完成微信扫码验证 - 首次绑定微信奖励 | |||
out.remark = `${i18n.t('firstBindingWechatRewards')}`; | |||
break; | |||
case 35: // 每日运行云脑任务 - 创建了(CPU/GPU/NPU)类型(调试/训练/推理/评测)任务tangl202204131431995 | |||
out.remark = `${i18n.t('created')}${record.Action?.Cloudbrain?.ComputeResource}${i18n.t('type')}${getJobType(record.Action?.Cloudbrain?.JobType)} <a href="${getJobTypeLink(record, 'INCREASE')}" rel="nofollow">${record.Action.RefName}</a>`; | |||
break; | |||
case 36: // 数据集被平台推荐 - 数据集XXX被设置为推荐数据集 | |||
out.remark = `${i18n.t('dataset')}<a href="${record.Action.RepoLink}/datasets" rel="nofollow">${record.Action.Content && record.Action.Content.split('|')[1]}</a>${i18n.t('setAsRecommendedDataset')}`; | |||
break; | |||
case 37: // 提交新公开镜像 - 提交了镜像jiangxiang_ceshi_tang03 | |||
out.remark = `${i18n.t('committedImage')}<span style="font-weight:bold;">${record.Action.Content && record.Action.Content.split('|')[1]}</span>`; | |||
break; | |||
case 38: // 镜像被平台推荐 - 镜像XXX被设置为推荐镜像 | |||
out.remark = `${i18n.t('image')}<span style="font-weight:bold;">${record.Action.Content && record.Action.Content.split('|')[1]}</span>${i18n.t('setAsRecommendedImage')}`; | |||
break; | |||
case 39: // 首次更换头像 - 更新了头像 | |||
out.remark = `${i18n.t('updatedAvatar')}`; | |||
break; | |||
case 40: // 每日commit - 推送了xxxx分支的代码到OpenI/aiforge | |||
const words = record.Action.RefName.split('/'); | |||
const branch = words[words.length - 1]; | |||
out.remark = `${i18n.t('pushedBranch', { | |||
branch: `<a href="${record.Action.RepoLink}/src/branch/${branch}" rel="nofollow">${branch}</a>` | |||
})}<a href="${record.Action.RepoLink}" rel="nofollow">${record.Action.ShortRepoFullDisplayName}</a>`; | |||
break; | |||
default: | |||
break; | |||
} | |||
} else if (record.SourceType === 'RUN_CLOUDBRAIN_TASK') { | |||
// | |||
} | |||
if (record.LossAmount !== 0) { | |||
out.amount = record.Amount; | |||
out.remark += `${out.remark ? i18n.t(';') : ''}${i18n.t('dailyMaxTips')}`; | |||
} | |||
} else if (record.OperateType === 'DECREASE') { | |||
if (record.SourceType === 'ADMIN_OPERATE') { | |||
out.remark = record.Remark; | |||
} else if (record.SourceType === 'ACCOMPLISH_TASK') { | |||
// | |||
} else if (record.SourceType === 'RUN_CLOUDBRAIN_TASK') { | |||
out.taskName = `<a href="${getJobTypeLink(record, 'DECREASE')}" rel="nofollow">${record?.Cloudbrain?.DisplayJobName}</a>`; | |||
if (record?.Cloudbrain?.ComputeResource === 'CPU/GPU') { | |||
const resourceSpec = record?.Cloudbrain?.ResourceSpec?.ResourceSpec; | |||
out.remark = `【${getJobType(record?.Cloudbrain?.JobType)}】【${record?.Cloudbrain?.ComputeResource}】【GPU: ${resourceSpec?.gpu}, CPU: ${resourceSpec?.cpu}, ${i18n.t('memory')}: ${(resourceSpec?.memMiB / 1024).toFixed(2)}GB, ${i18n.t('sharedMemory')}: ${(resourceSpec?.shareMemMiB / 1024).toFixed(2)}GB】`; | |||
} else { | |||
out.remark = `【${getJobType(record?.Cloudbrain?.JobType)}】【${record?.Cloudbrain?.ComputeResource}】【${record?.Cloudbrain?.ResourceSpec.FlavorInfo.desc}】`; | |||
} | |||
} | |||
} | |||
return out; | |||
}; |
@@ -0,0 +1,16 @@ | |||
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 './vp-point.vue'; | |||
Vue.use(ElementUI, { | |||
locale: lang === 'zh-CN' ? localeZh : localeEn | |||
}); | |||
new Vue({ | |||
i18n, | |||
render: (h) => h(App), | |||
}).$mount('#__vue-root'); |
@@ -0,0 +1,308 @@ | |||
<template> | |||
<div class="__reward-pointer-c"> | |||
<div class="ui container" style="width:80%;min-width:1200px;"> | |||
<div class="__r_p_header"> | |||
<div> | |||
<p class="__title">{{ $t('calcPointDetails') }}</p> | |||
</div> | |||
<div style="padding: 0 5px; font-size: 14px"> | |||
<span> | |||
<i class="question circle icon link" style="color: rgba(3, 102, 214, 1)" data-position="right center" | |||
data-variation="mini"></i> | |||
<a href="/reward/point/rule" target="_blank" style="color: rgba(3, 102, 214, 1)">{{ | |||
$t('calcPointAcquisitionInstructions') | |||
}}</a> | |||
</span> | |||
</div> | |||
</div> | |||
<div class="__r_p_summary"> | |||
<div class="__r_p_summary_item-c __flex-1"> | |||
<div class="__val">{{ summaryInfo.available }}</div> | |||
<div class="__exp">{{ $t('CurrAvailableCalcPoints') }}</div> | |||
</div> | |||
<div class="__r_p_summary_line"></div> | |||
<div class="__r_p_summary_item-c __flex-1"> | |||
<div class="__val">{{ summaryInfo.gain }}</div> | |||
<div class="__exp">{{ $t('totalGainCalcPoints') }}</div> | |||
</div> | |||
<div class="__r_p_summary_item-c __flex-1"> | |||
<div class="__val">{{ summaryInfo.used }}</div> | |||
<div class="__exp">{{ $t('totalConsumeCalcPoints') }}</div> | |||
</div> | |||
</div> | |||
<div class="__r_p_tab"> | |||
<div class="__r_p_tab-item" :class="tabIndex === 0 ? '__focus' : ''" style="border-radius: 5px 0px 0px 5px" | |||
@click="tabChange(0)"> | |||
{{ $t('gainDetail') }} | |||
</div> | |||
<div class="__r_p_tab-item" :class="tabIndex === 1 ? '__focus' : ''" style="border-radius: 0px 5px 5px 0px" | |||
@click="tabChange(1)"> | |||
{{ $t('consumeDetail') }} | |||
</div> | |||
</div> | |||
<div class="__r_p_table"> | |||
<div v-show="tabIndex === 0"> | |||
<el-table :data="tableData" row-key="sn" style="width: 100%" v-loading="loading" stripe | |||
v-if="tableData.length"> | |||
<el-table-column column-key="sn" prop="sn" :label="$t('serialNumber')" align="center" header-align="center" | |||
width="180"> | |||
</el-table-column> | |||
<el-table-column column-key="date" prop="date" :label="$t('time')" align="center" header-align="center" | |||
width="180"> | |||
</el-table-column> | |||
<el-table-column column-key="sourceType" prop="sourceType" :label="$t('scene')" align="center" | |||
header-align="center" width="180"></el-table-column> | |||
<el-table-column column-key="action" prop="action" :label="$t('behaviorOfPoint')" align="center" | |||
header-align="center" width="200"></el-table-column> | |||
<el-table-column column-key="remark" prop="remark" :label="$t('explanation')" align="left" min-width="200" | |||
header-align="center"> | |||
<template slot-scope="scope"> | |||
<span v-html="scope.row.remark"></span> | |||
</template> | |||
</el-table-column> | |||
<el-table-column column-key="amount" prop="amount" :label="$t('points')" align="center" | |||
header-align="center" width="120"></el-table-column> | |||
<template slot="empty"> | |||
<span>{{ loading ? $t('loading') : $t('noData') }}</span> | |||
</template> | |||
</el-table> | |||
<el-empty v-else :image-size="140" :description="$t('noPointGainRecord')"></el-empty> | |||
</div> | |||
<div v-show="tabIndex === 1"> | |||
<el-table :data="tableData" row-key="sn" style="width: 100%" v-loading="loading" stripe | |||
v-if="tableData.length"> | |||
<el-table-column column-key="sn" prop="sn" :label="$t('serialNumber')" align="center" header-align="center" | |||
width="180"> | |||
</el-table-column> | |||
<el-table-column column-key="date" prop="date" :label="$t('time')" align="center" header-align="center" | |||
width="180"> | |||
</el-table-column> | |||
<el-table-column column-key="status" prop="status" :label="$t('status')" align="center" | |||
header-align="center" width="120"> | |||
<template slot-scope="scope"> | |||
<span :style="{ color: scope.row.statusColor }">{{ scope.row.status }}</span> | |||
</template> | |||
</el-table-column> | |||
<el-table-column column-key="sourceType" prop="sourceType" :label="$t('scene')" align="center" | |||
header-align="center" width="180"></el-table-column> | |||
<el-table-column column-key="duration" prop="duration" :label="$t('runTime')" align="center" | |||
header-align="center" width="120"></el-table-column> | |||
<el-table-column column-key="remark" prop="remark" :label="$t('explanation')" align="left" min-width="200" | |||
header-align="center"> | |||
<template slot-scope="scope"> | |||
<span v-html="scope.row.remark"></span> | |||
</template> | |||
</el-table-column> | |||
<el-table-column column-key="taskName" prop="taskName" :label="$t('taskName')" align="center" | |||
header-align="center" width="180"> | |||
<template slot-scope="scope"> | |||
<span v-html="scope.row.taskName"></span> | |||
</template> | |||
</el-table-column> | |||
<el-table-column column-key="amount" prop="amount" :label="$t('points')" align="center" | |||
header-align="center" width="120"></el-table-column> | |||
<template slot="empty"> | |||
<span>{{ loading ? $t('loading') : $t('noData') }}</span> | |||
</template> | |||
</el-table> | |||
<el-empty v-else :image-size="140" :description="$t('noPointConsumeRecord')"></el-empty> | |||
</div> | |||
<div class="__r_p_pagination" v-if="tableData.length"> | |||
<div style="margin-top: 2rem"> | |||
<div class="center"> | |||
<el-pagination background @current-change="currentChange" :current-page="pageInfo.curpage" | |||
:page-sizes="pageInfo.pageSizes" :page-size="pageInfo.pageSize" | |||
layout="total, sizes, prev, pager, next, jumper" :total="pageInfo.total"> | |||
</el-pagination> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import { getPoint, getPointAccount, getPointList } from "~/apis/modules/point"; | |||
import { getRewardPointRecordInfo } from './utils'; | |||
export default { | |||
data() { | |||
return { | |||
loading: false, | |||
summaryInfo: { | |||
available: 0, | |||
gain: 0, | |||
used: 0, | |||
}, | |||
tabIndex: 0, | |||
tableData: [], | |||
pageInfo: { | |||
curpage: 1, | |||
pageSize: 10, | |||
pageSizes: [10], | |||
total: 0, | |||
}, | |||
eventSource: null, | |||
}; | |||
}, | |||
components: {}, | |||
methods: { | |||
currentChange: function (val) { | |||
this.pageInfo.curpage = val; | |||
this.getTableData(); | |||
}, | |||
tabChange: function (index) { | |||
if (this.tabIndex === index) return; | |||
this.tabIndex = index; | |||
this.pageInfo.curpage = 1; | |||
this.pageInfo.total = 0; | |||
this.getTableData(); | |||
}, | |||
getSummaryInfo: function () { | |||
getPointAccount().then(res => { | |||
if (res.data && res.data.Code === 0) { | |||
const data = res.data.Data; | |||
this.summaryInfo.available = data.Balance; | |||
this.summaryInfo.gain = data.TotalEarned; | |||
this.summaryInfo.used = data.TotalConsumed; | |||
} | |||
}).catch(err => { | |||
console.log(err); | |||
}) | |||
}, | |||
getTableData: function () { | |||
this.loading = true; | |||
getPointList({ | |||
Operate: this.tabIndex === 0 ? 'INCREASE' : 'DECREASE', | |||
Page: this.pageInfo.curpage, | |||
// pageSize: this.pageInfo.pageSize, | |||
}).then((res) => { | |||
this.loading = false; | |||
const tableData = []; | |||
if (res.data && res.data.Code === 0) { | |||
const data = res.data.Data; | |||
const records = data.Records; | |||
for (let i = 0, iLen = records.length; i < iLen; i++) { | |||
const record = records[i]; | |||
tableData.push(getRewardPointRecordInfo(record)); | |||
} | |||
this.tableData.splice(0, Infinity, ...tableData); | |||
this.pageInfo.total = data.Total; | |||
} | |||
}) | |||
.catch((err) => { | |||
console.log(err); | |||
this.loading = false; | |||
this.tableData.splice(0, Infinity); | |||
}); | |||
}, | |||
}, | |||
mounted: function () { | |||
this.getSummaryInfo(); | |||
this.getTableData(); | |||
const { AppSubUrl, csrf, NotificationSettings } = window.config; | |||
if (NotificationSettings.EventSourceUpdateTime > 0 && !!window.EventSource) { | |||
const source = new EventSource(`${AppSubUrl}/user/events`); | |||
source.addEventListener('reward-operation', (e) => { | |||
try { | |||
this.getSummaryInfo(); | |||
this.getTableData(); | |||
} catch (err) { | |||
console.error(err); | |||
} | |||
}); | |||
this.eventSource = source; | |||
} | |||
}, | |||
beforeDestroy: function () { | |||
this.eventSource && this.eventSource.close(); | |||
}, | |||
}; | |||
</script> | |||
<style scoped lang="less"> | |||
.__flex-1 { | |||
flex: 1; | |||
} | |||
.__reward-pointer-c { | |||
.__r_p_header { | |||
height: 30px; | |||
margin: 10px 0; | |||
display: flex; | |||
align-items: center; | |||
justify-content: space-between; | |||
.__title { | |||
font-weight: 400; | |||
font-size: 18px; | |||
color: rgb(16, 16, 16); | |||
line-height: 26px; | |||
} | |||
} | |||
.__r_p_summary { | |||
display: flex; | |||
align-items: center; | |||
height: 100px; | |||
background-color: rgb(245, 245, 246); | |||
.__r_p_summary_item-c { | |||
.__val { | |||
text-align: center; | |||
margin: 12px 0; | |||
font-weight: 400; | |||
font-size: 28px; | |||
color: rgb(16, 16, 16); | |||
} | |||
.__exp { | |||
text-align: center; | |||
font-weight: 400; | |||
font-size: 14px; | |||
color: rgba(54, 56, 64, 1); | |||
} | |||
} | |||
} | |||
} | |||
.__r_p_summary_line { | |||
width: 1px; | |||
height: 80%; | |||
background-color: rgb(212, 212, 213); | |||
} | |||
.__r_p_tab { | |||
display: flex; | |||
margin: 18px 0; | |||
.__r_p_tab-item { | |||
width: 115px; | |||
height: 38px; | |||
display: flex; | |||
justify-content: center; | |||
align-items: center; | |||
border: 1px solid rgb(225, 227, 230); | |||
color: #101010; | |||
box-sizing: border-box; | |||
cursor: pointer; | |||
&.__focus { | |||
border-color: rgb(50, 145, 248); | |||
color: rgb(50, 145, 248); | |||
cursor: default; | |||
} | |||
} | |||
} | |||
.__r_p_table { | |||
/deep/ .el-table__header { | |||
th { | |||
background: rgb(245, 245, 246); | |||
color: rgb(96, 98, 102); | |||
font-weight: 400; | |||
font-size: 14px; | |||
} | |||
} | |||
} | |||
</style> |
@@ -0,0 +1,7 @@ | |||
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 ''; | |||
}; |
@@ -29,6 +29,11 @@ for (const path of stadalonePaths) { | |||
standalone[parse(path).name] = [path]; | |||
} | |||
const vuePages = {}; | |||
for (const path of glob('web_src/vuepages/**/vp-*.js')) { | |||
vuePages[parse(path).name] = [path]; | |||
} | |||
const isProduction = process.env.NODE_ENV !== 'development'; | |||
module.exports = { | |||
@@ -44,6 +49,7 @@ module.exports = { | |||
icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'), | |||
...standalone, | |||
...themes, | |||
...vuePages, | |||
}, | |||
devtool: false, | |||
output: { | |||
@@ -267,6 +273,7 @@ module.exports = { | |||
symlinks: false, | |||
alias: { | |||
vue$: 'vue/dist/vue.esm.js', // needed because vue's default export is the runtime only | |||
'~': resolve(__dirname, 'web_src/vuepages'), | |||
}, | |||
extensions: ['.tsx', '.ts', '.js'] | |||
}, | |||
@@ -29,6 +29,11 @@ for (const path of stadalonePaths) { | |||
standalone[parse(path).name] = [path]; | |||
} | |||
const vuePages = {}; | |||
for (const path of glob('web_src/vuepages/**/vp-*.js')) { | |||
vuePages[parse(path).name] = [path]; | |||
} | |||
const isProduction = process.env.NODE_ENV !== 'development'; | |||
module.exports = { | |||
@@ -44,6 +49,7 @@ module.exports = { | |||
icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'), | |||
...standalone, | |||
...themes, | |||
...vuePages | |||
}, | |||
devtool: false, | |||
output: { | |||
@@ -267,6 +273,7 @@ module.exports = { | |||
symlinks: false, | |||
alias: { | |||
vue$: 'vue/dist/vue.esm.js', // needed because vue's default export is the runtime only | |||
'~': resolve(__dirname, 'web_src/vuepages'), | |||
}, | |||
extensions: ['.tsx', '.ts', '.js'] | |||
}, | |||