@@ -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'] | |||
}, | |||
Dear OpenI User
Thank you for your continuous support to the Openl Qizhi Community AI Collaboration Platform. In order to protect your usage rights and ensure network security, we updated the Openl Qizhi Community AI Collaboration Platform Usage Agreement in January 2024. The updated agreement specifies that users are prohibited from using intranet penetration tools. After you click "Agree and continue", you can continue to use our services. Thank you for your cooperation and understanding.
For more agreement content, please refer to the《Openl Qizhi Community AI Collaboration Platform Usage Agreement》