@@ -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" | GrampusStatusWaiting = "WAITING" | ||||
) | ) | ||||
const ( | |||||
//cluster | |||||
OpenICluster = "OpenI" | |||||
C2NetCluster = "C2Net" | |||||
//AI center | |||||
AICenterOfCloudBrainOne = "OpenIOne" | |||||
AICenterOfCloudBrainTwo = "OpenITwo" | |||||
) | |||||
type Cloudbrain struct { | type Cloudbrain struct { | ||||
ID int64 `xorm:"pk autoincr"` | ID int64 `xorm:"pk autoincr"` | ||||
JobID string `xorm:"INDEX NOT NULL"` | JobID string `xorm:"INDEX NOT NULL"` | ||||
@@ -1338,6 +1348,34 @@ type GrampusSpec struct { | |||||
Name string `json:"name"` | Name string `json:"name"` | ||||
ProcessorType string `json:"processorType"` | ProcessorType string `json:"processorType"` | ||||
Centers []Center `json:"centers"` | 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 { | type GetGrampusResourceSpecsResult struct { | ||||
@@ -1345,6 +1383,12 @@ type GetGrampusResourceSpecsResult struct { | |||||
Infos []GrampusSpec `json:"resourceSpecs"` | Infos []GrampusSpec `json:"resourceSpecs"` | ||||
} | } | ||||
type GetGrampusAiCentersResult struct { | |||||
GrampusResult | |||||
Infos []GrampusAiCenter `json:"aiCenterInfos"` | |||||
TotalSize int `json:"totalSize"` | |||||
} | |||||
type GrampusImage struct { | type GrampusImage struct { | ||||
CreatedAt int64 `json:"createdAt"` | CreatedAt int64 `json:"createdAt"` | ||||
UpdatedAt int64 `json:"updatedAt"` | UpdatedAt int64 `json:"updatedAt"` | ||||
@@ -145,6 +145,11 @@ func init() { | |||||
new(OrgStatistic), | new(OrgStatistic), | ||||
new(SearchRecord), | new(SearchRecord), | ||||
new(AiModelConvert), | new(AiModelConvert), | ||||
new(ResourceQueue), | |||||
new(ResourceSpecification), | |||||
new(ResourceScene), | |||||
new(ResourceSceneSpec), | |||||
new(AdminOperateLog), | |||||
new(CloudbrainTemp), | new(CloudbrainTemp), | ||||
new(DatasetReference), | 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 | package cron | ||||
import ( | import ( | ||||
"code.gitea.io/gitea/services/cloudbrain/resource" | |||||
"code.gitea.io/gitea/modules/modelarts" | "code.gitea.io/gitea/modules/modelarts" | ||||
"context" | "context" | ||||
"time" | "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() { | func registerSyncModelArtsTempJobs() { | ||||
RegisterTaskFatal("sync_model_arts_temp_jobs", &BaseConfig{ | RegisterTaskFatal("sync_model_arts_temp_jobs", &BaseConfig{ | ||||
Enabled: true, | Enabled: true, | ||||
@@ -239,5 +251,6 @@ func initBasicTasks() { | |||||
registerSyncCloudbrainStatus() | registerSyncCloudbrainStatus() | ||||
registerHandleOrgStatistic() | registerHandleOrgStatistic() | ||||
registerSyncResourceSpecs() | |||||
registerSyncModelArtsTempJobs() | registerSyncModelArtsTempJobs() | ||||
} | } |
@@ -23,6 +23,7 @@ const ( | |||||
urlGetToken = urlOpenApiV1 + "token" | urlGetToken = urlOpenApiV1 + "token" | ||||
urlTrainJob = urlOpenApiV1 + "trainjob" | urlTrainJob = urlOpenApiV1 + "trainjob" | ||||
urlGetResourceSpecs = urlOpenApiV1 + "resourcespec" | urlGetResourceSpecs = urlOpenApiV1 + "resourcespec" | ||||
urlGetAiCenter = urlOpenApiV1 + "sharescreen/aicenter" | |||||
urlGetImages = urlOpenApiV1 + "image" | urlGetImages = urlOpenApiV1 + "image" | ||||
errorIllegalToken = 1005 | errorIllegalToken = 1005 | ||||
@@ -275,3 +276,35 @@ sendjob: | |||||
return &result, nil | 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.op = Op. | ||||
notices.delete_success = The system notices have been deleted. | 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] | [action] | ||||
create_repo = created repository <a href="%s">%s</a> | 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> | 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.op=操作 | ||||
notices.delete_success=系统通知已被删除。 | notices.delete_success=系统通知已被删除。 | ||||
user_management = 用户管理 | |||||
resource_management = 资源管理 | |||||
resource_pool = 资源池(队列) | |||||
resource_price = 资源规格单价 | |||||
application_scenario = 应用场景 | |||||
system_configuration = 系统配置 | |||||
[action] | [action] | ||||
create_repo=创建了项目 <a href="%s">%s</a> | create_repo=创建了项目 <a href="%s">%s</a> | ||||
rename_repo=重命名项目 <code>%[1]s</code> 为 <a href="%[2]s">%[3]s</a> | rename_repo=重命名项目 <code>%[1]s</code> 为 <a href="%[2]s">%[3]s</a> | ||||
@@ -54,6 +54,7 @@ | |||||
"vue": "2.6.11", | "vue": "2.6.11", | ||||
"vue-bar-graph": "1.2.0", | "vue-bar-graph": "1.2.0", | ||||
"vue-calendar-heatmap": "0.8.4", | "vue-calendar-heatmap": "0.8.4", | ||||
"vue-i18n": "6.1.3", | |||||
"vue-loader": "15.9.2", | "vue-loader": "15.9.2", | ||||
"vue-router": "3.3.4", | "vue-router": "3.3.4", | ||||
"vue-template-compiler": "2.6.11", | "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} | 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 { | 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 { | func ErrorWithData(code int, msg string, data interface{}) *AiforgeResponse { | ||||
return &AiforgeResponse{Code: code, Msg: msg, Data: data} | 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("/delete", admin.DeleteNotices) | ||||
m.Post("/empty", admin.EmptyNotices) | 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) | }, adminReq) | ||||
// ***** END: Admin ***** | // ***** 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="alert"></div> | ||||
<div class="admin user"> | <div class="admin user"> | ||||
{{template "admin/navbar" .}} | {{template "admin/navbar" .}} | ||||
<div id="images-admin"> | |||||
</div> | |||||
<div class="ui container"> | |||||
<div id="images-admin"></div> | |||||
</div> | |||||
</div> | </div> | ||||
<!-- 确认模态框 --> | <!-- 确认模态框 --> | ||||
<div> | <div> | ||||
@@ -22,19 +22,19 @@ | |||||
data-all-compute="{{.i18n.Tr "admin.cloudbrain.all_computing_resources"}}" | data-all-compute="{{.i18n.Tr "admin.cloudbrain.all_computing_resources"}}" | ||||
data-all-status="{{.i18n.Tr "admin.cloudbrain.all_status"}}"></div> | data-all-status="{{.i18n.Tr "admin.cloudbrain.all_status"}}"></div> | ||||
{{template "admin/navbar" .}} | {{template "admin/navbar" .}} | ||||
<div class="ui container" style="width: 95%;"> | |||||
<div class="ui container"> | |||||
{{template "base/alert" .}} | {{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" .}} | {{template "admin/cloudbrain/search" .}} | ||||
<div class="ui six wide column right aligned" style="margin: 1rem 0;"> | <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;" | <a class="ui compact blue basic icon button" style="box-shadow: none !important; padding: 0.8em;" | ||||
href="/admin/cloudbrains/download"><i | href="/admin/cloudbrains/download"><i | ||||
class="ri-download-line middle aligned icon"></i>{{.i18n.Tr "admin.cloudbrain.download_report"}}</a> | class="ri-download-line middle aligned icon"></i>{{.i18n.Tr "admin.cloudbrain.download_report"}}</a> | ||||
</div> | </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="ui grid stackable" style="background: #f0f0f0;;"> | ||||
<div class="row"> | <div class="row"> | ||||
@@ -412,17 +412,16 @@ | |||||
</div> | </div> | ||||
{{end}} | {{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> | ||||
</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> | </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> | <template> | ||||
<div> | <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 attached segment"> | ||||
<div class="ui form ignore-dirty"> | <div class="ui form ignore-dirty"> | ||||
<div class="ui fluid action input"> | <div class="ui fluid action input"> | ||||
@@ -31,8 +30,8 @@ | |||||
<div class="ui six wide column right aligned" style="margin: 1rem 0;"> | <div class="ui six wide column right aligned" style="margin: 1rem 0;"> | ||||
<a class="ui blue small button" href="/admin/images/commit_image">创建云脑镜像</a> | <a class="ui blue small button" href="/admin/images/commit_image">创建云脑镜像</a> | ||||
</div> | </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"> | <el-table-column label="镜像Tag" min-width="19%" align="left" prop="tag"> | ||||
<template slot-scope="scope"> | <template slot-scope="scope"> | ||||
<div style="display: flex;align-items: center;"> | <div style="display: flex;align-items: center;"> | ||||
@@ -1,5 +1,5 @@ | |||||
.admin { | .admin { | ||||
padding-top: 15px; | |||||
padding-top: 15px !important; | |||||
.table.segment { | .table.segment { | ||||
padding: 0; | padding: 0; | ||||
@@ -75,4 +75,58 @@ | |||||
white-space: pre-wrap; | white-space: pre-wrap; | ||||
word-wrap: break-word; | 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]; | 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'; | const isProduction = process.env.NODE_ENV !== 'development'; | ||||
module.exports = { | module.exports = { | ||||
@@ -44,6 +49,7 @@ module.exports = { | |||||
icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'), | icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'), | ||||
...standalone, | ...standalone, | ||||
...themes, | ...themes, | ||||
...vuePages, | |||||
}, | }, | ||||
devtool: false, | devtool: false, | ||||
output: { | output: { | ||||
@@ -267,6 +273,7 @@ module.exports = { | |||||
symlinks: false, | symlinks: false, | ||||
alias: { | alias: { | ||||
vue$: 'vue/dist/vue.esm.js', // needed because vue's default export is the runtime only | 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'] | extensions: ['.tsx', '.ts', '.js'] | ||||
}, | }, | ||||
@@ -29,6 +29,11 @@ for (const path of stadalonePaths) { | |||||
standalone[parse(path).name] = [path]; | 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'; | const isProduction = process.env.NODE_ENV !== 'development'; | ||||
module.exports = { | module.exports = { | ||||
@@ -44,6 +49,7 @@ module.exports = { | |||||
icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'), | icons: glob('node_modules/@primer/octicons/build/svg/**/*.svg'), | ||||
...standalone, | ...standalone, | ||||
...themes, | ...themes, | ||||
...vuePages | |||||
}, | }, | ||||
devtool: false, | devtool: false, | ||||
output: { | output: { | ||||
@@ -267,6 +273,7 @@ module.exports = { | |||||
symlinks: false, | symlinks: false, | ||||
alias: { | alias: { | ||||
vue$: 'vue/dist/vue.esm.js', // needed because vue's default export is the runtime only | 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'] | extensions: ['.tsx', '.ts', '.js'] | ||||
}, | }, | ||||