44 Commits

Author SHA1 Message Date
  ychao e22d0d82ce Merge pull request 'yd_pay_computility' (#7166) from yd_pay_computility into V20251211.patch 13 hours ago
  wutianhao b919f27781 Merge remote-tracking branch 'origin/V20251229' into yd_pay_computility 1 day ago
  zhoupzh 03eb15f77f Merge pull request 'V20251211.patch' (#7161) from V20251211.patch into V20251229 1 day ago
  wutianhao 4f35d7c074 fix 中心筛选相关 2 days ago
  wutianhao 65b6b52eb6 fix 中心筛选相关 2 days ago
  wutianhao 5fcbed9f10 fix 中心筛选相关 2 days ago
  wutianhao 7da13a1fec add 使用角色来查询对应的资源 2 days ago
  wutianhao c52a2953bc Merge remote-tracking branch 'origin/V20251229' into yd_pay_computility 3 days ago
  wutianhao 7ce2d62239 add 中国移动的算力 4 days ago
  wutianhao 5ac43c0cb9 add 中国移动的算力 4 days ago
  wutianhao f10404c933 add 中国移动的算力 4 days ago
  wutianhao fc8519175e add 中国移动的算力 1 week ago
  wutianhao 32ae47eb44 fix 即便报错也直接返回为空 1 week ago
  wutianhao 03fcf1b226 Merge remote-tracking branch 'origin/V20251211' into pc_pay_computility 1 week ago
  chenshihai f44231146b update pagesize 2 weeks ago
  chenshihai dc9e8ceb55 update login back url 2 weeks ago
  wutianhao e57463cceb Merge remote-tracking branch 'origin/V20251113' into pc_pay_computility 4 weeks ago
  wutianhao 6b3beed890 add 若昊算数据为空,直接初始化一个新的来处理 4 weeks ago
  wutianhao dc178ec2bd add 若昊算数据为空,直接初始化一个新的来处理 4 weeks ago
  chenshihai b0cfb58187 #6930 4 weeks ago
  chenshihai 695e5563c8 Merge branch 'pc_pay_computility' of https://openi.pcl.ac.cn/OpenI/aiforge into pc_pay_computility 1 month ago
  chenshihai 7cc49cfe77 #6930 1 month ago
  wutianhao cee6648f84 add 合作伙伴的排序问题 1 month ago
  wutianhao 6b58de36e7 add 合作伙伴的排序问题 1 month ago
  wutianhao af37926ec3 add 合作伙伴的排序问题 1 month ago
  wutianhao 2e8cc06564 fix 修复名字 1 month ago
  wutianhao afb22d6730 add 算力合作伙伴变成动态读取配置 1 month ago
  wutianhao 968814b027 加点日志看问题 1 month ago
  wutianhao 1033a59aec add 增加算力伙伴的资源,同时通过配置获取的情况 1 month ago
  wutianhao f3713e6c27 fix 修复并处理资源的list 1 month ago
  wutianhao 9070fe4538 fix 修复并处理资源的list 1 month ago
  wutianhao bb8cb661f2 fix 修复并处理资源的list 1 month ago
  wutianhao d5b3028120 fix 修复并优化sql 1 month ago
  wutianhao 7c3551e600 add sql过滤 1 month ago
  wutianhao e7fb5afc03 add 普惠算力查询页面改成权限相关 1 month ago
  wutianhao 44f317ecf3 add 普惠算力查询页面改成权限相关 1 month ago
  wutianhao da17a7da76 add 普惠算力查询页面改成权限相关 1 month ago
  chenshihai 316b82509d update compute resource page 1 month ago
  wutianhao 27174fa2ec add 补充算力合作伙伴标签 1 month ago
  wutianhao 8ebfc65de3 add 补充算力合作伙伴标签 1 month ago
  wutianhao 1435f86657 add 补充算力合作伙伴标签 1 month ago
  wutianhao 0ee5586813 add 补充算力合作伙伴标签 1 month ago
  wutianhao f657f62511 add 补充标签 1 month ago
  wutianhao abdc867cd1 add 调整算力的获取 1 month ago
24 changed files with 1659 additions and 329 deletions
Split View
  1. +169
    -0
      manager/client/paratera/paratera_api.go
  2. +43
    -0
      models/resource_queue.go
  3. +205
    -0
      models/resource_specification.go
  4. +34
    -0
      models/role.go
  5. +27
    -1
      modules/setting/setting.go
  6. +102
    -0
      modules/util/byte_converter.go
  7. +31
    -0
      modules/util/util.go
  8. BIN
      public/img/computingpower/title-decoration.png
  9. +4
    -0
      routers/api/v1/api.go
  10. +94
    -0
      routers/api/v1/pay_computility/company.go
  11. +8
    -34
      routers/api/v1/pay_computility/computility_config.go
  12. +80
    -0
      routers/api/v1/pay_computility/computility_partners.go
  13. +250
    -0
      routers/api/v1/pay_computility/computility_strategy.go
  14. +70
    -0
      routers/api/v1/pay_computility/dynamic_config.go
  15. +28
    -0
      routers/api/v1/pay_computility/transform.go
  16. +1
    -1
      routers/card_request/card_request.go
  17. +15
    -2
      routers/resources/acc_card.go
  18. +38
    -0
      services/cloudbrain/resource/resource_specification.go
  19. +9
    -0
      web_src/vuepages/apis/modules/computingpower.js
  20. +81
    -28
      web_src/vuepages/pages/computingpower/components/ExtraResources.vue
  21. +126
    -0
      web_src/vuepages/pages/computingpower/components/JumpButton.vue
  22. +203
    -0
      web_src/vuepages/pages/computingpower/components/Partner.vue
  23. +25
    -29
      web_src/vuepages/pages/computingpower/components/Resources.vue
  24. +16
    -234
      web_src/vuepages/pages/computingpower/demand/index.vue

+ 169
- 0
manager/client/paratera/paratera_api.go View File

@@ -0,0 +1,169 @@
package paratera

import (
"code.gitea.io/gitea/modules/log"
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/go-resty/resty/v2"
"strconv"
"strings"
"time"
)

/*
并行科技对接api
参考文档:https://ai.paratera.com/document/openapi/computer/production/DescribeInstanceTypes
*/

type ParateraAPI struct {
ak string
sk string
//log *log.Helper
client *resty.Client
baseUrl string
}

type BaseRsp struct {
Code int `json:"code"`
Message string `json:"message"`
}

type StopInstancesReq struct {
ZoneCode string `json:"zoneCode"`
EcsUuids []string `json:"ecsUuids"`
StopType string `json:"stopType"`
StoppedMode string `json:"stoppedMode"`
ReleaseEip bool `json:"releaseEip"`
}

type DescribeInstanceTypesRsp struct {
BaseRsp
Data []*struct {
VhostCpus int64 `json:"vhostCpus"`
VhostType string `json:"vhostType"`
VhostModel string `json:"vhostModel"`
VhostMemory int64 `json:"vhostMemory"`
VhostGpus float64 `json:"vhostGpus"`
VhostGpumem int64 `json:"vhostGpumem"`
RootDiskTypes []string `json:"rootDiskTypes"`
Zone *struct {
ZoneCode string `json:"zoneCode"`
} `json:"zone"`
} `json:"data"`
}

func NewParateraAPI() (*ParateraAPI, error) {
// 初始化基础的配置
return &ParateraAPI{
client: resty.New(),
ak: "",
sk: "",
baseUrl: "https://ai.blsc.cn",
}, nil
}

type Request struct {
HttpMethod string
QueryString string
Body interface{}
Service string
Action string
Url string
}

func (p *ParateraAPI) DoRequest(ctx context.Context, r *Request, response interface{}) error {
var err error
canonicalHeaders := "content-type:application/json; charset=utf-8\nhost:ai.blsc.cn"
signedHeaders := "content-type;host"

var body []byte
b, ok := r.Body.(string)
if ok {
body = []byte(b)
} else {
body, err = json.Marshal(r.Body)
if err != nil {
return err
}

}
body = []byte(strings.ReplaceAll(string(body), "\n", ""))
sum256rp := sha256.Sum256(body)
hashedRequestPayload := hex.EncodeToString(sum256rp[:])
canonicalRequest := fmt.Sprintf("%v\n%v\n%v\n%v\n%v\n%v", r.HttpMethod, r.Url, r.QueryString, canonicalHeaders, signedHeaders, hashedRequestPayload)
sum256cr := sha256.Sum256([]byte(canonicalRequest))
hashedCanonicalRequest := hex.EncodeToString(sum256cr[:])

stringToSign := fmt.Sprintf("HmacSHA256\nV3\n%v\n%v\nparatera/aicloud/%v\n%v", p.ak, r.Service, r.Service, hashedCanonicalRequest)
mac := hmac.New(sha256.New, []byte("BC_SIGNATURE&"+p.sk))
mac.Write([]byte(stringToSign))
signature := hex.EncodeToString(mac.Sum(nil))

headers := map[string]string{
"X-AIC-Version": "V3",
"X-AIC-Action": r.Action,
"X-AIC-Timestamp": strconv.Itoa(int(time.Now().Unix())),
"X-AIC-AccessKey": p.ak,
"X-AIC-SignedHeaders": "content-type;host",
"X-AIC-Signature": signature,
"X-AIC-Service": r.Service,
"Content-Type": "application/json; charset=utf-8",
"HOST": "ai.blsc.cn",
}
url := fmt.Sprintf("%v%v", p.baseUrl, r.Url)
req := p.client.R().SetHeaders(headers).SetBody(r.Body)
var rsp *resty.Response
switch strings.ToUpper(r.HttpMethod) {
case "POST":
rsp, err = req.Post(url)
case "GET":
rsp, err = req.Get(url)
}
if err != nil {
log.Error("response error:%v", err)
return err
}

if rsp.StatusCode() != 200 {
br := &BaseRsp{}
if err := json.Unmarshal([]byte(rsp.String()), br); err == nil {
if br.Code != 0 {
return err
}
}

return err
}

if err := json.Unmarshal([]byte(rsp.String()), response); err != nil {
return err
}

return nil
}

func (p *ParateraAPI) DescribeInstanceTypes(ctx context.Context) (*DescribeInstanceTypesRsp, error) {
rsp := &DescribeInstanceTypesRsp{}

err := p.DoRequest(ctx, &Request{
HttpMethod: "POST",
QueryString: "",
Body: "{}",
Service: "product",
Action: "DescribeInstanceTypes",
Url: "/platform/v3/product/DescribeInstanceTypes",
}, rsp)
if err != nil {
return nil, err
}

if rsp.Data == nil || len(rsp.Data) == 0 {
return nil, err
}

return rsp, nil
}

+ 43
- 0
models/resource_queue.go View File

@@ -2,6 +2,7 @@ package models

import (
"errors"
"fmt"
"strconv"
"strings"

@@ -514,7 +515,49 @@ func GetAvailableResourceAiCenters() ([]*ResourceAiCenterRes, error) {
}
return r, nil
}
func GetAvailableResourceAiCentersV2(specIds []int64) ([]*ResourceAiCenterRes, error) {
if len(specIds) == 0 {
// 如果没有 specIds,返回空结果或所有结果?
// 根据业务需求决定,这里返回空
return []*ResourceAiCenterRes{}, nil
}

r := make([]*ResourceAiCenterRes, 0)

// 构建 SQL 查询
query := `
SELECT DISTINCT
rq.ai_center_code,
rq.ai_center_name,
rq.cluster
FROM resource_queue rq
INNER JOIN resource_specification rs ON rq.id = rs.queue_id
WHERE rs.id IN (%s)
ORDER BY rq.cluster DESC, rq.ai_center_code ASC
`

// 构建 IN 条件的占位符
placeholders := make([]string, len(specIds))
args := make([]interface{}, len(specIds))
for i, id := range specIds {
placeholders[i] = "?"
args[i] = id
}

sql := fmt.Sprintf(query, strings.Join(placeholders, ","))

// 执行查询
err := x.SQL(sql, args...).Find(&r)
if err != nil {

return nil, err
}

fmt.Println(fmt.Sprintf("lensp :%d", len(specIds)))
fmt.Println(fmt.Sprintf("SQL :%v args[%v]", sql, args))

return r, nil
}
func GetExclusiveQueueIds(opts FindSpecsOptions) []*ResourceExclusivePool {
pools, err := FindExclusivePools()
if err != nil {


+ 205
- 0
models/resource_specification.go View File

@@ -1282,6 +1282,178 @@ func GetResourceListPaging(opts GetResourceListOpts) ([]*ResourceInfo4CardReques
return resourceInfos, total, nil
}

func GetAllSpecIds() ([]int64, error) {
roleList, err := ListCommonAiforgeRole()
if err != nil {
return []int64{}, err
}

specIds := GetSpecIdsByList(roleList)

return specIds, nil
}

func GetResourceListPagingV2(opts GetResourceListOpts, specIds []int64) ([]*ResourceInfo4CardRequest, int64, error) {
// step 1 构建查询的基本条件
var (
cond = builder.NewCond()
resourceList = make([]string, 0)
)

for i := 0; i < len(opts.Resource); i++ {
if opts.Resource[i] != "" {
resourceList = append(resourceList, opts.Resource[i])
}
}
if len(resourceList) > 0 {
cond = cond.And(builder.In("resource_queue.compute_resource", resourceList))
}
if opts.AccCardType != "" {
cond = cond.And(builder.Eq{"resource_queue.acc_card_type": opts.AccCardType})
}
if opts.AccCardNum >= 0 {
if opts.AccCardNum > 999 {
cond = cond.And(builder.NotIn("resource_specification.acc_cards_num", opts.ExcludeAccCardNums))
} else {
cond = cond.And(builder.Eq{"resource_specification.acc_cards_num": opts.AccCardNum})
}
}
if opts.AICenterCode != "" {
cond = cond.And(builder.Eq{"resource_queue.ai_center_code": opts.AICenterCode})
}
if opts.MaxPrice >= 0 && opts.MinPrice >= 0 && opts.MaxPrice < opts.MinPrice {
opts.MaxPrice = -1
opts.MinPrice = -1
}
if opts.MaxPrice >= 0 {
cond = cond.And(builder.Lte{"resource_specification.unit_price": opts.MaxPrice})
}
if opts.MinPrice >= 0 {
cond = cond.And(builder.Gte{"resource_specification.unit_price": opts.MinPrice})
}

if len(specIds) > 0 {
cond = cond.And(builder.In("resource_specification.id", specIds))
}

// 删除时间
cond = cond.And(builder.Or(builder.Eq{"resource_queue.deleted_time": 0}, builder.IsNull{"resource_queue.deleted_time"}))
cond = cond.And(builder.Eq{"resource_specification.status": 2})

//先按多字段去重分页查询资源规格
//再基于结果查询智算中心信息
resourceInfos := make([]*ResourceInfo4CardRequest, 0)
err := x.Table("resource_specification").
Join("INNER", "resource_queue", "resource_specification.queue_id = resource_queue.id").
Select(`DISTINCT
resource_queue.compute_resource,
resource_queue.acc_card_type,
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`).
Where(cond).
OrderBy(`resource_queue.compute_resource,
resource_queue.acc_card_type,
resource_specification.acc_cards_num DESC,
resource_specification.gpu_mem_gi_b DESC,
resource_specification.cpu_cores DESC,
resource_specification.mem_gi_b DESC`).
Find(&resourceInfos)

if err != nil {
return nil, 0, err
}
tmpResourceInfos := make([]*ResourceInfo4CardRequest, 0)

// step 3 过滤专属池
for i := 0; i < len(resourceInfos); i++ {
//此处是为了过滤那些专属池中的规格又被配置到共享场景中的情况
if !resourceInfos[i].IsExclusive || (resourceInfos[i].IsExclusive && resourceInfos[i].IsSpecExclusive == "") {
tmpResourceInfos = append(tmpResourceInfos, resourceInfos[i])
}
}
resourceInfos = tmpResourceInfos
if len(resourceInfos) == 0 {
return []*ResourceInfo4CardRequest{}, 0, nil
}

total := int64(len(resourceInfos))
startIndex := int64((opts.Page - 1) * opts.PageSize)
endIndex := int64(opts.Page * opts.PageSize)
if startIndex >= total {
return []*ResourceInfo4CardRequest{}, 0, nil
}
if endIndex > total {
endIndex = total
}
resourceInfos = resourceInfos[startIndex:endIndex]

// 为了找到中心的资源?
newCond := builder.NewCond()

newCond = newCond.And(builder.Or(builder.Eq{"resource_queue.deleted_time": 0}, builder.IsNull{"resource_queue.deleted_time"}))
newCond = newCond.And(builder.Eq{"resource_specification.status": 2})
newCond = newCond.And(builder.In("resource_specification.id", specIds))

withCenterInfos := make([]ResourceWithAICenter4CardRequest, 0)
err = x.Table("resource_specification").
Join("INNER", "resource_queue", "resource_specification.queue_id = resource_queue.id").
Select(`resource_queue.cluster,
resource_queue.ai_center_code,
resource_queue.ai_center_name,
resource_queue.compute_resource,
resource_queue.acc_card_type,
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`).
Where(newCond).
Find(&withCenterInfos)

if err != nil {
return nil, 0, err
}
tmpMap := make(map[string][]*ResourceAiCenterRes, 0)
for i := 0; i < len(withCenterInfos); i++ {
t := withCenterInfos[i]
key := fmt.Sprintf("%s_%s_%d_%d_%f_%f_%f_%f_%t_%s", t.ComputeResource, t.AccCardType, t.AccCardsNum,
t.CpuCores, t.MemGiB, t.GPUMemGiB, t.ShareMemGiB, t.UnitPrice, t.IsExclusive, t.IsSpecExclusive)
if _, exists := tmpMap[key]; exists {
centerExists := false
for _, center := range tmpMap[key] {
if center.AiCenterCode == t.AICenterCode {
centerExists = true
}
}
if centerExists {
continue
}
tmpMap[key] = append(tmpMap[key], &ResourceAiCenterRes{
AiCenterCode: t.AICenterCode,
AiCenterName: t.AICenterName,
})
} else {
tmpMap[key] = []*ResourceAiCenterRes{{
AiCenterCode: t.AICenterCode,
AiCenterName: t.AICenterName,
}}
}
}

for i := 0; i < len(resourceInfos); i++ {
t := resourceInfos[i]
key := fmt.Sprintf("%s_%s_%d_%d_%f_%f_%f_%f_%t_%s", t.ComputeResource, t.AccCardType, t.AccCardsNum,
t.CpuCores, t.MemGiB, t.GPUMemGiB, t.ShareMemGiB, t.UnitPrice, t.IsExclusive, t.IsSpecExclusive)
resourceInfos[i].AICenterList = tmpMap[key]
}
return resourceInfos, total, nil
}

type AccCardInfo struct {
ComputeSource string
CardList []string
@@ -1334,6 +1506,39 @@ func GetAccCardList() ([]AccCardInfo, error) {
return res, nil
}

func GetAccCardListV2(specIds []int64) ([]AccCardInfo, error) {
res := make([]AccCardInfo, 0)
r := make([]*Specification, 0)

err := x.Where("resource_specification.status = ? and (resource_queue.deleted_time = 0 or resource_queue.deleted_time is null)", SpecOnShelf).
Join("INNER", "resource_queue", "resource_queue.id = resource_specification.queue_id").
In("resource_specification.id", specIds).
OrderBy("resource_queue.compute_resource asc,resource_queue.acc_card_type asc").
Unscoped().Distinct("resource_queue.compute_resource,resource_queue.acc_card_type").Find(&r)
if err != nil {
return nil, err
}
tmpMap := make(map[string][]string, 0)
keys := make([]string, 0)
for i := 0; i < len(r); i++ {
spec := r[i]
if _, exists := tmpMap[spec.ComputeResource]; exists {
tmpMap[spec.ComputeResource] = append(tmpMap[spec.ComputeResource], spec.AccCardType)
} else {
keys = append(keys, spec.ComputeResource)
tmpMap[spec.ComputeResource] = []string{spec.AccCardType}
}
}
for i := 0; i < len(keys); i++ {
res = append(res, AccCardInfo{
ComputeSource: keys[i],
CardList: tmpMap[keys[i]],
})
}

return res, nil
}

// FindSpecs
func FindAllSpecs() ([]*Specification, error) {
return nil, nil


+ 34
- 0
models/role.go View File

@@ -169,6 +169,7 @@ func ListNonCommonAiforgeRoleByNameAndType(name string, roleType int) ([]*Aiforg
return r, nil
}

// ListCommonAiforgeRole 查询默认角色列表
func ListCommonAiforgeRole() ([]*AiforgeRole, error) {
r := make([]*AiforgeRole, 0)

@@ -302,6 +303,7 @@ func queryAvailResource(opts FindAvailSpecsOptions) ([]*Specification, error) {
}
}

// setAvailSpecIdToMap 将RightInfo转换成map返回,同时key是对应的specId
func setAvailSpecIdToMap(tmp *AiforgeRole, jobType string, specIdMap map[int64]int64) {
rightInfo := tmp.RightInfo
tmpsRights := make([]*RightInfo, 0)
@@ -325,6 +327,38 @@ func setAvailSpecIdToMap(tmp *AiforgeRole, jobType string, specIdMap map[int64]i
}
}

// GetSpecIdsByList 将RightInfo组装成多个返回specIds
func GetSpecIdsByList(roles []*AiforgeRole) []int64 {
var (
specIdMap = make(map[int64]int64)
specIds = make([]int64, 0, 0)
tmpsRights []*RightInfo
)

for _, role := range roles {
if role.RightInfo != "" {
jsonerr := json.Unmarshal([]byte(role.RightInfo), &tmpsRights)
if jsonerr != nil {
log.Info("TransSpecIdToMap json error=" + jsonerr.Error())
continue
}

for _, tmpRight := range tmpsRights {
specId, _ := strconv.ParseInt(tmpRight.SpecId, 10, 64)
if specId > 0 {
specIdMap[specId] = 0
}
}
}
}

for specId := range specIdMap {
specIds = append(specIds, specId)
}

return specIds
}

func QueryAiforgeUserRoleByUserId(userid int64) ([]*AiforgeUserRole, error) {
r := make([]*AiforgeUserRole, 0)
err := x.Where("user_id = ?", userid).Find(&r)


+ 27
- 1
modules/setting/setting.go View File

@@ -511,10 +511,26 @@ var (
DyncConfigBranch string
DyncConfigDefaultTTL time.Duration

// 联通付费算力
PayConputilityConfigRepoOwner string
PayConputilityConfigRepoName string
PayConputilitycConfigBranch string

// 并行科技付费算力
PeraPayConputilityConfigRepoOwner string
PeraPayConputilityConfigRepoName string
PeraPayConputilitycConfigBranch string

// 中国移动云科技付费算力
ChinaMobilePayConputilityConfigRepoOwner string
ChinaMobilePayConputilityConfigRepoName string
ChinaMobilePayConputilitycConfigBranch string

// 算力合作伙伴
ConputilityPartnerConfigRepoOwner string
ConputilityPartnerConfigRepoName string
ConputilityPartnerConfigBranch string

//labelsystem config
LabelTaskName string
LabelDatasetDeleteQueue string
@@ -1699,10 +1715,20 @@ func NewContext() {
DyncConfigRepoName = sec.Key("REPO").MustString("promote")
DyncConfigBranch = sec.Key("BRANCH").MustString("master")
DyncConfigDefaultTTL = sec.Key("DEFAULT_TTL").MustDuration(2 * time.Minute)

PayConputilityConfigRepoOwner = sec.Key("ComputilityConfigOWNER").MustString("OpenIOSSG")
PayConputilityConfigRepoName = sec.Key("ComputilityConfigREPO").MustString("HaoSuanResource")
PayConputilitycConfigBranch = sec.Key("ComputilityConfigBRANCH").MustString("master")
PeraPayConputilityConfigRepoOwner = sec.Key("PERA_PAY_CONPUTILITY_CONFIG_OWNER").MustString("OpenIOSSG")
PeraPayConputilityConfigRepoName = sec.Key("PERA_PAY_CONPUTILITY_CONFIG_REPO").MustString("ParateraResource")
PeraPayConputilitycConfigBranch = sec.Key("PERA_PAY_CONPUTILITY_CONFIG_BRANCH").MustString("master")

ChinaMobilePayConputilityConfigRepoOwner = sec.Key("CHINA_MOBILE_PAY_CONPUTILITY_CONFIG_OWNER").MustString("OpenIOSSG")
ChinaMobilePayConputilityConfigRepoName = sec.Key("CHINA_MOBILE_PAY_CONPUTILITY_CONFIG_REPO").MustString("ecloudResource")
ChinaMobilePayConputilitycConfigBranch = sec.Key("CHINA_MOBILE_PAY_CONPUTILITY_CONFIG_BRANCH").MustString("master")

ConputilityPartnerConfigRepoOwner = sec.Key("CONPUTILITY_PARTNER_CONFIG_OWNER").MustString("OpenIOSSG")
ConputilityPartnerConfigRepoName = sec.Key("CONPUTILITY_PARTNER_CONFIG_REPO").MustString("promote")
ConputilityPartnerConfigBranch = sec.Key("CONPUTILITY_PARTNER_CONFIG_BRANCH").MustString("master")

sec = Cfg.Section("cloudbrain")
CBAuthUser = sec.Key("USER").MustString("")


+ 102
- 0
modules/util/byte_converter.go View File

@@ -0,0 +1,102 @@
package util

import (
"fmt"
)

// ByteConverter 字节转换器结构体
type ByteConverter struct{}

// ConvertBytes 将字节转换为MB或GB,自动选择合适的单位
func (bc *ByteConverter) ConvertBytes(bytes uint64) (float64, string) {
const (
KB = 1024
MB = KB * 1024
GB = MB * 1024
TB = GB * 1024
)

switch {
case bytes >= TB:
return float64(bytes) / float64(TB), "TB"
case bytes >= GB:
return float64(bytes) / float64(GB), "GB"
case bytes >= MB:
return float64(bytes) / float64(MB), "MB"
case bytes >= KB:
return float64(bytes) / float64(KB), "KB"
default:
return float64(bytes), "B"
}
}

// ConvertToMB 将字节转换为MB
func (bc *ByteConverter) ConvertToMB(bytes uint64) float64 {
const MB = 1024 * 1024
return float64(bytes) / float64(MB)
}

// ConvertToGB 将字节转换为GB
func (bc *ByteConverter) ConvertToGB(bytes uint64) float64 {
const GB = 1024 * 1024 * 1024
return float64(bytes) / float64(GB)
}

// FormatBytes 格式化字节显示,保留指定位数小数
func (bc *ByteConverter) FormatBytes(bytes uint64, precision int) string {
value, unit := bc.ConvertBytes(bytes)
return fmt.Sprintf("%.*f %s", precision, value, unit)
}

// 简单的函数版本(不需要结构体)

// BytesToMB 将字节转换为MB
func BytesToMB(bytes uint64) float64 {
return float64(bytes) / (1024 * 1024)
}

// BytesToGB 将字节转换为GB
func BytesToGB(bytes uint64) float64 {
return float64(bytes) / (1024 * 1024 * 1024)
}

// FormatBytesAuto 自动选择合适的单位格式化字节
func FormatBytesAuto(bytes uint64) string {
const (
KB = 1024
MB = KB * 1024
GB = MB * 1024
TB = GB * 1024
)

var value float64
var unit string

switch {
case bytes >= TB:
value = float64(bytes) / float64(TB)
unit = "TB"
case bytes >= GB:
value = float64(bytes) / float64(GB)
unit = "GB"
case bytes >= MB:
value = float64(bytes) / float64(MB)
unit = "MB"
case bytes >= KB:
value = float64(bytes) / float64(KB)
unit = "KB"
default:
value = float64(bytes)
unit = "B"
}

// 根据数值大小决定小数位数
precision := 2
if value < 10 {
precision = 3
} else if value >= 100 {
precision = 1
}

return fmt.Sprintf("%.*f %s", precision, value, unit)
}

+ 31
- 0
modules/util/util.go View File

@@ -6,6 +6,7 @@ package util

import (
"bytes"
"encoding/json"
"math/rand"
"net/http"
"strconv"
@@ -144,3 +145,33 @@ func GetIP(r *http.Request) string {
}
return r.RemoteAddr
}

const (
PUBULIC_CHUNK_SIZE = 200
)

// ChunkInt64 将 int64 切片分成指定大小的批次
func ChunkInt64(items []int64, chunkSize int) [][]int64 {
if chunkSize <= 0 {
chunkSize = 500 // 默认批次大小
}

var chunks [][]int64
total := len(items)

for start := 0; start < total; start += chunkSize {
end := start + chunkSize
if end > total {
end = total
}
chunks = append(chunks, items[start:end])
}

return chunks
}

func JsonToString(unMarshalJson interface{}) string {
toString, _ := json.Marshal(unMarshalJson)

return string(toString)
}

BIN
public/img/computingpower/title-decoration.png View File

Before After
Width: 324  |  Height: 80  |  Size: 32 KiB

+ 4
- 0
routers/api/v1/api.go View File

@@ -1341,6 +1341,10 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("", pay_computility.GetComputilityConfig)
})

m.Group("/computility_partner", func() {
m.Get("", pay_computility.ComputilityPartner)
})

m.Get("/compute-nodes", reqToken(), user.GetComputeNodes)

// Notifications


+ 94
- 0
routers/api/v1/pay_computility/company.go View File

@@ -0,0 +1,94 @@
package pay_computility

// Company 公司基本信息
type Company struct {
Provider string `json:"provider,omitempty"` // 提供方
Name string `json:"name"` // 公司名称
Logo string `json:"logo"` // Logo图片路径或URL
Sort int `json:"sort"` // 排序值
Height float64 `json:"height"` // 排序值
Description string `json:"description,omitempty"` // 描述(可选)
Website string `json:"website,omitempty"` // 官网(可选)
}

type ResponseCompany struct {
ComputingPlatform []Company `json:"computing_platform"`
AiCenter []Company `json:"ai_center"`
ChipVendor []Company `json:"chip_vendor"`
}

func GetCompanies() ResponseCompany {
return ResponseCompany{
ComputingPlatform: []Company{
{
Provider: "haosuan",
Name: "联通昊算",
Logo: "",
},
{
Provider: "paratera",
Name: "并行科技",
Logo: "",
},
{
Provider: "supercomputing-internet",
Name: "超算互联网平台",
Logo: "",
},
{
Provider: "ctyun",
Name: "天翼云",
Logo: "",
},
},
AiCenter: []Company{
{
Provider: "pku-center",
Name: "北大分中心",
Logo: "",
},
{
Provider: "guangzhou-supercomputing",
Name: "广州超算",
Logo: "",
},
{
Provider: "hebei-ai-center",
Name: "河北人工智能计算中心",
Logo: "",
},
},
ChipVendor: []Company{
{
Provider: "huawei",
Name: "华为",
Logo: "",
},
{
Provider: "tiansu",
Name: "天数智芯",
Logo: "",
},
{
Provider: "suiyuan",
Name: "燧原科技",
Logo: "",
},
{
Provider: "haiguang",
Name: "海光",
Logo: "",
},
{
Provider: "muxi",
Name: "沐曦",
Logo: "",
},
{
Provider: "cambricon",
Name: "寒武纪",
Logo: "",
},
},
}
}

+ 8
- 34
routers/api/v1/pay_computility/computility_config.go View File

@@ -2,50 +2,24 @@ package pay_computility

import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/response"
"code.gitea.io/gitea/services/dynconfig"
"code.gitea.io/gitea/services/dynconfig/config_cache"
"code.gitea.io/gitea/services/dynconfig/fetcher"
"encoding/json"
"net/http"
"sync"
"time"
)

var (
gitComputilityConfigHelper *dynconfig.DyncConfigHelper
gitLocalOnce sync.Once
)

const GitComputilityConfigPath = "haosuan_config.json"

func getComputilityConfigHelper() *dynconfig.DyncConfigHelper {
gitLocalOnce.Do(func() {
gitComputilityConfigHelper = dynconfig.NewDyncConfigHelper(
config_cache.NewLocalCache(2*time.Minute, 1*time.Minute),
fetcher.NewGitLocalFetcher(setting.PayConputilityConfigRepoOwner, setting.PayConputilityConfigRepoName, setting.PayConputilitycConfigBranch),
)
})
return gitComputilityConfigHelper
}

// GetComputilityConfig 获取付费算力的配置
func GetComputilityConfig(ctx *context.APIContext) {
gitHelper := getComputilityConfigHelper()
resp := FilterData{}

computerConfigList, _ := gitHelper.GetConfig(GitComputilityConfigPath, false)
// step 1: 初始化算力管理器
cm := InitComputilityManager()

// 不用断言会出现panic
if str, ok := computerConfigList.(string); ok && computerConfigList != nil {
_ = json.Unmarshal([]byte(str), &resp)
}
// step 2: 算力过滤/获取
_ = cm.ComputilityFilter()

if len(resp.Products) == 0 {
resp = GetFilterData()
// step 3: 如果返回为空,塞入默认值返回
if len(cm.filterData.Products) == 0 {
cm.filterData = GetFilterData()
}

ctx.JSON(http.StatusOK, response.SuccessWithData(resp))
ctx.JSON(http.StatusOK, response.SuccessWithData(cm.filterData))
return
}

+ 80
- 0
routers/api/v1/pay_computility/computility_partners.go View File

@@ -0,0 +1,80 @@
package pay_computility

import (
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/routers/response"
"code.gitea.io/gitea/services/dynconfig"
"code.gitea.io/gitea/services/dynconfig/config_cache"
"code.gitea.io/gitea/services/dynconfig/fetcher"
"encoding/json"
"net/http"
"sort"
"strings"
"sync"
"time"
)

var partConfig *dynconfig.DyncConfigHelper

const GitComputilityPartnerConfigPath = "computility/computility_partners.json"

func getPartnerConfigHelper() *dynconfig.DyncConfigHelper {

partConfig = &dynconfig.DyncConfigHelper{}
gitLocalOnce := sync.Once{}
gitLocalOnce.Do(func() {
partConfig = dynconfig.NewDyncConfigHelper(
config_cache.NewLocalCache(2*time.Minute, 1*time.Minute),
fetcher.NewGitLocalFetcher(setting.ConputilityPartnerConfigRepoOwner, setting.ConputilityPartnerConfigRepoName, setting.ConputilityPartnerConfigBranch),
)
})

return partConfig
}

// ComputilityPartner 获取算力合作伙伴
func ComputilityPartner(ctx *context.APIContext) {
var (
resp = ResponseCompany{}
helper = getPartnerConfigHelper()
)

partConfigList, _ := helper.GetConfig(GitComputilityPartnerConfigPath, false)

if str, ok := partConfigList.(string); ok && partConfigList != nil {
if err := json.Unmarshal([]byte(str), &resp); err != nil {
ctx.JSON(200, response.OuterTrBizError(response.NewBizError(err), ctx.Locale))
return
}
}

if len(resp.AiCenter) == 0 {
resp = GetCompanies()
}

// 排序
SortCompany(resp.AiCenter)
SortCompany(resp.ChipVendor)
SortCompany(resp.ComputingPlatform)

// 获取算力合作伙伴配置
ctx.JSON(http.StatusOK, response.SuccessWithData(resp))
return
}

// SortCompany 排序【首先按 Sort 从大到小排序;Sort 相同时,按字母顺序 A-Z 排序】
func SortCompany(company []Company) {
sort.SliceStable(company, func(i, j int) bool {
// 首先按 Sort 从大到小排序
if company[i].Sort != company[j].Sort {
return company[i].Sort > company[j].Sort
}

// Sort 相同时,按字母顺序 A-Z 排序
cardTypeI := strings.ToUpper(company[i].Provider)
cardTypeJ := strings.ToUpper(company[j].Provider)

return cardTypeI < cardTypeJ
})
}

+ 250
- 0
routers/api/v1/pay_computility/computility_strategy.go View File

@@ -0,0 +1,250 @@
package pay_computility

import (
"code.gitea.io/gitea/modules/log"
"context"
"encoding/json"
"fmt"
"sort"
"strings"
"sync"
)

const (
HAOSUANPartnerName = "联通昊算"
PERATERPartnerName = "并行科技"
CHINAMOBILEPartnerName = "移动云"
)

var ProvidersList = []string{HAOSUANPartnerName, PERATERPartnerName, CHINAMOBILEPartnerName}

type ComputilityManager struct {
ctx context.Context
filterData FilterData
errorList []error
wg sync.WaitGroup
}

func InitComputilityManager() *ComputilityManager {
return &ComputilityManager{
ctx: context.Background(),
filterData: FilterData{
ResourceTypes: make([]ResourceType, 0),
Products: make([]Product, 0),
},
errorList: []error{},
wg: sync.WaitGroup{},
}
}

// ComputilityFilter 算力过滤,后续叠加算力
func (c *ComputilityManager) ComputilityFilter() error {
c.GetJson()

if len(c.errorList) > 0 {
// 错误可以做特殊处理或者日志打印

for _, err := range c.errorList {
log.Error("Computility error: %+v", err)
}
// 随意返回一条
return c.errorList[0]
}

return nil
}

// GetJson 获取配置的json
func (c *ComputilityManager) GetJson() *ComputilityManager {

defer func() {
if r := recover(); r != nil {
log.Error("ComputilityManager panic: %+v", r)
}
}()

var (
haosuanJsonData = FilterData{}
perateraJsonData = FilterData{}
chinaMobileJsonData = FilterData{}
manager = InitComputilityConfigManager()
)

// 增加对应的数据
// step 1 HAOSUAN 配置
haosuanConfigList, _ := manager.hsConfig.GetConfig(GitComputilityConfigPath, false)

// 不用断言会出现panic
if str, ok := haosuanConfigList.(string); ok && haosuanConfigList != nil {
if err := json.Unmarshal([]byte(str), &haosuanJsonData); err != nil {
// 错误放到列表里面去
c.errorList = append(c.errorList, err)
}
}

//打上产品提供方标签
c.filterData = c.FillProductType(HAOSUANPartnerName, haosuanJsonData)

// step 1.1 并行科技配置
perateraConfigList, _ := manager.peraConfig.GetConfig(GitPerateraComputilityConfigPath, false)

// 不用断言会出现panic
if str, ok := perateraConfigList.(string); ok && perateraConfigList != nil {
if err := json.Unmarshal([]byte(str), &perateraJsonData); err != nil {
// 错误放到列表里面去
c.errorList = append(c.errorList, err)
}
}

// 打上产品提供方标签
perateraJsonData = c.FillProductType(PERATERPartnerName, perateraJsonData)

// step 1.2 增加中国移动
mobildConfigList, _ := manager.mobileConfig.GetConfig(GitChinaMobileComputilityConfigPath, false)

// 不用断言会出现panic
if str, ok := mobildConfigList.(string); ok && mobildConfigList != nil {
if err := json.Unmarshal([]byte(str), &chinaMobileJsonData); err != nil {
// 错误放到列表里面去
c.errorList = append(c.errorList, err)
}
}

// 打上产品提供方标签
chinaMobileJsonData = c.FillProductType(CHINAMOBILEPartnerName, chinaMobileJsonData)

marshal, _ := json.Marshal(chinaMobileJsonData)
fmt.Println(fmt.Sprintf("china-[%+v]", string(marshal)))

// step 2 组装数据
c.ComposeFilterData(perateraJsonData, chinaMobileJsonData)

// step 3 补充筛选列表提供方
c.FillProviders()

// step 4 排序返回
c.Sort()
return c
}

// FillProviders 补充提供方
func (c *ComputilityManager) FillProviders() {
if len(c.filterData.ResourceTypes) == 0 {
return
}
for i := range c.filterData.ResourceTypes {
c.filterData.ResourceTypes[i].Providers = ProvidersList
}

return
}

// FillProductType 补充产品类型
func (c *ComputilityManager) FillProductType(partner string, filterData FilterData) FilterData {
if len(filterData.Products) == 0 {
return filterData
}
for i := range filterData.Products {
filterData.Products[i].Provider = partner
}

return filterData
}

// Sort 根据CardType按字母A-Z排序(不区分大小写)
func (c *ComputilityManager) Sort() *ComputilityManager {
if c == nil || len(c.filterData.Products) == 0 {
return c
}

// 在排序卡类型,否则id降序排序
sort.SliceStable(c.filterData.Products, func(i, j int) bool {
cardTypeI := strings.ToUpper(c.filterData.Products[i].CardType)
cardTypeJ := strings.ToUpper(c.filterData.Products[j].CardType)

if cardTypeI != cardTypeJ {
return cardTypeI < cardTypeJ
}

// CardType相同时,按ID降序排序
return c.filterData.Products[i].ID > c.filterData.Products[j].ID
})

return c
}

// ComposeFilterData 组合返回资源
// 主要处理【c.filterData】里面的数据
func (c *ComputilityManager) ComposeFilterData(newFilterDataList ...FilterData) *ComputilityManager {

if c.filterData.ResourceTypes == nil {
c.filterData = GetFilterData()
}

var (
categoryMap = make(map[string]ResourceType)
cardTypesMap = make(map[string]bool)
cardCountMap = make(map[int64]bool)
resourceTypes = make([]ResourceType, 0, len(c.filterData.ResourceTypes))
)

for _, newFilterData := range newFilterDataList {
if newFilterData.Products != nil {
c.filterData.Products = append(c.filterData.Products, newFilterData.Products...)
}

// 处理之前的数据,主要是筛选类型
for _, index := range c.filterData.ResourceTypes {
if index.Category == "" {
continue
}

categoryMap[index.Category] = index
for _, cardType := range index.CardTypes {
cardTypesMap[cardType] = true
}
for _, cardCount := range index.CardCounts {
cardCountMap[cardCount] = true
}
}

// 双列表合并
for _, index := range newFilterData.Products {
if categoryMap[index.Category].Category == "" {
continue
}

isNotExistCardType := !cardTypesMap[index.CardType]
isNotExistCardCount := !cardCountMap[index.CardCount]
category := categoryMap[index.Category]

// 如果不存在对应数据,就添加
if isNotExistCardType {
category.CardTypes = append(category.CardTypes, index.CardType)
}

if isNotExistCardCount {
category.CardCounts = append(category.CardCounts, index.CardCount)
}

categoryMap[index.Category] = category
}
}

// 同时补充筛选条件
for _, index := range categoryMap {
resourceTypes = append(resourceTypes, index)
}

if len(resourceTypes) > 0 {
c.filterData.ResourceTypes = resourceTypes
}

return c
}

// GetApi 获取第三方api
func (c *ComputilityManager) GetApi() *ComputilityManager {
// TODO 若需要对接bx算力,需要在这里合并返回
return c
}

+ 70
- 0
routers/api/v1/pay_computility/dynamic_config.go View File

@@ -0,0 +1,70 @@
package pay_computility

import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/dynconfig"
"code.gitea.io/gitea/services/dynconfig/config_cache"
"code.gitea.io/gitea/services/dynconfig/fetcher"
"context"
"sync"
"time"
)

const (
GitComputilityConfigPath = "haosuan_config.json"
GitPerateraComputilityConfigPath = "peratera.json"
GitChinaMobileComputilityConfigPath = "ecloud.json"
)

type ComputilityConfigManager struct {
ctx context.Context
hsConfig *dynconfig.DyncConfigHelper
peraConfig *dynconfig.DyncConfigHelper
mobileConfig *dynconfig.DyncConfigHelper
}

func InitComputilityConfigManager() *ComputilityConfigManager {
return &ComputilityConfigManager{
ctx: context.Background(),
hsConfig: getHaosuanConfigHelper(),
peraConfig: getPerateraConfigHelper(),
mobileConfig: getChinaMobileConfigHelper(),
}
}

func getHaosuanConfigHelper() *dynconfig.DyncConfigHelper {
gitComputilityConfigHelper := &dynconfig.DyncConfigHelper{}
gitLocalOnce := &sync.Once{}

gitLocalOnce.Do(func() {
gitComputilityConfigHelper = dynconfig.NewDyncConfigHelper(
config_cache.NewLocalCache(2*time.Minute, 1*time.Minute),
fetcher.NewGitLocalFetcher(setting.PayConputilityConfigRepoOwner, setting.PayConputilityConfigRepoName, setting.PayConputilitycConfigBranch),
)
})
return gitComputilityConfigHelper
}

func getPerateraConfigHelper() *dynconfig.DyncConfigHelper {
gitComputilityConfigHelper := &dynconfig.DyncConfigHelper{}
gitLocalOnce := sync.Once{}
gitLocalOnce.Do(func() {
gitComputilityConfigHelper = dynconfig.NewDyncConfigHelper(
config_cache.NewLocalCache(2*time.Minute, 1*time.Minute),
fetcher.NewGitLocalFetcher(setting.PeraPayConputilityConfigRepoOwner, setting.PeraPayConputilityConfigRepoName, setting.PeraPayConputilitycConfigBranch),
)
})
return gitComputilityConfigHelper
}

func getChinaMobileConfigHelper() *dynconfig.DyncConfigHelper {
gitComputilityConfigHelper := &dynconfig.DyncConfigHelper{}
gitLocalOnce := sync.Once{}
gitLocalOnce.Do(func() {
gitComputilityConfigHelper = dynconfig.NewDyncConfigHelper(
config_cache.NewLocalCache(2*time.Minute, 1*time.Minute),
fetcher.NewGitLocalFetcher(setting.ChinaMobilePayConputilityConfigRepoOwner, setting.ChinaMobilePayConputilityConfigRepoName, setting.ChinaMobilePayConputilitycConfigBranch),
)
})
return gitComputilityConfigHelper
}

+ 28
- 0
routers/api/v1/pay_computility/transform.go View File

@@ -6,6 +6,7 @@ type ResourceType struct {
CardTypes []string `json:"cardTypes,omitempty"` // 联动卡类型
CardCounts []int64 `json:"cardCounts,omitempty"` // 联动卡数
PriceUnit string `json:"priceUnit,omitempty"` // 价格单位
Providers []string `json:"providers,omitempty"` // 联动的供应商
}

type Product struct {
@@ -23,6 +24,7 @@ type Product struct {
Category string `json:"category,omitempty"` // 产品类别:bare_metal/cloud_server/container_cloud
CardType string `json:"cardType,omitempty"` // 联动卡类型
CardCount int64 `json:"cardCount,omitempty"` // 联动卡数
Provider string `json:"provider,omitempty"` // 提供方
}

type FilterData struct {
@@ -58,3 +60,29 @@ func GetFilterData() FilterData {
Products: []Product{},
}
}

func GetResourceTypeEmpty() []ResourceType {
return []ResourceType{
{
Label: "云服务器",
Category: "cloud_server",
CardTypes: []string{},
CardCounts: []int64{},
PriceUnit: "元/卡/时",
},
{
Label: "裸金属",
Category: "bare_metal",
CardTypes: []string{},
CardCounts: nil,
PriceUnit: "元/台/月",
},
{
Label: "容器云",
Category: "container_cloud",
CardTypes: []string{},
CardCounts: nil,
PriceUnit: "元/核时",
},
}
}

+ 1
- 1
routers/card_request/card_request.go View File

@@ -97,7 +97,7 @@ func GetResourceList(ctx *context.Context) {
MinPrice: minPrice,
MaxPrice: maxPrice,
}
res, total, err := resource.GetResourceListPaging(opts)
res, total, err := resource.GetResourceListPagingV2(opts)
if err != nil {
log.Error("GetResourceList err.opts=%+v,%v", opts, err)
ctx.JSON(http.StatusOK, response.OuterResponseError(err))


+ 15
- 2
routers/resources/acc_card.go View File

@@ -1,6 +1,7 @@
package resources

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/routers/response"
@@ -9,7 +10,13 @@ import (
)

func GetAccCardList(ctx *context.Context) {
list, err := resource.GetAccCardList()
specIds, err := models.GetAllSpecIds()
if err != nil {
ctx.JSON(http.StatusOK, response.OuterResponseError(err))
return

}
list, err := resource.GetAccCardListV2(specIds)
if err != nil {
log.Error("GetAccCardList error.%v", err)
ctx.JSON(http.StatusOK, response.OuterResponseError(err))
@@ -21,7 +28,13 @@ func GetAccCardList(ctx *context.Context) {
}

func GetAvailableAICenterList(ctx *context.Context) {
list, err := resource.GetAvailableAICenter()
specIds, err := models.GetAllSpecIds()
if err != nil {
ctx.JSON(http.StatusOK, response.OuterResponseError(err))
return

}
list, err := resource.GetAvailableAICenterV2(specIds)
if err != nil {
log.Error("GetAvailableAICenterList error.%v", err)
ctx.JSON(http.StatusOK, response.OuterResponseError(err))


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

@@ -1,6 +1,7 @@
package resource

import (
"code.gitea.io/gitea/modules/util"
"encoding/json"
"errors"
"fmt"
@@ -754,10 +755,47 @@ func GetResourceListPaging(opts models.GetResourceListOpts) ([]*models.ResourceI
return models.GetResourceListPaging(opts)
}

// GetResourceListPagingV2 处理资源的list
func GetResourceListPagingV2(opts models.GetResourceListOpts) ([]*models.ResourceInfo4CardRequest, int64, error) {
// 获取所有的资源ids
specIds, err := models.GetAllSpecIds()
if err != nil {
return []*models.ResourceInfo4CardRequest{}, 0, err
}

var (
rsp = make([]*models.ResourceInfo4CardRequest, 0)
total int64
)

// 分批函数处理,防止in sql过多,size = 【200】
for _, chunkSpecIds := range util.ChunkInt64(specIds, util.PUBULIC_CHUNK_SIZE) {
rtr, gTotal, mErr := models.GetResourceListPagingV2(opts, chunkSpecIds)
if mErr != nil {
return []*models.ResourceInfo4CardRequest{}, 0, mErr
}

// 总数叠加处理
total += gTotal

rsp = append(rsp, rtr...)

}
return rsp, total, nil
}

func GetAccCardList() ([]models.AccCardInfo, error) {
return models.GetAccCardList()
}

// GetAccCardList 根据角色查询
func GetAccCardListV2(specIds []int64) ([]models.AccCardInfo, error) {
return models.GetAccCardListV2(specIds)
}

func GetAvailableAICenter() ([]*models.ResourceAiCenterRes, error) {
return models.GetAvailableResourceAiCenters()
}
func GetAvailableAICenterV2(specIds []int64) ([]*models.ResourceAiCenterRes, error) {
return models.GetAvailableResourceAiCentersV2(specIds)
}

+ 9
- 0
web_src/vuepages/apis/modules/computingpower.js View File

@@ -70,6 +70,15 @@ export const getPayResourceList = (params) => {
});
};

/* 合作伙伴 */
export const getPartners = (params) => {
return service({
url: `/api/v1/computility_partner`,
method: 'get',
params: { ...params },
});
};

/* 算力需求 */
// 获取创建算力计算资源和卡类型信息
export const getDemandCreationRequired = (params) => {


+ 81
- 28
web_src/vuepages/pages/computingpower/components/ExtraResources.vue View File

@@ -31,6 +31,15 @@
</div>
</div>
</div>
<div class="conds-item" v-show="providerList.length > 2">
<div class="conds-item-tit">提供方:</div>
<div class="conds-item-content">
<div class="sel-item" :class="item.k == conds.provider ? 'active' : ''" v-for="(item, index) in providerList"
:key="index" @click="changeConds(item.k, 'provider')">
{{ item.v }}
</div>
</div>
</div>
</div>
<div class="content-c">
<div class="list-c" v-loading="loading">
@@ -39,14 +48,15 @@
<div class="left">
<div class="title">
<div class="name">{{ item.name }}</div>
<div class="type" :class="item.source_type == '独享' ? 'exclusive' : ''">{{ item.source_type }}</div>
<div v-if="item.source_type" class="type" :class="item.source_type == '独享' ? 'exclusive' : ''">{{
item.source_type }}</div>
</div>
</div>
<div class="right">
<span class="price">{{ item.price }}</span> {{ item.priceUnit }}
</div>
</div>
<div class="bottom">
<div class="mid">
<div class="left">
<div class="attributes">
<div class="attribute" v-for="(attr, index) in ['cpu', 'gpu', 'npu', 'memory', 'storage']"
@@ -57,7 +67,10 @@
</div>
<div class="right"></div>
</div>
<div class="go-buy" @click="goBuy(item)">立即购买</div>
<div class="bottom">
<div class="provider"> {{ item.provider }}</div>
<div class="go-buy" @click="goBuy(item)">立即购买</div>
</div>
</div>
<div class="demand-item no-data" v-if="(!showList.length && !loading)">
<div class="item-empty">
@@ -92,6 +105,7 @@ export default {
computeResourceList: [{ k: '', v: '全部' }],
cardTypeList: [{ k: '', v: '全部' }],
cardNumList: [{ k: '', v: '全部' }],
providerList: [{ k: '', v: '全部' }],
attrTitle: {
cpu: 'CPU',
gpu: '显存',
@@ -102,10 +116,12 @@ export default {
computeResourceMap: {},
cardTypeMap: {},
cardNumMap: {},
providerMap: {},
conds: {
compute_resource: '',
card_type: '',
acc_cards_num: '',
provider: '',
page: 1,
page_size: 15,
},
@@ -176,14 +192,9 @@ export default {
},
goBuy(item) {
if (this.isLogin) {
if (item.purchaseLink.includes('https://haosuan.com/api/auth/sso/haosuan/')) {
window.open(item.purchaseLink, '_blank');
} else {
const url = item.purchaseLink.includes('%3A%2F%2F') ? item.purchaseLink : encodeURIComponent(item.purchaseLink);
window.open(`https://haosuan.com/api/auth/sso/haosuan/openi?url=${url}`, '_blank');
}
window.open(item.purchaseLink, '_blank');
} else {
window.location.href = `/user/login?redirect_to=${encodeURIComponent(window.location.href + '?active=4')}`;
window.location.href = `/user/login?redirect_to=${encodeURIComponent(window.location.href + '?active=1')}`;
}
},
getData() {
@@ -195,6 +206,7 @@ export default {
compute_resource: '',
card_type: '',
acc_cards_num: '',
provider: '',
page: 1,
page_size: 15,
};
@@ -216,6 +228,7 @@ export default {
this.computeResourceMap[computeSourceKey] = computeSource;
const cardList = resourceTypesI.cardTypes || [];
const cardCounts = resourceTypesI.cardCounts || [];
const providers = resourceTypesI.providers || [];
if (this.cardTypeMap[computeSourceKey]) {
this.cardTypeMap[computeSourceKey].push(...cardList);
} else {
@@ -226,6 +239,11 @@ export default {
} else {
this.cardNumMap[computeSourceKey] = cardCounts;
}
if (this.providerMap[computeSourceKey]) {
this.providerMap[computeSourceKey].push(...providers);
} else {
this.providerMap[computeSourceKey] = providers;
}
}
for (let key in this.computeResourceMap) {
const computeSource = this.computeResourceMap[key]
@@ -258,13 +276,25 @@ export default {
k: 'other',
v: '其它'
});
for (let key in this.providerMap) {
this.providerMap[key] = Array.from(new Set(this.providerMap[key]));
this.providerMap[key].forEach(item => {
if (!this.providerList.find(itm => itm.k == item)) {
this.providerList.push({
k: item,
v: item,
});
}
})
}
const dataList = res.Data.products || []
this.list = dataList.map(item => {
return {
...item,
computeResource: item.category,
cardType: item.cardType,
cardNum: item.cardCount
cardNum: item.cardCount,
provider: item.provider,
}
})
}
@@ -282,12 +312,13 @@ export default {
resource: this.conds.compute_resource === '' ? '' : this.computeResourceMap[this.conds.compute_resource] || [this.conds.compute_resource],
accCardType: this.conds.card_type,
accCardNum: this.conds.acc_cards_num === '' ? -1 : this.conds.acc_cards_num,
provider: this.conds.provider === '' ? '' : this.conds.provider,
};
// console.log('search conds', this.conds);
// console.log('search params', params);
this.loading = true;
const useList = this.list.filter((item) => {
let check1 = true, check2 = true, check3 = true;
let check1 = true, check2 = true, check3 = true, check4 = true;
if (params.resource) {
check1 = params.resource.includes(item.computeResource)
}
@@ -301,7 +332,10 @@ export default {
check3 = item.cardNum == params.accCardNum
}
}
return check1 && check2 && check3
if (params.provider) {
check4 = item.provider == params.provider
}
return check1 && check2 && check3 && check4
})
this.paginationInfo.total = useList.length;
this.showList = useList.slice((params.page - 1) * params.pageSize, params.page * params.pageSize)
@@ -463,10 +497,11 @@ export default {
}
}

.bottom {
.mid {
padding-top: 6px;
display: flex;
align-items: flex-end;
min-height: 100px;

.left {
width: 0;
@@ -501,25 +536,43 @@ export default {
display: flex;
align-items: center;
justify-content: flex-end;


}
}

.go-buy {
position: absolute;
right: 22px;
bottom: 18px;
.bottom {
border-top: 1px solid rgba(157, 197, 226, 0.2);
margin-top: 8px;
padding-top: 14px;
display: flex;
align-items: center;
justify-content: center;
height: 30px;
padding: 0 12px;
border-radius: 4px;
color: rgb(255, 255, 255);
font-size: 12px;
background: rgb(1, 72, 255);
cursor: pointer;
justify-content: space-between;

.provider {
display: flex;
align-items: center;
justify-content: center;
padding: 0 12px;
height: 30px;
border-radius: 4px;
background-color: rgba(50, 145, 248, 0.1);
color: #3291f8;
font-size: 12px;
text-align: center;
border: 1px solid rgba(50, 145, 248, 0.6);
}

.go-buy {
display: flex;
align-items: center;
justify-content: center;
height: 30px;
padding: 0 12px;
border-radius: 4px;
color: rgb(255, 255, 255);
font-size: 12px;
background: rgb(1, 72, 255);
cursor: pointer;
}
}
}
}


+ 126
- 0
web_src/vuepages/pages/computingpower/components/JumpButton.vue View File

@@ -0,0 +1,126 @@
<template>
<div class="jp-btn" :class="[size]" @click="handleClick">
<div class="bg-1">
<div class="bg-2">
<el-tooltip v-if="tooltip" effect="dark" placement="top">
<div slot="content">{{ tooltip }}</div>
<div class="content">
<div class="icon-c">
<svg xmlns="http://www.w3.org/2000/svg" fill="rgb(255, 255, 255)" viewBox="0 0 512 512" width="16"
height="16">
<defs></defs>
<g>
<path
d="M478.21 334.093L336 256l142.21-78.093c11.795-6.477 15.961-21.384 9.232-33.037l-19.48-33.741c-6.728-11.653-21.72-15.499-33.227-8.523L296 186.718l3.475-162.204C299.763 11.061 288.937 0 275.48 0h-38.96c-13.456 0-24.283 11.061-23.994 24.514L216 186.718 77.265 102.607c-11.506-6.976-26.499-3.13-33.227 8.523l-19.48 33.741c-6.728 11.653-2.562 26.56 9.233 33.037L176 256 33.79 334.093c-11.795 6.477-15.961 21.384-9.232 33.037l19.48 33.741c6.728 11.653 21.721 15.499 33.227 8.523L216 325.282l-3.475 162.204C212.237 500.939 223.064 512 236.52 512h38.961c13.456 0 24.283-11.061 23.995-24.514L296 325.282l138.735 84.111c11.506 6.976 26.499 3.13 33.227-8.523l19.48-33.741c6.728-11.653 2.563-26.559-9.232-33.036z">
</path>
</g>
</svg>
</div>
<div class="title">{{ title }}</div>
</div>
</el-tooltip>
<div v-else class="content">
<div class="icon-c">
<svg xmlns="http://www.w3.org/2000/svg" fill="rgb(255, 255, 255)" viewBox="0 0 512 512" width="16"
height="16">
<defs></defs>
<g>
<path
d="M478.21 334.093L336 256l142.21-78.093c11.795-6.477 15.961-21.384 9.232-33.037l-19.48-33.741c-6.728-11.653-21.72-15.499-33.227-8.523L296 186.718l3.475-162.204C299.763 11.061 288.937 0 275.48 0h-38.96c-13.456 0-24.283 11.061-23.994 24.514L216 186.718 77.265 102.607c-11.506-6.976-26.499-3.13-33.227 8.523l-19.48 33.741c-6.728 11.653-2.562 26.56 9.233 33.037L176 256 33.79 334.093c-11.795 6.477-15.961 21.384-9.232 33.037l19.48 33.741c6.728 11.653 21.721 15.499 33.227 8.523L216 325.282l-3.475 162.204C212.237 500.939 223.064 512 236.52 512h38.961c13.456 0 24.283-11.061 23.995-24.514L296 325.282l138.735 84.111c11.506 6.976 26.499 3.13 33.227-8.523l19.48-33.741c6.728-11.653 2.563-26.559-9.232-33.036z">
</path>
</g>
</svg>
</div>
<div class="title">{{ title }}</div>
</div>
</div>
</div>
</div>
</template>

<script>

export default {
name: "JumpButton",
props: {
size: { type: String, default: 'big' }, // big|small
title: { type: String, default: '' },
tooltip: { type: String, default: '' },
},
data() {
return {};
},
methods: {
handleClick() {
this.$emit('click');
},
},
mounted() { },
};
</script>
<style scoped lang="less">
.jp-btn {
position: relative;
height: 40px;
border-radius: 4px;
cursor: pointer;

.bg-1 {
height: 100%;
border-radius: 4px;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3ClinearGradient%20id%3D%221%22%20x1%3D%220%22%20x2%3D%221%22%20y1%3D%220%22%20y2%3D%220%22%20gradientTransform%3D%22matrix(0.596%2C%200.9139999999999999%2C%20-0.07461224489795917%2C%200.596%2C%20-0.016%2C%200.047)%22%3E%3Cstop%20stop-color%3D%22%2350a0f7%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%230b3ff7%22%20stop-opacity%3D%220.88%22%20offset%3D%221%22%3E%3C%2Fstop%3E%3C%2FlinearGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");

.bg-2 {
height: 100%;
border-radius: 4px;
background: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20version%3D%221.1%22%3E%3Cdefs%3E%3CradialGradient%20id%3D%221%22%20cx%3D%220%22%20cy%3D%220%22%20r%3D%221%22%20gradientTransform%3D%22matrix(-1.295%2C%200.9540000000000001%2C%20-0.16112865306122448%2C%20-2.6793549999999997%2C%200.998%2C%200.061)%22%3E%3Cstop%20stop-color%3D%22%23ffb798%22%20stop-opacity%3D%221%22%20offset%3D%220%22%3E%3C%2Fstop%3E%3Cstop%20stop-color%3D%22%23650bf7%22%20stop-opacity%3D%220%22%20offset%3D%220.82%22%3E%3C%2Fstop%3E%3C%2FradialGradient%3E%3C%2Fdefs%3E%3Crect%20width%3D%22100%25%22%20height%3D%22100%25%22%20fill%3D%22url(%231)%22%3E%3C%2Frect%3E%3C%2Fsvg%3E");
}
}

.content {
height: 100%;
top: 0px;
left: 0px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 10px;
z-index: 1;

.icon-c {
position: relative;
border: 0.8px solid rgb(255, 255, 255);
border-radius: 100%;
height: 22px;
width: 22px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 6px;

svg {
width: calc(100% - 4px);
height: calc(100% - 4px);
}
}

.title {
color: rgb(255, 255, 255);
font-size: 14px;
}
}

&.small {
height: 30px;

.content {
padding: 0 8px;

.icon-c {
height: 20px;
width: 20px;
}
}
}
}
</style>

+ 203
- 0
web_src/vuepages/pages/computingpower/components/Partner.vue View File

@@ -0,0 +1,203 @@
<template>
<div class="partner-c">
<div class="title-c">
<div class="l"><img src="/img/computingpower/title-decoration.png" alt=""></div>
<div class="tit">算力合作伙伴</div>
<div class="r"><img src="/img/computingpower/title-decoration.png" alt=""></div>
</div>
<div class="part-title-c">
<div class="part-title">
<div class="l"></div>
<div class="tit">算力运营平台</div>
<div class="r"></div>
</div>
</div>
<div class="computing-platform-c">
<div class="item" v-for="(item, index) in computingPlatform" :key="index">
<img :src="item.logo" :style="item.height ? `height:${item.height}px` : ''" alt="">
</div>
</div>
<div class="part-title-c">
<div class="part-title">
<div class="l"></div>
<div class="tit">芯片厂商</div>
<div class="r"></div>
</div>
</div>
<div class="chip-vendor-c">
<div class="item" v-for="(item, index) in chipVendor" :key="index">
<img :src="item.logo" :style="item.height ? `height:${item.height}px` : ''" alt="">
</div>
</div>
<div class="part-title-c">
<div class="part-title">
<div class="l"></div>
<div class="tit">智算中心</div>
<div class="r"></div>
</div>
</div>
<div class="ai-center-c">
<div class="item" v-for="(item, index) in aiCenter" :key="index">
<img :src="item.logo" alt="">
</div>
</div>
</div>
</template>

<script>

import { getPartners } from '~/apis/modules/computingpower';

export default {
name: "Partner",
props: {},
data() {
return {
computingPlatform: [],
chipVendor: [],
aiCenter: [],
};
},
methods: {},
mounted() {
getPartners({}).then(res => {
res = res.data;
if (res.Code == 0) {
const data = res.Data;
this.computingPlatform = (data.computing_platform || []);
this.chipVendor = (data.chip_vendor || []);
this.aiCenter = (data.ai_center || []);
}
}).catch(err => {
console.log(err);
})
},
};
</script>
<style scoped lang="less">
.partner-c {
margin-top: 32px;

.title-c {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 32px;

.l {
width: 162px;
height: 40px;

img {
height: 100%;
width: 100%;
}
}

.tit {
font-size: 28px;
}

.r {
width: 162px;
height: 40px;

img {
height: 100%;
width: 100%;
transform: rotate(180deg);
}
}
}

.part-title-c {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 32px;

.part-title {
display: flex;
align-items: center;
justify-content: center;
width: 268px;

.tit {
font-size: 16px;
color: rgba(16, 16, 16, 0.5);
margin: 0 16px;
}

.l,
.r {
flex: 1;
height: 0;
width: 268px;
border-bottom: 1px solid rgb(187, 187, 187);
}
}
}

.computing-platform-c {
display: flex;
align-items: center;
justify-content: center;
gap: 0 70px;
margin-bottom: 20px;
flex-wrap: wrap;

.item {
display: flex;
height: 60px;
margin-bottom: 23px;
align-items: center;
justify-content: center;

img {
height: 60px;
}
}
}

.chip-vendor-c {
display: flex;
align-items: center;
justify-content: center;
gap: 0 35px;
margin-bottom: 22px;
flex-wrap: wrap;

.item {
height: 60px;
margin-bottom: 23px;
display: flex;
align-items: center;
justify-content: center;

img {
height: 55px;
}
}
}

.ai-center-c {
display: flex;
align-items: center;
justify-content: center;
gap: 0 4px;
margin-bottom: 20px;
flex-wrap: wrap;

.item {
height: 65px;
margin-bottom: 0px;
align-items: center;
justify-content: center;

img {
height: 100%;
}
}
}
}
</style>

+ 25
- 29
web_src/vuepages/pages/computingpower/components/Resources.vue View File

@@ -59,8 +59,8 @@
<div class="left">
<div class="title">
<div class="name">{{ item.AccCardTypeShow || item.AccCardType }}</div>
<div v-if="!item.IsExclusive" class="type">共享池</div>
<div v-if="item.IsExclusive" class="type exclusive">专属池</div>
<!-- <div v-if="!item.IsExclusive" class="type">共享池</div>
<div v-if="item.IsExclusive" class="type exclusive">专属池</div> -->
</div>
<div class="attributes">
<div class="attribute">
@@ -81,10 +81,11 @@
<div class="right">
<span class="price">{{ item.UnitPrice.toFixed(2) }}</span> 积分/卡时
<div class="right use-position-mobile-show">
<div class="apply"
<!-- <div class="apply"
v-if="item.IsExclusive || (item.IsExclusive == false && item.IsSpecExclusive == 'exclusive')"
@click="applyUse(item)">需申请使用</div>
<div class="available" v-else>可直接使用</div>
<div class="available" v-else>可直接使用</div> -->
<JumpButton title="去使用" size="small" tooltip="创建算力任务" @click="goAiTask(item)" />
</div>
</div>
</div>
@@ -95,10 +96,11 @@
</div>
</div>
<div class="right use-position-mobile-hide">
<div class="apply"
<!-- <div class="apply"
v-if="item.IsExclusive || (item.IsExclusive == false && item.IsSpecExclusive == 'exclusive')"
@click="applyUse(item)">需申请使用</div>
<div class="available" v-else>可直接使用</div>
<div class="available" v-else>可直接使用</div> -->
<JumpButton title="去使用" size="small" tooltip="创建算力任务" @click="goAiTask(item)" />
</div>
</div>
</div>
@@ -120,6 +122,7 @@
</template>

<script>
import JumpButton from './JumpButton.vue';
import { getAccCardList, getAvailableAiCenterList, getResourceList } from '~/apis/modules/computingpower';
import { ACC_CARD_TYPE } from '~/const';
import { getListValueWithKey } from '~/utils';
@@ -130,6 +133,7 @@ export default {
condtions: { type: Object, default: () => ({}) },
active: { type: Boolean, defalut: false },
},
components: { JumpButton },
data() {
return {
filterShow: true,
@@ -147,13 +151,13 @@ export default {
price_start: '',
price_end: '',
page: 1,
page_size: 15,
page_size: 20,
},
_price_start: '',
_price_end: '',
list: [],
paginationInfo: {
pageSizes: [15, 30, 50],
pageSizes: [20, 30, 50],
total: 0,
},
loading: false,
@@ -251,8 +255,8 @@ export default {
console.log(err);
});
},
applyUse(data) {
this.$emit('apply', data);
goAiTask(item) {
window.location.href = '/cloudbrains/create';
},
setConds() {
for (let key in this.conds) {
@@ -381,6 +385,10 @@ export default {
color: rgba(50, 145, 248, 1);
font-size: 12px;
}

.list-item {
width: 100% !important;
}
}

.resources-c {
@@ -455,6 +463,10 @@ export default {
margin-top: 20px;

.list-c {
display: flex;
flex-wrap: wrap;
gap: 30px 20px;

.list-item {
padding: 18px 22px;
border-radius: 15px;
@@ -462,6 +474,7 @@ export default {
box-shadow: 0px 5px 10px 0px rgba(157, 197, 226, 0.2);
border: 1px solid rgba(157, 197, 226, 0.4);
margin-bottom: 20px;
width: calc(50% - 10px);

.top {
display: flex;
@@ -521,7 +534,7 @@ export default {
font-size: 14px;

.price {
color: rgba(16, 16, 16, 1);
color: rgb(64, 123, 237);
font-size: 20px;
margin-right: 2px;
}
@@ -563,24 +576,7 @@ export default {
display: flex;
align-items: center;
justify-content: flex-end;

.available {
color: rgba(39, 177, 72, 1);
font-size: 12px;
}

.apply {
display: flex;
align-items: center;
justify-content: center;
height: 30px;
padding: 0 12px;
border-radius: 4px;
color: rgba(50, 145, 248, 1);
font-size: 12px;
border: 1px solid rgba(50, 145, 248, 1);
cursor: pointer;
}
padding-top: 10px;
}
}
}


+ 16
- 234
web_src/vuepages/pages/computingpower/demand/index.vue View File

@@ -4,13 +4,14 @@
<div class="ui container">
<div class="top-head only-mobile-hidden">
<div class="title">算力资源</div>
<div class="descr">为了满足用户的个性化算力需求, 可以根据用户的需求定制算力池,欢迎提交您的算力需求。</div>
<div class="descr" style="color:transparent;user-select:none;">为了满足用户的个性化算力需求, 可以根据用户的需求定制算力池,欢迎提交您的算力需求。
</div>
</div>
<div class="main-content">
<div class="top-area">
<div class="tab-c">
<div class="tab-item" :class="activeTab == 0 ? 'active' : ''" @click="changeTab(0)">普惠算力</div>
<div class="tab-item" :class="activeTab == 4 ? 'active' : ''" @click="changeTab(4)">
<div class="tab-item" :class="activeTab == 1 ? 'active' : ''" @click="changeTab(1)">
付费算力
<el-tooltip effect="dark" placement="top">
<div slot="content"> 付费算力及售后服务由合作伙伴提供 </div>
@@ -25,141 +26,26 @@
</svg>
</el-tooltip>
</div>
<div class="tab-item new-demand-tab-mobile-show" v-if="isLogin" :class="activeTab == 3 ? 'active' : ''"
@click="changeTab(3)">提交新需求</div>
</div>
<div class="operate-c only-mobile-hidden">
<div class="tab-c" style="margin-right:10px;">
<div class="tab-item" :class="activeTab == 1 ? 'active' : ''" @click="changeTab(1)">算力需求广场</div>
<div class="tab-item" v-if="isLogin" :class="activeTab == 2 ? 'active' : ''" @click="changeTab(2)">我的需求
</div>
</div>
<el-button size="default" v-if="isOperator" type="primary" @click="goOperate">需求处理</el-button>
<JumpButton title="新建计算任务" @click="goAiTask" />
</div>
</div>
<div class="middle-area">
<div class="left-area" v-loading="loading">
<div class="tab-content" v-show="activeTab == 0">
<Resources :active="activeTab == 0" @apply="applyUse">
<Resources :active="activeTab == 0">
</Resources>
</div>
<div class="tab-content" v-show="activeTab == 4">
<ExtraResources :active="activeTab == 4">
<div class="tab-content" v-show="activeTab == 1">
<ExtraResources :active="activeTab == 1">
</ExtraResources>
</div>
<div class="tab-content" v-if="activeTab == 1">
<div class="demand-item demand-item-all" v-for="(item, index) in dataAll" :key="index">
<div class="demand-item-top">
<div class="demand-item-line">
<div class="demand-item-line-block"><span>计算资源:</span><span>{{ item.compute_resource }}</span></div>
<div class="demand-item-line-block"><span>卡类型:</span><span>{{ item.card_type_show || item.card_type
}}</span></div>
<div class="demand-item-line-block"><span>卡数(卡):</span><span>{{ item.acc_cards_num }}</span></div>
</div>
<div class="demand-item-line">
<div class="demand-item-line-block">
<span>使用时间:</span><span>{{ item.begin_date }} 至 {{ item.end_date }}</span>
</div>
</div>
</div>
<div class="demand-item-bottom">
<div class="demand-item-left">
<span>{{ '创建于' }}</span>
<el-tooltip effect="dark" :content="dateFormatB(item.created_unix)" placement="top-start">
<span>{{ calcFromNow(item.created_unix) }}</span>
</el-tooltip>
</div>
<div class="demand-item-right">
<span class="status pending" v-if="item.status == 1"><i
class="el-icon-stopwatch"></i><span>待处理</span></span>
<span class="status refuse " v-if="item.status == 2"><i
class="el-icon-circle-check"></i><span>已处理</span></span>
<span class="status refuse" v-if="item.status == 3"><i
class="el-icon-circle-check"></i><span>已处理</span></span>
</div>
</div>
</div>
<div class="demand-item no-data" v-show="(!dataAll.length && !loading)">
<div class="item-empty">
<div class="item-empty-icon"></div>
<div class="item-empty-tips">{{ $t('modelObj.model_square_empty') }}</div>
</div>
</div>
</div>
<div class="tab-content" v-if="activeTab == 2">
<div class="demand-item demand-item-my" v-for="(item, index) in dataMy" :key="index">
<div class="demand-item-top">
<div class="demand-item-line">
<div class="demand-item-line-block"><span>计算资源:</span><span>{{ item.compute_resource }}</span></div>
<div class="demand-item-line-block"><span>卡类型:</span><span>{{ item.card_type_show || item.card_type
}}</span></div>
<div class="demand-item-line-block"><span>卡数(卡):</span><span>{{ item.acc_cards_num }}</span></div>
</div>
<div class="demand-item-line">
<div class="demand-item-line-block">
<span>使用时间:</span><span>{{ item.begin_date }} 至 {{ item.end_date }}</span>
</div>
</div>
<div class="demand-item-line">
<div class="demand-item-line-block">
<span>承接方:</span><span>{{Array.from(new Set((item.specs || []).map(itm =>
itm.AiCenterName))).join('、') || '--'}}</span>
</div>
</div>
</div>
<div class="demand-item-bottom">
<div class="demand-item-left">
<span>{{ '创建于' }}</span>
<el-tooltip effect="dark" :content="dateFormatB(item.created_unix)" placement="top-start">
<span>{{ calcFromNow(item.created_unix) }}</span>
</el-tooltip>
</div>
<div class="demand-item-right">
<DemandEditDialog v-if="item.status == 1 || item.status == 2" :title="`修改我的需求:`" :data="item"
@success="demandUpdateSuccess" @error="demandUpdateError">
<div class="edit-btn">修改</div>
</DemandEditDialog>
<span class="status pending" v-if="item.status == 1"><i
class="el-icon-stopwatch"></i><span>待处理</span></span>
<span class="status accepted" v-if="item.status == 2"><i
class="el-icon-circle-check"></i><span>已接纳</span></span>
<span class="status refuse" v-if="item.status == 3">
<i class="el-icon-circle-close"></i><span>未接纳</span>
<el-tooltip v-if="false" class="item" effect="dark" :content="item.review" placement="top">
<i class="el-icon-info" style="margin-left:5px;cursor:pointer"></i>
</el-tooltip>
</span>
</div>
</div>
</div>
<div class="demand-item no-data" v-show="(!dataMy.length && !loading)">
<div class="item-empty">
<div class="item-empty-icon"></div>
<div class="item-empty-tips">{{ $t('modelObj.model_square_empty') }}</div>
</div>
</div>
</div>
<div class="tab-content" v-if="activeTab == 3">
<div class="new-demand-content" v-if="isLogin">
<div class="form-container">
<DemandForm ref="demandFormRef" @success="demandAddSuccess" @error="demandAddError"></DemandForm>
</div>
</div>
</div>
<div class="pagination-c" v-if="![0, 4].includes(activeTab)">
<el-pagination background layout="total, sizes, prev, pager, next, jumper"
:current-page.sync="paginationInfo.page" :page-sizes="paginationInfo.pageSizes"
:page-size.sync="paginationInfo.pageSize" :total="paginationInfo.total" @current-change="currentChange"
@size-change="sizeChange">
</el-pagination>
</div>
</div>
<div class="right-area only-mobile-hidden" v-if="isLogin && ![4].includes(activeTab)">
<div class="form-container">
<DemandForm ref="demandFormRef" @success="demandAddSuccess" @error="demandAddError"></DemandForm>
</div>
</div>
</div>
<div>
<Partner />
</div>
</div>
</div>
</div>
@@ -168,14 +54,8 @@
<script>
import Resources from '../components/Resources.vue';
import ExtraResources from '../components/ExtraResources.vue';
import DemandForm from '../components/DemandForm.vue';
import DemandEditDialog from '../components/DemandEditDialog.vue';
import { getDemandList, getDemandMyList } from '~/apis/modules/computingpower';
import dayjs from 'dayjs';
import { lang } from '~/langs';
import { timeSinceUnix } from '~/utils';
import { ACC_CARD_TYPE } from '~/const';
import { getListValueWithKey } from '~/utils';
import JumpButton from '../components/JumpButton.vue';
import Partner from '../components/Partner.vue';

export default {
data() {
@@ -184,116 +64,18 @@ export default {
isOperator: false,
loading: false,
activeTab: 0,
dataAll: [],
dataMy: [],
paginationInfo: {
page: 1,
pageSize: 15,
pageSizes: [15, 30, 50],
total: 0,
},
};
},
components: { Resources, ExtraResources, DemandForm, DemandEditDialog },
components: { Resources, ExtraResources, JumpButton, Partner },
methods: {
changeTab(tab) {
if (tab == this.activeTab) return;
this.activeTab = tab;
this.paginationInfo.page = 1;
this.paginationInfo.total = 0;
this.getData();
this.$nextTick(() => {
this.$refs['demandFormRef']?.resetForm();
});
},
getData() {
const params = {
pageSize: this.paginationInfo.pageSize,
page: this.paginationInfo.page,
}
const subApi = this.activeTab == 1 ? getDemandList : this.activeTab == 2 ? getDemandMyList : '';
if (!subApi) return;
this.loading = true;
subApi(params).then(res => {
this.loading = false;
res = res.data;
if (res.code == 0) {
const data = res.data;
this.paginationInfo.total = data.total;
const cardRequestList = (data.cardRequestList || []).map(item => {
return {
...item,
card_type_show: getListValueWithKey(ACC_CARD_TYPE, item.card_type),
}
})
if (this.activeTab == 1) {
this.dataAll = cardRequestList;
} else if (this.activeTab == 2) {
this.dataMy = cardRequestList;
}
}
}).catch(err => {
this.loading = false;
console.log(err);
});
},
goOperate() {
window.location.href = `/kanban/index.html#/dashboard/computingpower/demand`;
},
currentChange(page) {
this.paginationInfo.page = page;
this.getData();
},
sizeChange(pageSize) {
this.paginationInfo.pageSize = pageSize;
this.getData();
},
calcFromNow(unix) {
return timeSinceUnix(unix, Date.now() / 1000);
},
dateFormatA(unix) {
return dayjs(unix * 1000).format('YYYY-MM-DD');
},
dateFormatB(unix) {
return lang == 'zh-CN' ? dayjs(unix * 1000).format('YYYY年MM月DD日 HH时mm分ss秒') :
dayjs(unix * 1000).format('ddd, D MMM YYYY HH:mm:ss [CST]');
},
demandAddSuccess() {
this.activeTab = -1;
window.scrollTo({ top: 0 });
this.changeTab(2);
},
demandAddError() { },
demandUpdateSuccess() {
this.getData();
},
demandUpdateError() { },
applyUse(data) {
if (!this.isLogin) {
window.location.href = `/user/login?redirect_to=${encodeURIComponent(window.location.href)}`;
return;
}
// 通过获取类名判断是否为移动端
const element = document.querySelector('.new-demand-tab-mobile-show');
if (element) {
const displayValue = window.getComputedStyle(element).getPropertyValue('display');
if (displayValue === 'none') {
// 当为web端时,保持原有逻辑不变,直接传递数据
this.$nextTick(() => {
this.$refs['demandFormRef']?.initApply(data);
});
} else if (displayValue === 'flex') {
// 当为移动端时,跳转到对应tab,并延迟半秒传递数据,以便先让mounted初始化
this.changeTab(3)
setTimeout(() => {
this.$nextTick(() => {
this.$refs['demandFormRef']?.initApply(data);
});
}, 500)
}
} else {
console.log('Element not found.');
}
getData() { },
goAiTask() {
window.location.href = '/cloudbrains/create';
},
},
created() {


Loading…
Cancel
Save
Baidu
map