Browse Source

merge

tags/v1.22.9.2^2
liuzx 3 years ago
parent
commit
313a176a65
100 changed files with 4662 additions and 203 deletions
  1. +110
    -0
      models/action.go
  2. +165
    -1
      models/action_list.go
  3. +8
    -0
      models/attachment.go
  4. +77
    -5
      models/cloudbrain.go
  5. +23
    -0
      models/cloudbrain_spec.go
  6. +24
    -0
      models/error.go
  7. +22
    -0
      models/helper.go
  8. +184
    -0
      models/limit_config.go
  9. +8
    -0
      models/models.go
  10. +142
    -0
      models/point_account.go
  11. +21
    -0
      models/point_account_log.go
  12. +6
    -0
      models/repo.go
  13. +10
    -2
      models/repo_watch.go
  14. +79
    -0
      models/reward_admin_log.go
  15. +459
    -0
      models/reward_operate_record.go
  16. +115
    -0
      models/reward_periodic_task.go
  17. +44
    -0
      models/task_accomplish_log.go
  18. +302
    -0
      models/task_config.go
  19. +4
    -0
      models/user.go
  20. +4
    -0
      models/wechat_bind.go
  21. +9
    -7
      modules/auth/wechat/access_token.go
  22. +3
    -3
      modules/auth/wechat/bind.go
  23. +2
    -0
      modules/auth/wechat/client.go
  24. +8
    -3
      modules/cloudbrain/resty.go
  25. +21
    -0
      modules/context/point.go
  26. +26
    -0
      modules/cron/tasks_basic.go
  27. +22
    -0
      modules/eventsource/manager_run.go
  28. +3
    -2
      modules/modelarts/modelarts.go
  29. +99
    -0
      modules/notification/action/action.go
  30. +6
    -0
      modules/notification/base/notifier.go
  31. +18
    -0
      modules/notification/base/null.go
  32. +38
    -0
      modules/notification/notification.go
  33. +27
    -0
      modules/notification/reward/point.go
  34. +83
    -1
      modules/redis/redis_client/client.go
  35. +17
    -0
      modules/redis/redis_key/account_redis_key.go
  36. +2
    -0
      modules/redis/redis_key/key_base.go
  37. +26
    -0
      modules/redis/redis_key/limit_redis_key.go
  38. +21
    -0
      modules/redis/redis_key/reward_redis_key.go
  39. +10
    -0
      modules/redis/redis_key/serial_redis_key.go
  40. +14
    -0
      modules/redis/redis_key/task_redis_key.go
  41. +15
    -8
      modules/redis/redis_lock/lock.go
  42. +30
    -13
      modules/setting/setting.go
  43. +1
    -1
      modules/templates/helper.go
  44. +10
    -0
      modules/util/uuid_util.go
  45. +14
    -3
      options/locale/locale_en-US.ini
  46. +13
    -1
      options/locale/locale_zh-CN.ini
  47. +1
    -1
      package.json
  48. +3
    -0
      routers/admin/dataset.go
  49. +1
    -0
      routers/authentication/wechat.go
  50. +1
    -1
      routers/authentication/wechat_event.go
  51. +5
    -0
      routers/image/image.go
  52. +57
    -15
      routers/repo/cloudbrain.go
  53. +20
    -5
      routers/repo/grampus.go
  54. +41
    -10
      routers/repo/modelarts.go
  55. +24
    -0
      routers/reward/point/account.go
  56. +45
    -0
      routers/reward/point/limit.go
  57. +158
    -0
      routers/reward/point/point.go
  58. +36
    -10
      routers/routes/routes.go
  59. +68
    -0
      routers/task/config.go
  60. +15
    -0
      routers/task/task.go
  61. +2
    -0
      routers/user/setting/profile.go
  62. +1
    -1
      services/phone/phone.go
  63. +50
    -0
      services/reward/admin_operate.go
  64. +133
    -0
      services/reward/cloudbrain_deduct.go
  65. +100
    -0
      services/reward/limiter/config.go
  66. +258
    -0
      services/reward/limiter/limiter.go
  67. +54
    -0
      services/reward/notify.go
  68. +280
    -0
      services/reward/operator.go
  69. +131
    -0
      services/reward/period_task.go
  70. +150
    -0
      services/reward/point/account/point_account.go
  71. +65
    -0
      services/reward/point/point_operate.go
  72. +47
    -0
      services/reward/record.go
  73. +28
    -0
      services/reward/serial.go
  74. +50
    -0
      services/task/period/handler.go
  75. +111
    -0
      services/task/task.go
  76. +183
    -0
      services/task/task_config.go
  77. +0
    -0
      services/wechat/auto_reply.go
  78. +14
    -7
      services/wechat/event_handle.go
  79. +5
    -5
      templates/base/footer_content.tmpl
  80. +5
    -5
      templates/base/footer_content_fluid.tmpl
  81. +4
    -2
      templates/base/head_navbar.tmpl
  82. +2
    -0
      templates/base/head_navbar_fluid.tmpl
  83. +4
    -2
      templates/base/head_navbar_home.tmpl
  84. +4
    -2
      templates/base/head_navbar_pro.tmpl
  85. +24
    -4
      templates/repo/cloudbrain/benchmark/new.tmpl
  86. +4
    -8
      templates/repo/cloudbrain/benchmark/show.tmpl
  87. +13
    -4
      templates/repo/cloudbrain/inference/new.tmpl
  88. +4
    -8
      templates/repo/cloudbrain/inference/show.tmpl
  89. +24
    -14
      templates/repo/cloudbrain/new.tmpl
  90. +3
    -5
      templates/repo/cloudbrain/show.tmpl
  91. +13
    -3
      templates/repo/cloudbrain/trainjob/new.tmpl
  92. +4
    -8
      templates/repo/cloudbrain/trainjob/show.tmpl
  93. +14
    -5
      templates/repo/grampus/trainjob/gpu/new.tmpl
  94. +12
    -3
      templates/repo/grampus/trainjob/npu/new.tmpl
  95. +2
    -4
      templates/repo/grampus/trainjob/show.tmpl
  96. +12
    -3
      templates/repo/modelarts/inferencejob/new.tmpl
  97. +2
    -5
      templates/repo/modelarts/inferencejob/show.tmpl
  98. +15
    -4
      templates/repo/modelarts/notebook/new.tmpl
  99. +2
    -5
      templates/repo/modelarts/notebook/show.tmpl
  100. +13
    -4
      templates/repo/modelarts/trainjob/new.tmpl

+ 110
- 0
models/action.go View File

@@ -60,6 +60,15 @@ const (
ActionCreateGPUTrainTask //31 ActionCreateGPUTrainTask //31
ActionCreateGrampusNPUTrainTask //32 ActionCreateGrampusNPUTrainTask //32
ActionCreateGrampusGPUTrainTask //33 ActionCreateGrampusGPUTrainTask //33
ActionBindWechat //34
ActionCreateCloudbrainTask //35
ActionDatasetRecommended //36
ActionCreateImage //37
ActionImageRecommend //38
ActionChangeUserAvatar //39
ActionPushCommits //40
ActionForkRepo //41

) )


// Action represents user operation type and other information to // Action represents user operation type and other information to
@@ -81,6 +90,18 @@ type Action struct {
IsTransformed bool `xorm:"INDEX NOT NULL DEFAULT false"` IsTransformed bool `xorm:"INDEX NOT NULL DEFAULT false"`
Content string `xorm:"TEXT"` Content string `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
Cloudbrain *Cloudbrain `xorm:"-"`
}

type ActionShow struct {
OpType ActionType
RepoLink string
ShortRepoFullDisplayName string
Content string
RefName string
IssueInfos []string
CommentLink string
Cloudbrain *CloudbrainShow4Action
} }


// GetOpType gets the ActionType of this action. // GetOpType gets the ActionType of this action.
@@ -218,6 +239,47 @@ func (a *Action) GetRepoLink() string {
return "/" + a.GetRepoPath() return "/" + a.GetRepoPath()
} }


func (a *Action) ToShow() *ActionShow {
actionShow := &ActionShow{}
actionShow.OpType = GetTaskOptType(*a)
actionShow.Content = a.Content
actionShow.RefName = a.RefName

if strings.Contains(a.Content, "|") && a.IsIssueAction() {
actionShow.IssueInfos = a.GetIssueInfos()
}

if a.Repo != nil {
actionShow.RepoLink = a.GetRepoLink()
actionShow.ShortRepoFullDisplayName = a.ShortRepoFullDisplayName()
}
if a.Comment != nil {
actionShow.CommentLink = a.GetCommentLink()
}

if a.Cloudbrain != nil {
c := &CloudbrainShow4Action{
ID: a.Cloudbrain.ID,
JobID: a.Cloudbrain.JobID,
Type: a.Cloudbrain.Type,
JobType: a.Cloudbrain.JobType,
DisplayJobName: a.Cloudbrain.DisplayJobName,
ComputeResource: a.Cloudbrain.ComputeResource,
}
actionShow.Cloudbrain = c
}

return actionShow
}

func GetTaskOptType(action Action) ActionType {
//Convert all types of cloudbrain tasks action into ActionCreateCloudbrainTask
if action.IsCloudbrainAction() {
return ActionCreateCloudbrainTask
}
return action.OpType
}

// GetRepositoryFromMatch returns a *Repository from a username and repo strings // GetRepositoryFromMatch returns a *Repository from a username and repo strings
func GetRepositoryFromMatch(ownerName string, repoName string) (*Repository, error) { func GetRepositoryFromMatch(ownerName string, repoName string) (*Repository, error) {
var err error var err error
@@ -315,6 +377,39 @@ func (a *Action) GetIssueContent() string {
return issue.Content return issue.Content
} }


func (a *Action) IsCloudbrainAction() bool {
switch a.OpType {
case ActionCreateDebugGPUTask,
ActionCreateDebugNPUTask,
ActionCreateTrainTask,
ActionCreateInferenceTask,
ActionCreateBenchMarkTask,
ActionCreateGPUTrainTask,
ActionCreateGrampusNPUTrainTask,
ActionCreateGrampusGPUTrainTask:
return true
}
return false
}

func (a *Action) IsIssueAction() bool {
switch a.OpType {
case ActionCreateIssue,
ActionCloseIssue,
ActionClosePullRequest,
ActionReopenIssue,
ActionReopenPullRequest,
ActionCommentPull,
ActionCommentIssue,
ActionCreatePullRequest,
ActionApprovePullRequest,
ActionRejectPullRequest,
ActionMergePullRequest:
return true
}
return false
}

// GetFeedsOptions options for retrieving feeds // GetFeedsOptions options for retrieving feeds
type GetFeedsOptions struct { type GetFeedsOptions struct {
RequestedUser *User // the user we want activity for RequestedUser *User // the user we want activity for
@@ -404,3 +499,18 @@ func GetUnTransformedActions() ([]*Action, error) {
Find(&actions) Find(&actions)
return actions, err return actions, err
} }

func GetActionByIds(ids []int64) ([]*Action, error) {
if len(ids) == 0 {
return nil, nil
}
actions := make([]*Action, 0)
err := x.In("id", ids).Find(&actions)
if err != nil {
return nil, err
}
if err := ActionList(actions).LoadAllAttributes(); err != nil {
return nil, fmt.Errorf("ActionList loadAttributes: %v", err)
}
return actions, nil
}

+ 165
- 1
models/action_list.go View File

@@ -4,7 +4,11 @@


package models package models


import "fmt"
import (
"fmt"
"strconv"
"xorm.io/builder"
)


// ActionList defines a list of actions // ActionList defines a list of actions
type ActionList []*Action type ActionList []*Action
@@ -26,6 +30,9 @@ func (actions ActionList) loadUsers(e Engine) ([]*User, error) {


userIDs := actions.getUserIDs() userIDs := actions.getUserIDs()
userMaps := make(map[int64]*User, len(userIDs)) userMaps := make(map[int64]*User, len(userIDs))
if len(userIDs) == 0 {
return make([]*User, 0), nil
}
err := e. err := e.
In("id", userIDs). In("id", userIDs).
Find(&userMaps) Find(&userMaps)
@@ -61,6 +68,9 @@ func (actions ActionList) loadRepositories(e Engine) ([]*Repository, error) {


repoIDs := actions.getRepoIDs() repoIDs := actions.getRepoIDs()
repoMaps := make(map[int64]*Repository, len(repoIDs)) repoMaps := make(map[int64]*Repository, len(repoIDs))
if len(repoIDs) == 0 {
return make([]*Repository, 0), nil
}
err := e. err := e.
In("id", repoIDs). In("id", repoIDs).
Find(&repoMaps) Find(&repoMaps)
@@ -79,6 +89,133 @@ func (actions ActionList) LoadRepositories() ([]*Repository, error) {
return actions.loadRepositories(x) return actions.loadRepositories(x)
} }


func (actions ActionList) getCommentIDs() []int64 {
commentIDs := make(map[int64]struct{}, len(actions))
for _, action := range actions {
if action.CommentID == 0 {
continue
}
if _, ok := commentIDs[action.CommentID]; !ok {
commentIDs[action.CommentID] = struct{}{}
}
}
return keysInt64(commentIDs)
}

func (actions ActionList) loadComments(e Engine) ([]*Comment, error) {
if len(actions) == 0 {
return nil, nil
}

commentIDs := actions.getCommentIDs()

commentMaps := make(map[int64]*Comment, len(commentIDs))
if len(commentIDs) == 0 {
return make([]*Comment, 0), nil
}
err := e.
In("id", commentIDs).
Find(&commentMaps)
if err != nil {
return nil, fmt.Errorf("find comment: %v", err)
}

for _, action := range actions {
if action.CommentID > 0 {
action.Comment = commentMaps[action.CommentID]
}
}
return valuesComment(commentMaps), nil
}

// LoadComments loads actions' all comments
func (actions ActionList) LoadComments() ([]*Comment, error) {
return actions.loadComments(x)
}

func (actions ActionList) getCloudbrainIDs() []int64 {
cloudbrainIDs := make(map[int64]struct{}, 0)
for _, action := range actions {
if !action.IsCloudbrainAction() {
continue
}
cloudbrainId, _ := strconv.ParseInt(action.Content, 10, 64)
if _, ok := cloudbrainIDs[cloudbrainId]; !ok {
cloudbrainIDs[cloudbrainId] = struct{}{}
}
}
return keysInt64(cloudbrainIDs)
}

func (actions ActionList) getCloudbrainJobIDs() []string {
cloudbrainJobIDs := make(map[string]struct{}, 0)
for _, action := range actions {
if !action.IsCloudbrainAction() {
continue
}
if _, ok := cloudbrainJobIDs[action.Content]; !ok {
cloudbrainJobIDs[action.Content] = struct{}{}
}
}
return keysString(cloudbrainJobIDs)
}

func (actions ActionList) loadCloudbrains(e Engine) ([]*Cloudbrain, error) {
if len(actions) == 0 {
return nil, nil
}
cloudbrainIDs := actions.getCloudbrainIDs()
cloudbrainJobIDs := actions.getCloudbrainJobIDs()

cloudbrainMaps := make(map[int64]*Cloudbrain, len(cloudbrainIDs))
if len(cloudbrainIDs) == 0 {
return make([]*Cloudbrain, 0), nil
}
//由于各个类型的云脑任务在发布action的时候,content字段保存的ID含义不同,部分取的是ID,部分取的是jobId
//所以在查询action对应的cloudbrain对象时,以这两个字段做为条件查询
cond := builder.Or(builder.In("id", cloudbrainIDs)).Or(builder.In("job_id", cloudbrainJobIDs))
err := e.
Where(cond).Unscoped().
Find(&cloudbrainMaps)
if err != nil {
return nil, fmt.Errorf("find cloudbrain: %v", err)
}

cloudBrainJobIdMap := make(map[string]*Cloudbrain, len(cloudbrainIDs))
for _, v := range cloudbrainMaps {
cloudBrainJobIdMap[v.JobID] = v
}

for _, action := range actions {
if !action.IsCloudbrainAction() {
continue
}
cloudbrainId, _ := strconv.ParseInt(action.Content, 10, 64)
if cloudbrainId > 0 {
if c, ok := cloudbrainMaps[cloudbrainId]; ok {
if c.DisplayJobName == action.RefName || c.JobName == action.RefName {
action.Cloudbrain = c
continue
}

}
}
if c, ok := cloudBrainJobIdMap[action.Content]; ok {
if c.DisplayJobName == action.RefName || c.JobName == action.RefName {
action.Cloudbrain = c
continue
}

}
}
return valuesCloudbrain(cloudbrainMaps), nil
}

// LoadComments loads actions' all comments
func (actions ActionList) LoadCloudbrains() ([]*Comment, error) {
return actions.loadComments(x)
}

// loadAttributes loads all attributes // loadAttributes loads all attributes
func (actions ActionList) loadAttributes(e Engine) (err error) { func (actions ActionList) loadAttributes(e Engine) (err error) {
if _, err = actions.loadUsers(e); err != nil { if _, err = actions.loadUsers(e); err != nil {
@@ -96,3 +233,30 @@ func (actions ActionList) loadAttributes(e Engine) (err error) {
func (actions ActionList) LoadAttributes() error { func (actions ActionList) LoadAttributes() error {
return actions.loadAttributes(x) return actions.loadAttributes(x)
} }

// LoadAllAttributes loads all attributes of the actions
// compare with LoadAttributes() ,LoadAllAttributes() loads Comment and Cloudbrain attribute
func (actions ActionList) LoadAllAttributes() error {
return actions.loadAllAttributes(x)
}

// loadAllAttributes
func (actions ActionList) loadAllAttributes(e Engine) (err error) {
if _, err = actions.loadUsers(e); err != nil {
return
}

if _, err = actions.loadRepositories(e); err != nil {
return
}

if _, err = actions.loadComments(e); err != nil {
return
}

if _, err = actions.loadCloudbrains(e); err != nil {
return
}

return nil
}

+ 8
- 0
models/attachment.go View File

@@ -701,3 +701,11 @@ func Attachments(opts *AttachmentsOptions) ([]*AttachmentInfo, int64, error) {


return attachments, count, nil return attachments, count, nil
} }

func GetAllDatasetContributorByDatasetId(datasetId int64) ([]*User, error) {
r := make([]*User, 0)
if err := x.Select("distinct(public.user.*)").Table("attachment").Join("LEFT", "user", "public.user.ID = attachment.uploader_id").Where("attachment.dataset_id = ?", datasetId).Find(&r); err != nil {
return nil, err
}
return r, nil
}

+ 77
- 5
models/cloudbrain.go View File

@@ -200,6 +200,45 @@ type Cloudbrain struct {
Spec *Specification `xorm:"-"` Spec *Specification `xorm:"-"`
} }


type CloudbrainShow struct {
ID int64
JobID string
RepoFullName string
Type int
JobType string
DisplayJobName string
Duration string
ResourceSpec *Specification
ComputeResource string
AiCenter string
}

type CloudbrainShow4Action struct {
ID int64
JobID string
Type int
JobType string
DisplayJobName string
ComputeResource string
}

func (task *Cloudbrain) ToShow() *CloudbrainShow {
c := &CloudbrainShow{
ID: task.ID,
JobID: task.JobID,
JobType: task.JobType,
Type: task.Type,
DisplayJobName: task.DisplayJobName,
Duration: task.TrainJobDuration,
ResourceSpec: task.Spec,
ComputeResource: task.ComputeResource,
}
if task.Repo != nil {
c.RepoFullName = task.Repo.FullName()
}
return c
}

func (task *Cloudbrain) ComputeAndSetDuration() { func (task *Cloudbrain) ComputeAndSetDuration() {
var d int64 var d int64
if task.StartTime == 0 { if task.StartTime == 0 {
@@ -597,11 +636,23 @@ type ResourceSpecs struct {
} }


type ResourceSpec struct { type ResourceSpec struct {
Id int `json:"id"`
CpuNum int `json:"cpu"`
GpuNum int `json:"gpu"`
MemMiB int `json:"memMiB"`
ShareMemMiB int `json:"shareMemMiB"`
Id int `json:"id"`
CpuNum int `json:"cpu"`
GpuNum int `json:"gpu"`
MemMiB int `json:"memMiB"`
ShareMemMiB int `json:"shareMemMiB"`
UnitPrice int64 `json:"unitPrice"`
}

type FlavorInfos struct {
FlavorInfo []*FlavorInfo `json:"flavor_info"`
}

type FlavorInfo struct {
Id int `json:"id"`
Value string `json:"value"`
Desc string `json:"desc"`
UnitPrice int64 `json:"unitPrice"`
} }


type SpecialPools struct { type SpecialPools struct {
@@ -2221,6 +2272,27 @@ func CloudbrainAllStatic(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, er
return cloudbrains, count, nil return cloudbrains, count, nil
} }


func GetStartedCloudbrainTaskByUpdatedUnix(startTime, endTime time.Time) ([]Cloudbrain, error) {
r := make([]Cloudbrain, 0)
err := x.Where("updated_unix >= ? and updated_unix <= ? and start_time > 0", startTime.Unix(), endTime.Unix()).Unscoped().Find(&r)
if err != nil {
return nil, err
}
return r, nil
}

func GetCloudbrainByIds(ids []int64) ([]*Cloudbrain, error) {
if len(ids) == 0 {
return nil, nil
}
cloudbrains := make([]*Cloudbrain, 0)
err := x.In("id", ids).Unscoped().Find(&cloudbrains)
if err != nil {
return nil, err
}
return cloudbrains, nil
}

type DatasetInfo struct { type DatasetInfo struct {
DataLocalPath string DataLocalPath string
Name string Name string


+ 23
- 0
models/cloudbrain_spec.go View File

@@ -72,6 +72,8 @@ func NewCloudBrainSpec(cloudbrainId int64, s Specification) CloudbrainSpec {
} }
} }


var StatusChangeChan = make(chan *Cloudbrain, 50)

func InsertCloudbrainSpec(c CloudbrainSpec) (int64, error) { func InsertCloudbrainSpec(c CloudbrainSpec) (int64, error) {
return x.Insert(&c) return x.Insert(&c)
} }
@@ -107,3 +109,24 @@ func CountNoSpecHistoricTask() (int64, error) {
} }
return n, nil return n, nil
} }

// GetResourceSpecMapByCloudbrainIDs
func GetResourceSpecMapByCloudbrainIDs(ids []int64) (map[int64]*Specification, error) {
specs := make([]*CloudbrainSpec, 0)
if err := x.In("cloudbrain_id", ids).Find(&specs); err != nil {
return nil, err
}
r := make(map[int64]*Specification, len(ids))
for _, s := range specs {
r[s.CloudbrainID] = s.ConvertToSpecification()
}
return r, nil
}

func GetCloudbrainTaskUnitPrice(cloudbrainId int64) (int, error) {
s, err := GetCloudbrainSpecByID(cloudbrainId)
if err != nil {
return 0, err
}
return s.UnitPrice, nil
}

+ 24
- 0
models/error.go View File

@@ -2012,3 +2012,27 @@ func IsErrTagNotExist(err error) bool {
_, ok := err.(ErrTagNotExist) _, ok := err.(ErrTagNotExist)
return ok return ok
} }

type ErrRecordNotExist struct {
}

func IsErrRecordNotExist(err error) bool {
_, ok := err.(ErrRecordNotExist)
return ok
}

func (err ErrRecordNotExist) Error() string {
return fmt.Sprintf("record not exist in database")
}

type ErrInsufficientPointsBalance struct {
}

func IsErrInsufficientPointsBalance(err error) bool {
_, ok := err.(ErrInsufficientPointsBalance)
return ok
}

func (err ErrInsufficientPointsBalance) Error() string {
return fmt.Sprintf("Insufficient points balance")
}

+ 22
- 0
models/helper.go View File

@@ -11,6 +11,13 @@ func keysInt64(m map[int64]struct{}) []int64 {
} }
return keys return keys
} }
func keysString(m map[string]struct{}) []string {
var keys = make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
return keys
}


func valuesRepository(m map[int64]*Repository) []*Repository { func valuesRepository(m map[int64]*Repository) []*Repository {
var values = make([]*Repository, 0, len(m)) var values = make([]*Repository, 0, len(m))
@@ -27,3 +34,18 @@ func valuesUser(m map[int64]*User) []*User {
} }
return values return values
} }

func valuesComment(m map[int64]*Comment) []*Comment {
var values = make([]*Comment, 0, len(m))
for _, v := range m {
values = append(values, v)
}
return values
}
func valuesCloudbrain(m map[int64]*Cloudbrain) []*Cloudbrain {
var values = make([]*Cloudbrain, 0, len(m))
for _, v := range m {
values = append(values, v)
}
return values
}

+ 184
- 0
models/limit_config.go View File

@@ -0,0 +1,184 @@
package models

import (
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
)

type LimitType string

const (
LimitTypeTask LimitType = "TASK"
LimitTypeRewardPoint LimitType = "REWARD_POINT"
)

func (l LimitType) Name() string {
switch l {
case LimitTypeTask:
return "TASK"
case LimitTypeRewardPoint:
return "REWARD_POINT"
default:
return ""
}
}

type LimitScope string

const (
LimitScopeAllUsers LimitScope = "ALL_USERS"
LimitScopeSingleUser LimitScope = "SINGLE_USER"
)

func (l LimitScope) Name() string {
switch l {
case LimitScopeAllUsers:
return "ALL_USERS"
case LimitScopeSingleUser:
return "SINGLE_USER"
default:
return ""
}
}

type LimiterRejectPolicy string

const (
JustReject LimiterRejectPolicy = "JUST_REJECT"
PermittedOnce LimiterRejectPolicy = "PERMITTED_ONCE"
FillUp LimiterRejectPolicy = "FillUp"
)

type LimitConfig struct {
ID int64 `xorm:"pk autoincr"`
Title string
RefreshRate string `xorm:"NOT NULL"`
Scope string `xorm:"NOT NULL"`
LimitNum int64 `xorm:"NOT NULL"`
LimitCode string
LimitType string `xorm:"NOT NULL"`
RelatedId int64 `xorm:"INDEX"`
CreatorId int64 `xorm:"NOT NULL"`
CreatorName string
DeleterId int64
DeleterName string
CreatedUnix timeutil.TimeStamp `xorm:"created"`
DeletedAt timeutil.TimeStamp `xorm:"deleted"`
}

type LimitConfigQueryOpts struct {
RefreshRate string
Scope LimitScope
LimitCode string
LimitType LimitType
}

type LimitConfigVO struct {
ID int64
Title string
RefreshRate string
Scope string
LimitNum int64
LimitCode string
LimitType string
Creator string
CreatedUnix timeutil.TimeStamp
}

func (l *LimitConfig) ToLimitConfigVO() *LimitConfigVO {
return &LimitConfigVO{
ID: l.ID,
Title: l.Title,
RefreshRate: l.RefreshRate,
Scope: l.Scope,
LimitNum: l.LimitNum,
LimitCode: l.LimitCode,
LimitType: l.LimitType,
Creator: l.CreatorName,
CreatedUnix: l.CreatedUnix,
}
}

func GetLimitConfigByLimitType(limitType LimitType) ([]LimitConfig, error) {
r := make([]LimitConfig, 0)
err := x.Where(" limit_type = ?", limitType.Name()).Find(&r)
if err != nil {
return nil, err
} else if len(r) == 0 {
return nil, ErrRecordNotExist{}
}
return r, nil
}

func GetLimitersByRelatedIdWithDeleted(limitType LimitType) ([]LimitConfig, error) {
r := make([]LimitConfig, 0)
err := x.Unscoped().Where(" = ?", limitType.Name()).Find(&r)
if err != nil {
return nil, err
} else if len(r) == 0 {
return nil, ErrRecordNotExist{}
}
return r, nil
}

func AddLimitConfig(l *LimitConfig) error {
sess := x.NewSession()
defer sess.Close()

//delete old limit config
cond := builder.NewCond()
cond = cond.And(builder.Eq{"limit_type": l.LimitType})
cond = cond.And(builder.Eq{"scope": l.Scope})
if l.LimitCode == "" {
subCond := builder.NewCond()
subCond = subCond.Or(builder.IsNull{"limit_code"})
subCond = subCond.Or(builder.Eq{"limit_code": ""})
cond = cond.And(subCond)
} else {
cond = cond.And(builder.Eq{"limit_code": l.LimitCode})
}
_, err := sess.Where(cond).Delete(&LimitConfig{})
if err != nil {
sess.Rollback()
return err
}

//add new config
_, err = sess.Insert(l)
if err != nil {
sess.Rollback()
return err
}

sess.Commit()
return nil
}

func DeleteLimitConfig(config LimitConfig, deleterId int64, deleterName string) error {
sess := x.NewSession()
defer sess.Close()

_, err := x.ID(config.ID).Update(&LimitConfig{DeleterName: deleterName, DeleterId: deleterId})
if err != nil {
sess.Rollback()
return err
}
_, err = x.ID(config.ID).Delete(&LimitConfig{})
if err != nil {
sess.Rollback()
return err
}
sess.Commit()
return nil
}

func GetLimitConfigById(id int64) (*LimitConfig, error) {
r := &LimitConfig{}
isOk, err := x.ID(id).Get(r)
if err != nil {
return nil, err
} else if !isOk {
return nil, nil
}
return r, nil
}

+ 8
- 0
models/models.go View File

@@ -144,6 +144,14 @@ func init() {
new(WechatBindLog), new(WechatBindLog),
new(OrgStatistic), new(OrgStatistic),
new(SearchRecord), new(SearchRecord),
new(TaskConfig),
new(TaskAccomplishLog),
new(RewardOperateRecord),
new(LimitConfig),
new(RewardPeriodicTask),
new(PointAccountLog),
new(PointAccount),
new(RewardAdminLog),
new(AiModelConvert), new(AiModelConvert),
new(ResourceQueue), new(ResourceQueue),
new(ResourceSpecification), new(ResourceSpecification),


+ 142
- 0
models/point_account.go View File

@@ -0,0 +1,142 @@
package models

import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
)

type PointAccountStatus int

// Possible PointAccountStatus types.
const (
PointAccountNormal int = iota + 1 // 1
PointAccountFreeze // 2
PointAccountDeleted // 3
)

type PointAccount struct {
ID int64 `xorm:"pk autoincr"`
AccountCode string `xorm:"INDEX NOT NULL"`
Balance int64 `xorm:"NOT NULL DEFAULT 0"`
TotalEarned int64 `xorm:"NOT NULL DEFAULT 0"`
TotalConsumed int64 `xorm:"NOT NULL DEFAULT 0"`
UserId int64 `xorm:"INDEX NOT NULL"`
Status int `xorm:"NOT NULL"`
Version int64 `xorm:"NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}

func (account *PointAccount) Increase(amount int64, sourceId string) error {
sess := x.NewSession()
defer sess.Close()
sql := "update point_account set balance = balance + ?,total_earned = total_earned + ? ,version = version + 1 where account_code = ? "
_, err := sess.Exec(sql, amount, amount, account.AccountCode)
if err != nil {
sess.Rollback()
return err
}
accountLog := &PointAccountLog{
AccountCode: account.AccountCode,
UserId: account.UserId,
Type: IncreaseAccountBalance,
SourceId: sourceId,
PointsAmount: amount,
BalanceBefore: account.Balance,
BalanceAfter: account.Balance + amount,
AccountVersion: account.Version,
}
_, err = sess.Insert(accountLog)
if err != nil {
sess.Rollback()
return err
}
sess.Commit()
return nil
}

func (account *PointAccount) Decrease(amount int64, sourceId string) error {
sess := x.NewSession()
defer sess.Close()
sql := "update point_account set balance = balance - ?,total_consumed = total_consumed + ? ,version = version + 1 where account_code = ? "
_, err := sess.Exec(sql, amount, amount, account.AccountCode)
if err != nil {
sess.Rollback()
return err
}
accountLog := &PointAccountLog{
AccountCode: account.AccountCode,
UserId: account.UserId,
Type: DecreaseAccountBalance,
SourceId: sourceId,
PointsAmount: amount,
BalanceBefore: account.Balance,
BalanceAfter: account.Balance - amount,
AccountVersion: account.Version,
}
_, err = sess.Insert(accountLog)
if err != nil {
sess.Rollback()
return err
}
sess.Commit()
return nil
}

func GetAccountByUserId(userId int64) (*PointAccount, error) {
p := &PointAccount{}
has, err := x.Where("user_id = ?", userId).Get(p)
if err != nil {
return nil, err
}
if !has {
return nil, ErrRecordNotExist{}
}
return p, nil
}

func InsertAccount(tl *PointAccount) (int64, error) {
return x.Insert(tl)
}

type SearchPointAccountOpts struct {
ListOptions
Keyword string
}

type SearchPointAccountResponse struct {
Records []*UserPointAccount
PageSize int
Page int
Total int64
}

type UserPointAccount struct {
UserId int64
UserName string
Email string
Balance int64
TotalEarned int64
TotalConsumed int64
}

func (UserPointAccount) TableName() string {
return "user"
}

func GetPointAccountMapByUserIds(userIds []int64) (map[int64]*PointAccount, error) {
if len(userIds) == 0 {
return make(map[int64]*PointAccount, 0), nil
}
accounts := make([]*PointAccount, 0)
err := x.In("user_id", userIds).Find(&accounts)
if err != nil {
log.Error("GetPointAccountMapByUserIds error.%v", err)
return nil, err
}
accountMap := make(map[int64]*PointAccount, 0)
for _, v := range accounts {
accountMap[v.UserId] = v
}
return accountMap, nil
}

+ 21
- 0
models/point_account_log.go View File

@@ -0,0 +1,21 @@
package models

import "code.gitea.io/gitea/modules/timeutil"

const (
IncreaseAccountBalance = "increase"
DecreaseAccountBalance = "decrease"
)

type PointAccountLog struct {
ID int64 `xorm:"pk autoincr"`
AccountCode string `xorm:"INDEX NOT NULL"`
UserId int64 `xorm:"INDEX NOT NULL"`
Type string `xorm:"NOT NULL"`
SourceId string `xorm:"INDEX NOT NULL"`
PointsAmount int64 `xorm:"NOT NULL"`
BalanceBefore int64 `xorm:"NOT NULL"`
BalanceAfter int64 `xorm:"NOT NULL"`
AccountVersion int64 `xorm:"NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}

+ 6
- 0
models/repo.go View File

@@ -237,6 +237,12 @@ type Repository struct {
LowerAlias string `xorm:"INDEX"` LowerAlias string `xorm:"INDEX"`
} }


type RepositoryShow struct {
Name string
RepoType RepoType
Alias string
}

// SanitizedOriginalURL returns a sanitized OriginalURL // SanitizedOriginalURL returns a sanitized OriginalURL
func (repo *Repository) SanitizedOriginalURL() string { func (repo *Repository) SanitizedOriginalURL() string {
if repo.OriginalURL == "" { if repo.OriginalURL == "" {


+ 10
- 2
models/repo_watch.go View File

@@ -25,6 +25,7 @@ const (
) )


var ActionChan = make(chan *Action, 200) var ActionChan = make(chan *Action, 200)
var ActionChan4Task = make(chan Action, 200)


// Watch is connection request for receiving repository notification. // Watch is connection request for receiving repository notification.
type Watch struct { type Watch struct {
@@ -199,6 +200,14 @@ func notifyWatchers(e Engine, actions ...*Action) error {
if _, err = e.InsertOne(act); err != nil { if _, err = e.InsertOne(act); err != nil {
return fmt.Errorf("insert new actioner: %v", err) return fmt.Errorf("insert new actioner: %v", err)
} }
// After InsertOne(act),the act has ID
// Send the act to task chan
ActionChan4Task <- *act

// If it has nothing to do with repo, return directly
if act.Repo == nil && act.RepoID == 0 {
return nil
}


if repoChanged { if repoChanged {
act.loadRepo() act.loadRepo()
@@ -279,7 +288,6 @@ func notifyWatchers(e Engine, actions ...*Action) error {


// NotifyWatchers creates batch of actions for every watcher. // NotifyWatchers creates batch of actions for every watcher.
func NotifyWatchers(actions ...*Action) error { func NotifyWatchers(actions ...*Action) error {

error := notifyWatchers(x, actions...) error := notifyWatchers(x, actions...)
producer(actions...) producer(actions...)
return error return error
@@ -287,7 +295,7 @@ func NotifyWatchers(actions ...*Action) error {


func producer(actions ...*Action) { func producer(actions ...*Action) {
for _, action := range actions { for _, action := range actions {
if !action.IsPrivate{
if !action.IsPrivate {
ActionChan <- action ActionChan <- action
} }
} }


+ 79
- 0
models/reward_admin_log.go View File

@@ -0,0 +1,79 @@
package models

import (
"code.gitea.io/gitea/modules/timeutil"
)

const (
RewardAdminLogProcessing = 1
RewardAdminLogSuccess = 2
RewardAdminLogFailed = 3
)

type RewardAdminLog struct {
ID int64 `xorm:"pk autoincr"`
LogId string `xorm:"INDEX NOT NULL"`
Amount int64 `xorm:"NOT NULL"`
RewardType string
Remark string
Status int
TargetUserId int64 `xorm:"INDEX NOT NULL"`
CreatorId int64 `xorm:"NOT NULL"`
CreatorName string
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}

func (r *RewardAdminLog) ToShow() *RewardAdminLogShow {
return &RewardAdminLogShow{
CreatorName: r.CreatorName,
}
}

type RewardAdminLogShow struct {
CreatorName string
}

type AdminLogAndUser struct {
AdminRewardAdminLog RewardAdminLog `xorm:"extends"`
User User `xorm:"extends"`
}

func getRewardAdminLog(ra *RewardAdminLog) (*RewardAdminLog, error) {
has, err := x.Get(ra)
if err != nil {
return nil, err
} else if !has {
return nil, ErrRecordNotExist{}
}
return ra, nil
}

func InsertRewardAdminLog(ra *RewardAdminLog) (int64, error) {
return x.Insert(ra)
}

func UpdateRewardAdminLogStatus(logId string, oldStatus, newStatus int) error {
_, err := x.Where("log_id = ? and status = ?", logId, oldStatus).Update(&RewardAdminLog{Status: newStatus})
if err != nil {
return err
}
return nil
}

func GetRewardAdminLogByLogIds(logIds []string) ([]*RewardAdminLog, error) {
if len(logIds) == 0 {
return nil, nil
}
adminLogs := make([]*AdminLogAndUser, 0)
err := x.Table("reward_admin_log").Join("LEFT", "user", "reward_admin_log.creator_id = public.user.id").In("reward_admin_log.log_id", logIds).Find(&adminLogs)
if err != nil {
return nil, err
}
r := make([]*RewardAdminLog, len(adminLogs))
for i, v := range adminLogs {
temp := &v.AdminRewardAdminLog
temp.CreatorName = v.User.Name
r[i] = temp
}
return r, nil
}

+ 459
- 0
models/reward_operate_record.go View File

@@ -0,0 +1,459 @@
package models

import (
"code.gitea.io/gitea/modules/timeutil"
"fmt"
"strconv"
"strings"
"xorm.io/builder"
)

type SourceType string

const (
SourceTypeAccomplishTask SourceType = "ACCOMPLISH_TASK"
SourceTypeAdminOperate SourceType = "ADMIN_OPERATE"
SourceTypeRunCloudbrainTask SourceType = "RUN_CLOUDBRAIN_TASK"
)

func (r SourceType) Name() string {
switch r {
case SourceTypeAccomplishTask:
return "ACCOMPLISH_TASK"
case SourceTypeAdminOperate:
return "ADMIN_OPERATE"
case SourceTypeRunCloudbrainTask:
return "RUN_CLOUDBRAIN_TASK"
default:
return ""
}
}

type RewardType string

const (
RewardTypePoint RewardType = "POINT"
)

func (r RewardType) Name() string {
switch r {
case RewardTypePoint:
return "POINT"
default:
return ""
}
}
func (r RewardType) Show() string {
switch r {
case RewardTypePoint:
return "积分"
default:
return ""
}
}
func GetRewardTypeInstance(s string) RewardType {
switch s {
case RewardTypePoint.Name():
return RewardTypePoint
default:
return ""
}
}

type RewardOperateType string

func (r RewardOperateType) Name() string {
switch r {
case OperateTypeIncrease:
return "INCREASE"
case OperateTypeDecrease:
return "DECREASE"
default:
return ""
}
}
func (r RewardOperateType) Show() string {
switch r {
case OperateTypeIncrease:
return "奖励"
case OperateTypeDecrease:
return "扣减"
default:
return ""
}
}

func GetRewardOperateTypeInstance(s string) RewardOperateType {
switch s {
case OperateTypeIncrease.Name():
return OperateTypeIncrease
case OperateTypeDecrease.Name():
return OperateTypeDecrease
default:
return ""
}
}

const (
OperateTypeIncrease RewardOperateType = "INCREASE"
OperateTypeDecrease RewardOperateType = "DECREASE"
OperateTypeNull RewardOperateType = "NIL"
)

const (
OperateStatusOperating = "OPERATING"
OperateStatusSucceeded = "SUCCEEDED"
OperateStatusFailed = "FAILED"
)

const Semicolon = ";"

type RewardOperateOrderBy string

const (
RewardOrderByIDDesc RewardOperateOrderBy = "reward_operate_record.id desc"
)

type RewardRecordList []*RewardOperateRecord
type RewardRecordShowList []*RewardOperateRecordShow

func (l RewardRecordShowList) loadAttribute(isAdmin bool) {
l.loadAction()
l.loadCloudbrain()
if isAdmin {
l.loadAdminLog()
}
}

func (l RewardRecordShowList) loadAction() error {
if len(l) == 0 {
return nil
}
actionIds := make([]int64, 0)
actionIdMap := make(map[int64]*RewardOperateRecordShow, 0)
for _, r := range l {
if r.SourceType != SourceTypeAccomplishTask.Name() {
continue
}
i, _ := strconv.ParseInt(r.SourceId, 10, 64)
actionIds = append(actionIds, i)
actionIdMap[i] = r
}
actions, err := GetActionByIds(actionIds)
if err != nil {
return err
}
for _, v := range actions {
actionIdMap[v.ID].Action = v.ToShow()
}
return nil
}

func (l RewardRecordShowList) loadCloudbrain() error {
if len(l) == 0 {
return nil
}
cloudbrainIds := make([]int64, 0)
cloudbrainMap := make(map[int64]*RewardOperateRecordShow, 0)
for _, r := range l {
if r.SourceType != SourceTypeRunCloudbrainTask.Name() {
continue
}
i, _ := strconv.ParseInt(r.SourceId, 10, 64)
cloudbrainIds = append(cloudbrainIds, i)
cloudbrainMap[i] = r
}
cloudbrains, err := GetCloudbrainByIds(cloudbrainIds)
if err != nil {
return err
}
var repoIds []int64
var taskIds []int64
for _, task := range cloudbrains {
repoIds = append(repoIds, task.RepoID)
taskIds = append(taskIds, task.ID)
}
repositoryMap, err := GetRepositoriesMapByIDs(repoIds)
specMap, err := GetResourceSpecMapByCloudbrainIDs(taskIds)
if err != nil {
return err
}
for _, v := range cloudbrains {
v.Repo = repositoryMap[v.RepoID]
v.Spec = specMap[v.ID]
cloudbrainMap[v.ID].Cloudbrain = v.ToShow()
}

return nil

}

func (l RewardRecordShowList) loadAdminLog() error {
if len(l) == 0 {
return nil
}
logIds := make([]string, 0)
logMap := make(map[string]*RewardOperateRecordShow, 0)
for _, r := range l {
if r.SourceType != SourceTypeAdminOperate.Name() {
continue
}
logIds = append(logIds, r.SourceId)
logMap[r.SourceId] = r
}
adminLogs, err := GetRewardAdminLogByLogIds(logIds)
if err != nil {
return err
}
for _, v := range adminLogs {
logMap[v.LogId].AdminLog = v.ToShow()
}

return nil

}

type RewardOperateRecord struct {
ID int64 `xorm:"pk autoincr"`
SerialNo string `xorm:"INDEX NOT NULL"`
UserId int64 `xorm:"INDEX NOT NULL"`
Amount int64 `xorm:"NOT NULL"`
LossAmount int64
Title string
RewardType string `xorm:"NOT NULL"`
SourceType string `xorm:"NOT NULL"`
SourceId string `xorm:"INDEX NOT NULL"`
SourceTemplateId string
RequestId string `xorm:"INDEX NOT NULL"`
OperateType string `xorm:"NOT NULL"`
Status string `xorm:"NOT NULL"`
Remark string
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
LastOperateUnix timeutil.TimeStamp `xorm:"INDEX"`
}

type AdminRewardOperateReq struct {
TargetUserId int64 `binding:"Required"`
OperateType RewardOperateType `binding:"Required"`
Amount int64 `binding:"Required;Range(1,100000)"`
Remark string
RewardType RewardType
}

type RewardOperateRecordShow struct {
SerialNo string
Status string
OperateType string
SourceId string
Amount int64
LossAmount int64
BalanceAfter int64
Remark string
SourceType string
UserName string
LastOperateDate timeutil.TimeStamp
UnitPrice int64
SuccessCount int
Action *ActionShow
Cloudbrain *CloudbrainShow
AdminLog *RewardAdminLogShow
}

func getPointOperateRecord(tl *RewardOperateRecord) (*RewardOperateRecord, error) {
has, err := x.Get(tl)
if err != nil {
return nil, err
} else if !has {
return nil, ErrRecordNotExist{}
}
return tl, nil
}

func GetPointOperateRecordBySourceTypeAndRequestId(sourceType, requestId, operateType string) (*RewardOperateRecord, error) {
t := &RewardOperateRecord{
SourceType: sourceType,
RequestId: requestId,
OperateType: operateType,
}
return getPointOperateRecord(t)
}

func GetPointOperateRecordBySerialNo(serialNo string) (*RewardOperateRecord, error) {
t := &RewardOperateRecord{
SerialNo: serialNo,
}
return getPointOperateRecord(t)
}

func InsertRewardOperateRecord(tl *RewardOperateRecord) (int64, error) {
return x.Insert(tl)
}

func UpdateRewardRecordToFinalStatus(sourceType, requestId, newStatus string) (int64, error) {
r := &RewardOperateRecord{
Status: newStatus,
LastOperateUnix: timeutil.TimeStampNow(),
}
return x.Cols("status", "last_operate_unix").Where("source_type=? and request_id=? and status=?", sourceType, requestId, OperateStatusOperating).Update(r)
}

func SumRewardAmountInTaskPeriod(rewardType string, sourceType string, userId int64, period *PeriodResult) (int64, error) {
var cond = builder.NewCond()
if period != nil {
cond = cond.And(builder.Gte{"created_unix": period.StartTime.Unix()})
cond = cond.And(builder.Lt{"created_unix": period.EndTime.Unix()})
}
if sourceType != "" {
cond = cond.And(builder.Eq{"source_type": sourceType})
}
cond = cond.And(builder.Eq{"reward_type": rewardType})
cond = cond.And(builder.Eq{"user_id": userId})
return x.Where(cond).SumInt(&RewardOperateRecord{}, "amount")
}

type RewardOperateContext struct {
SourceType SourceType
SourceId string
SourceTemplateId string
Title string
Remark string
Reward Reward
TargetUserId int64
RequestId string
OperateType RewardOperateType
RejectPolicy LimiterRejectPolicy
PermittedNegative bool
LossAmount int64
}

type Reward struct {
Amount int64
Type RewardType
}

type UserRewardOperationRedis struct {
UserId int64
Amount int64
RewardType RewardType
OperateType RewardOperateType
}

type UserRewardOperation struct {
UserId int64
Msg string
}

func AppendRemark(remark, appendStr string) string {
return strings.TrimPrefix(remark+Semicolon+appendStr, Semicolon)
}

type RewardRecordListOpts struct {
ListOptions
UserId int64
UserName string
OperateType RewardOperateType
RewardType RewardType
SourceType string
ActionType int
SerialNo string
OrderBy RewardOperateOrderBy
IsAdmin bool
Status string
}

func (opts *RewardRecordListOpts) toCond() builder.Cond {
if opts.Page <= 0 {
opts.Page = 1
}

if len(opts.OrderBy) == 0 {
opts.OrderBy = RewardOrderByIDDesc
}

cond := builder.NewCond()
if opts.UserId > 0 {
cond = cond.And(builder.Eq{"reward_operate_record.user_id": opts.UserId})
}
if opts.OperateType != OperateTypeNull {
cond = cond.And(builder.Eq{"reward_operate_record.operate_type": opts.OperateType.Name()})
}
if opts.SourceType != "" {
cond = cond.And(builder.Eq{"reward_operate_record.source_type": opts.SourceType})
}
if opts.ActionType > 0 {
cond = cond.And(builder.Eq{"reward_operate_record.source_template_id": fmt.Sprint(opts.ActionType)})
}
if opts.SerialNo != "" {
cond = cond.And(builder.Like{"reward_operate_record.serial_no", opts.SerialNo})
}
if opts.Status != "" {
cond = cond.And(builder.Like{"reward_operate_record.status", opts.Status})
}

cond = cond.And(builder.Eq{"reward_operate_record.reward_type": opts.RewardType.Name()})
cond = cond.And(builder.Gt{"reward_operate_record.amount": 0})
return cond
}

type TestTT struct {
SerialNo string
UserId int64
Amount int64
UserName string
}

func GetRewardRecordShowList(opts *RewardRecordListOpts) (RewardRecordShowList, int64, error) {
cond := opts.toCond()
count, err := x.Where(cond).Count(&RewardOperateRecord{})
if err != nil {
return nil, 0, err
}
r := make([]*RewardOperateRecordShow, 0)
err = x.Table("reward_operate_record").Cols("reward_operate_record.source_id", "reward_operate_record.serial_no",
"reward_operate_record.status", "reward_operate_record.operate_type", "reward_operate_record.amount",
"reward_operate_record.loss_amount", "reward_operate_record.remark", "reward_operate_record.source_type",
"reward_operate_record.last_operate_unix as last_operate_date").
Where(cond).Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).OrderBy(string(opts.OrderBy)).Find(&r)

if err != nil {
return nil, 0, err
}
RewardRecordShowList(r).loadAttribute(false)
return r, count, nil
}

func GetAdminRewardRecordShowList(opts *RewardRecordListOpts) (RewardRecordShowList, int64, error) {
cond := opts.toCond()
count, err := x.Where(cond).Count(&RewardOperateRecord{})
if err != nil {
return nil, 0, err
}
r := make([]*RewardOperateRecordShow, 0)
switch opts.OperateType {
case OperateTypeIncrease:
err = x.Table("reward_operate_record").Cols("reward_operate_record.source_id", "reward_operate_record.serial_no",
"reward_operate_record.status", "reward_operate_record.operate_type", "reward_operate_record.amount",
"reward_operate_record.loss_amount", "reward_operate_record.remark", "reward_operate_record.source_type",
"reward_operate_record.last_operate_unix as last_operate_date", "public.user.name as user_name",
"point_account_log.balance_after").
Join("LEFT", "public.user", "reward_operate_record.user_id = public.user.id").
Join("LEFT", "point_account_log", " reward_operate_record.serial_no = point_account_log.source_id").
Where(cond).Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).OrderBy(string(opts.OrderBy)).Find(&r)
case OperateTypeDecrease:
err = x.Table("reward_operate_record").Cols("reward_operate_record.source_id", "reward_operate_record.serial_no",
"reward_operate_record.status", "reward_operate_record.operate_type", "reward_operate_record.amount",
"reward_operate_record.loss_amount", "reward_operate_record.remark", "reward_operate_record.source_type",
"reward_operate_record.last_operate_unix as last_operate_date", "public.user.name as user_name",
"reward_periodic_task.amount as unit_price", "reward_periodic_task.success_count").
Join("LEFT", "public.user", "reward_operate_record.user_id = public.user.id").
Join("LEFT", "reward_periodic_task", "reward_operate_record.serial_no = reward_periodic_task.operate_serial_no").
Where(cond).Limit(opts.PageSize, (opts.Page-1)*opts.PageSize).OrderBy(string(opts.OrderBy)).Find(&r)
}

if err != nil {
return nil, 0, err
}
RewardRecordShowList(r).loadAttribute(true)
return r, count, nil
}

+ 115
- 0
models/reward_periodic_task.go View File

@@ -0,0 +1,115 @@
package models

import (
"code.gitea.io/gitea/modules/timeutil"
"time"
)

type PeriodicTaskStatus int

const (
PeriodicTaskStatusRunning = iota + 1 // 1
PeriodicTaskStatusFinished // 2
)

type PeriodType string

const (
PeriodType30MinutesFree1HourCost PeriodType = "30MF1HC"
)

func (r PeriodType) Name() string {
switch r {
case PeriodType30MinutesFree1HourCost:
return "30MF1HC"
default:
return ""
}
}

type RewardPeriodicTask struct {
ID int64 `xorm:"pk autoincr"`
OperateSerialNo string `xorm:"INDEX NOT NULL"`
DelaySeconds int64
IntervalSeconds int64
Amount int64 `xorm:"NOT NULL"`
NextExecuteTime timeutil.TimeStamp `xorm:"INDEX NOT NULL"`
SuccessCount int `xorm:"NOT NULL default 0"`
Status int `xorm:"NOT NULL"`
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
FinishedUnix timeutil.TimeStamp `xorm:"INDEX"`
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
}

type StartPeriodicTaskOpts struct {
SourceType SourceType
SourceId string
Remark string
Title string
TargetUserId int64
RequestId string
OperateType RewardOperateType
Delay time.Duration
Interval time.Duration
UnitAmount int
RewardType RewardType
StartTime time.Time
}

func InsertPeriodicTask(tl *RewardPeriodicTask) (int64, error) {
return x.Insert(tl)
}

func GetRunningRewardTask(now time.Time) ([]RewardPeriodicTask, error) {
r := make([]RewardPeriodicTask, 0)
err := x.Where("next_execute_time <= ? and status = ?", now.Unix(), PeriodicTaskStatusRunning).Find(&r)
if err != nil {
return nil, err
}
return r, err
}

func IncrRewardTaskSuccessCount(t RewardPeriodicTask, count int64, nextTime timeutil.TimeStamp) error {
sess := x.NewSession()
defer sess.Close()
_, err := sess.Exec("update reward_periodic_task set success_count = success_count + ? , next_execute_time = ?, updated_unix = ? where id = ?", count, nextTime, timeutil.TimeStampNow(), t.ID)
if err != nil {
sess.Rollback()
return err
}
_, err = sess.Exec("update reward_operate_record set amount = amount + ? ,updated_unix = ? ,last_operate_unix = ? where serial_no = ?", t.Amount, timeutil.TimeStampNow(), timeutil.TimeStampNow(), t.OperateSerialNo)
if err != nil {
sess.Rollback()
return err
}
sess.Commit()
return nil
}

func GetPeriodicTaskBySourceIdAndType(sourceType SourceType, sourceId string, operateType RewardOperateType) (*RewardPeriodicTask, error) {
r := RewardPeriodicTask{}
_, err := x.SQL("select rpt.* from reward_periodic_task rpt "+
"inner join reward_operate_record ror on rpt.operate_serial_no = ror.serial_no"+
" where ror.source_type = ? and ror.source_id = ? and ror.operate_type = ? ", sourceType.Name(), sourceId, operateType.Name()).Get(&r)
if err != nil {
return nil, err
}
return &r, nil
}

func StopPeriodicTask(taskId int64, operateSerialNo string, stopTime time.Time) error {
sess := x.NewSession()
defer sess.Close()
_, err := sess.Where("id = ? and status = ?", taskId, PeriodicTaskStatusRunning).Update(&RewardPeriodicTask{Status: PeriodicTaskStatusFinished, FinishedUnix: timeutil.TimeStamp(stopTime.Unix())})
if err != nil {
sess.Rollback()
return err
}
_, err = sess.Where("serial_no = ? and status = ?", operateSerialNo, OperateStatusOperating).Update(&RewardOperateRecord{Status: OperateStatusSucceeded})
if err != nil {
sess.Rollback()
return err
}
sess.Commit()
return nil
}

+ 44
- 0
models/task_accomplish_log.go View File

@@ -0,0 +1,44 @@
package models

import (
"code.gitea.io/gitea/modules/timeutil"
"time"
)

type TaskAccomplishLog struct {
ID int64 `xorm:"pk autoincr"`
ConfigId int64 `xorm:"NOT NULL"`
TaskCode string `xorm:"NOT NULL"`
UserId int64 `xorm:"INDEX NOT NULL"`
ActionId int64
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
}

type PeriodResult struct {
StartTime time.Time
EndTime time.Time
LeftTime time.Duration
}

func getTaskAccomplishLog(tl *TaskAccomplishLog) (*TaskAccomplishLog, error) {
has, err := x.Get(tl)
if err != nil {
return nil, err
} else if !has {
return nil, ErrRecordNotExist{}
}
return tl, nil
}

func CountTaskAccomplishLogInTaskPeriod(taskCode string, userId int64, period *PeriodResult) (int64, error) {
if period == nil {
return x.Where("task_code = ? and user_id = ?", taskCode, userId).Count(&TaskAccomplishLog{})
} else {
return x.Where("task_code = ? and user_id = ? and created_unix >= ? and created_unix < ? ", taskCode, userId, period.StartTime.Unix(), period.EndTime.Unix()).Count(&TaskAccomplishLog{})
}

}

func InsertTaskAccomplishLog(tl *TaskAccomplishLog) (int64, error) {
return x.Insert(tl)
}

+ 302
- 0
models/task_config.go View File

@@ -0,0 +1,302 @@
package models

import (
"code.gitea.io/gitea/modules/timeutil"
"fmt"
"xorm.io/builder"
)

const (
PeriodNotCycle = "NOT_CYCLE"
PeriodDaily = "DAILY"
)

//PointTaskConfig Only add and delete are allowed, edit is not allowed
//so if you want to edit config for some task code,please delete first and add new one
type TaskConfig struct {
ID int64 `xorm:"pk autoincr"`
TaskCode string `xorm:"NOT NULL"`
Title string
AwardType string `xorm:"NOT NULL"`
AwardAmount int64 `xorm:"NOT NULL"`
CreatorId int64 `xorm:"NOT NULL"`
CreatorName string
CreatedUnix timeutil.TimeStamp `xorm:"created"`
DeletedAt timeutil.TimeStamp `xorm:"deleted"`
DeleterId int64
DeleterName string
}

type TaskConfigWithLimit struct {
ID int64
TaskCode string
Title string
AwardType string
AwardAmount int64
Creator string
IsDeleted bool
CreatedUnix timeutil.TimeStamp
DeleteAt timeutil.TimeStamp
Limiters []*LimitConfigVO
}

type TaskConfigWithLimitResponse struct {
Records []*TaskConfigWithSingleLimit
Total int64
PageSize int
Page int
}

type TaskConfigWithSingleLimit struct {
ID int64
TaskCode string
AwardType string
AwardAmount int64
Creator string
IsDeleted bool
CreatedUnix timeutil.TimeStamp
DeleteAt timeutil.TimeStamp
RefreshRate string
LimitNum int64
}

type TaskAndLimiterConfig struct {
TaskConfig TaskConfig `xorm:"extends"`
LimitConfig LimitConfig `xorm:"extends"`
}

func (TaskAndLimiterConfig) TableName() string {
return "task_config"
}

type BatchLimitConfigVO struct {
ConfigList []TaskConfigWithLimit
}

func getTaskConfig(t *TaskConfig) (*TaskConfig, error) {
has, err := x.Get(t)
if err != nil {
return nil, err
} else if !has {
return nil, ErrRecordNotExist{}
}
return t, nil
}

func GetTaskConfigByTaskCode(taskCode string) (*TaskConfig, error) {
t := &TaskConfig{
TaskCode: taskCode,
}
return getTaskConfig(t)
}

func GetTaskConfigByID(id int64) (*TaskConfig, error) {
t := &TaskConfig{
ID: id,
}
return getTaskConfig(t)
}

func GetTaskConfigList() ([]*TaskConfig, error) {
r := make([]*TaskConfig, 0)
err := x.Find(&r)
if err != nil {
return nil, err
}
if len(r) == 0 {
return nil, ErrRecordNotExist{}
}
return r, nil
}

type GetTaskConfigOpts struct {
ListOptions
Status int //1 normal 2 deleted
ActionType int
}

func GetTaskConfigPageWithDeleted(opt GetTaskConfigOpts) ([]*TaskAndLimiterConfig, int64, error) {
if opt.Page <= 0 {
opt.Page = 1
}
cond := builder.NewCond()
if opt.ActionType > 0 {
cond = cond.And(builder.Eq{"task_code": fmt.Sprint(opt.ActionType)})
}

var count int64
var err error
if opt.Status == 1 {
subCond := builder.NewCond()
subCond = subCond.Or(builder.IsNull{"task_config.deleted_at"})
subCond = subCond.Or(builder.Eq{"task_config.deleted_at": 0})
cond = cond.And(subCond)
} else if opt.Status == 2 {
cond = cond.And(builder.Gt{"task_config.deleted_at": 0})
}
count, err = x.Unscoped().Where(cond).Count(&TaskConfig{})
if err != nil {
return nil, 0, err
}
r := make([]*TaskAndLimiterConfig, 0)
err = x.Join("LEFT", "limit_config", "task_config.id = limit_config.related_id").
Unscoped().Where(cond).Limit(opt.PageSize, (opt.Page-1)*opt.PageSize).
OrderBy("task_config.deleted_at desc,task_config.id desc").Find(&r)

if len(r) == 0 {
return nil, 0, ErrRecordNotExist{}
}
return r, count, nil
}

func EditTaskConfig(config TaskConfigWithLimit, doer *User) error {
sess := x.NewSession()
defer sess.Close()

//delete old task config
p := &TaskConfig{
ID: config.ID,
}
_, err := sess.Delete(p)
if err != nil {
sess.Rollback()
return err
}
//update deleter
p.DeleterId = doer.ID
p.DeleterName = doer.Name
sess.Where("id = ?", config.ID).Unscoped().Update(p)

//add new config
t := &TaskConfig{
TaskCode: config.TaskCode,
Title: config.Title,
AwardType: config.AwardType,
AwardAmount: config.AwardAmount,
CreatorId: doer.ID,
CreatorName: doer.Name,
}
_, err = sess.InsertOne(t)
if err != nil {
sess.Rollback()
return err
}

//delete old limiter config
lp := &LimitConfig{
RelatedId: config.ID,
}
_, err = sess.Delete(lp)
if err != nil {
sess.Rollback()
return err
}
lp.DeleterName = doer.Name
lp.DeleterId = doer.ID
//update deleter
sess.Where("related_id = ?", config.ID).Unscoped().Update(lp)

//add new limiter config
if config.Limiters != nil && len(config.Limiters) > 0 {
for _, v := range config.Limiters {
//add new config
l := &LimitConfig{
Title: v.Title,
RefreshRate: v.RefreshRate,
Scope: v.Scope,
LimitNum: v.LimitNum,
LimitCode: config.TaskCode,
LimitType: LimitTypeTask.Name(),
CreatorId: doer.ID,
CreatorName: doer.Name,
RelatedId: t.ID,
}
_, err = sess.Insert(l)
if err != nil {
sess.Rollback()
return err
}
}
}
sess.Commit()
return nil
}

func NewTaskConfig(config TaskConfigWithLimit, doer *User) error {
sess := x.NewSession()
defer sess.Close()

//add new config
t := &TaskConfig{
TaskCode: config.TaskCode,
Title: config.Title,
AwardType: config.AwardType,
AwardAmount: config.AwardAmount,
CreatorId: doer.ID,
CreatorName: doer.Name,
}
_, err := sess.InsertOne(t)
if err != nil {
sess.Rollback()
return err
}

//add new limiter config
if config.Limiters != nil && len(config.Limiters) > 0 {
for _, v := range config.Limiters {
//add new config
l := &LimitConfig{
RelatedId: t.ID,
Title: v.Title,
RefreshRate: v.RefreshRate,
Scope: v.Scope,
LimitNum: v.LimitNum,
LimitCode: config.TaskCode,
LimitType: LimitTypeTask.Name(),
CreatorId: doer.ID,
CreatorName: doer.Name,
}
_, err = sess.Insert(l)
if err != nil {
sess.Rollback()
return err
}
}
}
sess.Commit()
return nil
}

func DelTaskConfig(id int64, doer *User) error {
sess := x.NewSession()
defer sess.Close()

//delete old task config
p := &TaskConfig{
ID: id,
}
_, err := sess.Delete(p)
if err != nil {
sess.Rollback()
return err
}
//update deleter
p.DeleterId = doer.ID
p.DeleterName = doer.Name
sess.Where("id = ?", id).Unscoped().Update(p)
//delete old limiter config
lp := &LimitConfig{
RelatedId: id,
}
_, err = sess.Delete(lp)
if err != nil {
sess.Rollback()
return err
}
lp.DeleterName = doer.Name
lp.DeleterId = doer.ID
//update deleter
sess.Where("related_id = ?", id).Unscoped().Update(lp)
sess.Commit()
return nil
}

+ 4
- 0
models/user.go View File

@@ -188,6 +188,10 @@ type User struct {
PhoneNumber string `xorm:"VARCHAR(255)"` PhoneNumber string `xorm:"VARCHAR(255)"`
} }


type UserShow struct {
Name string
}

// SearchOrganizationsOptions options to filter organizations // SearchOrganizationsOptions options to filter organizations
type SearchOrganizationsOptions struct { type SearchOrganizationsOptions struct {
ListOptions ListOptions


+ 4
- 0
models/wechat_bind.go View File

@@ -96,3 +96,7 @@ func UnbindWechatOpenId(userId int64, oldWechatOpenID string) error {
sess.Insert(logParam) sess.Insert(logParam)
return sess.Commit() return sess.Commit()
} }

func CountWechatBindLog(wechatOpenId string, action WechatBindAction) (int64, error) {
return x.Where("wechat_open_id = ? and action = ?", action, wechatOpenId).Count(&WechatBindLog{})
}

+ 9
- 7
modules/auth/wechat/access_token.go View File

@@ -8,14 +8,12 @@ import (
"code.gitea.io/gitea/modules/redis/redis_lock" "code.gitea.io/gitea/modules/redis/redis_lock"
) )


const EMPTY_REDIS_VAL = "Nil"

var accessTokenLock = redis_lock.NewDistributeLock(redis_key.AccessTokenLockKey()) var accessTokenLock = redis_lock.NewDistributeLock(redis_key.AccessTokenLockKey())


func GetWechatAccessToken() string { func GetWechatAccessToken() string {
token, _ := redis_client.Get(redis_key.WechatAccessTokenKey()) token, _ := redis_client.Get(redis_key.WechatAccessTokenKey())
if token != "" { if token != "" {
if token == EMPTY_REDIS_VAL {
if token == redis_key.EMPTY_REDIS_VAL {
return "" return ""
} }
live, _ := redis_client.TTL(redis_key.WechatAccessTokenKey()) live, _ := redis_client.TTL(redis_key.WechatAccessTokenKey())
@@ -29,18 +27,22 @@ func GetWechatAccessToken() string {
} }


func refreshAccessToken() { func refreshAccessToken() {
if ok := accessTokenLock.Lock(3 * time.Second); ok {
if ok, _ := accessTokenLock.Lock(3 * time.Second); ok {
defer accessTokenLock.UnLock() defer accessTokenLock.UnLock()
callAccessTokenAndUpdateCache() callAccessTokenAndUpdateCache()
} }
} }


func refreshAndGetAccessToken() string { func refreshAndGetAccessToken() string {
if ok := accessTokenLock.LockWithWait(3*time.Second, 3*time.Second); ok {
isOk, err := accessTokenLock.LockWithWait(3*time.Second, 3*time.Second)
if err != nil {
return ""
}
if isOk {
defer accessTokenLock.UnLock() defer accessTokenLock.UnLock()
token, _ := redis_client.Get(redis_key.WechatAccessTokenKey()) token, _ := redis_client.Get(redis_key.WechatAccessTokenKey())
if token != "" { if token != "" {
if token == EMPTY_REDIS_VAL {
if token == redis_key.EMPTY_REDIS_VAL {
return "" return ""
} }
return token return token
@@ -60,7 +62,7 @@ func callAccessTokenAndUpdateCache() string {
} }


if token == "" { if token == "" {
redis_client.Setex(redis_key.WechatAccessTokenKey(), EMPTY_REDIS_VAL, 10*time.Second)
redis_client.Setex(redis_key.WechatAccessTokenKey(), redis_key.EMPTY_REDIS_VAL, 10*time.Second)
return "" return ""
} }
redis_client.Setex(redis_key.WechatAccessTokenKey(), token, time.Duration(r.Expires_in)*time.Second) redis_client.Setex(redis_key.WechatAccessTokenKey(), token, time.Duration(r.Expires_in)*time.Second)


+ 3
- 3
modules/auth/wechat/bind.go View File

@@ -38,7 +38,7 @@ func (err WechatBindError) Error() string {
} }


func BindWechat(userId int64, wechatOpenId string) error { func BindWechat(userId int64, wechatOpenId string) error {
if !IsWechatAccountAvailable(userId, wechatOpenId) {
if !IsWechatAccountUsed(userId, wechatOpenId) {
log.Error("bind wechat failed, because user use wrong wechat account to bind,userId=%d wechatOpenId=%s", userId, wechatOpenId) log.Error("bind wechat failed, because user use wrong wechat account to bind,userId=%d wechatOpenId=%s", userId, wechatOpenId)
return NewWechatBindError(BIND_REPLY_WECHAT_ACCOUNT_USED) return NewWechatBindError(BIND_REPLY_WECHAT_ACCOUNT_USED)
} }
@@ -60,9 +60,9 @@ func IsUserAvailableForWechatBind(userId int64, wechatOpenId string) bool {
return currentOpenId == "" || currentOpenId == wechatOpenId return currentOpenId == "" || currentOpenId == wechatOpenId
} }


//IsWechatAccountAvailable if wechat account used by another account,return false
//IsWechatAccountUsed if wechat account used by another account,return false
//if wechat account not used or used by the given user,return true //if wechat account not used or used by the given user,return true
func IsWechatAccountAvailable(userId int64, wechatOpenId string) bool {
func IsWechatAccountUsed(userId int64, wechatOpenId string) bool {
user := models.GetUserByWechatOpenId(wechatOpenId) user := models.GetUserByWechatOpenId(wechatOpenId)
if user != nil && user.WechatOpenId != "" && user.ID != userId { if user != nil && user.WechatOpenId != "" && user.ID != userId {
return false return false


+ 2
- 0
modules/auth/wechat/client.go View File

@@ -95,6 +95,7 @@ func getWechatRestyClient() *resty.Client {
func callAccessToken() *AccessTokenResponse { func callAccessToken() *AccessTokenResponse {
client := getWechatRestyClient() client := getWechatRestyClient()


log.Info("start to get wechat access token")
var result AccessTokenResponse var result AccessTokenResponse
_, err := client.R(). _, err := client.R().
SetQueryParam("grant_type", GRANT_TYPE). SetQueryParam("grant_type", GRANT_TYPE).
@@ -106,6 +107,7 @@ func callAccessToken() *AccessTokenResponse {
log.Error("get wechat access token failed,e=%v", err) log.Error("get wechat access token failed,e=%v", err)
return nil return nil
} }
log.Info("get wechat access token result=%v", result)
return &result return &result
} }




+ 8
- 3
modules/cloudbrain/resty.go View File

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


import ( import (
"code.gitea.io/gitea/modules/notification"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -25,10 +26,10 @@ var (


const ( const (
JobHasBeenStopped = "S410" JobHasBeenStopped = "S410"
errInvalidToken = "S401"
Public = "public" Public = "public"
Custom = "custom" Custom = "custom"
LogPageSize = 500 LogPageSize = 500
errInvalidToken = "S401"
LogPageTokenExpired = "5m" LogPageTokenExpired = "5m"
pageSize = 15 pageSize = 15
QueuesDetailUrl = "/rest-server/api/v2/queuesdetail" QueuesDetailUrl = "/rest-server/api/v2/queuesdetail"
@@ -234,7 +235,7 @@ func getQueryString(page int, size int, name string) string {
return fmt.Sprintf("pageIndex=%d&pageSize=%d&name=%s", page, size, name) return fmt.Sprintf("pageIndex=%d&pageSize=%d&name=%s", page, size, name)
} }


func CommitImage(jobID string, params models.CommitImageParams) error {
func CommitImage(jobID string, params models.CommitImageParams, doer *models.User) error {
imageTag := strings.TrimSpace(params.ImageTag) imageTag := strings.TrimSpace(params.ImageTag)


dbImage, err := models.GetImageByTag(imageTag) dbImage, err := models.GetImageByTag(imageTag)
@@ -339,11 +340,12 @@ sendjob:
}) })
if err == nil { if err == nil {
go updateImageStatus(image, isSetCreatedUnix, createTime) go updateImageStatus(image, isSetCreatedUnix, createTime)
notification.NotifyCreateImage(doer, image)
} }
return err return err
} }


func CommitAdminImage(params models.CommitImageParams) error {
func CommitAdminImage(params models.CommitImageParams, doer *models.User) error {
imageTag := strings.TrimSpace(params.ImageTag) imageTag := strings.TrimSpace(params.ImageTag)
exist, err := models.IsImageExist(imageTag) exist, err := models.IsImageExist(imageTag)


@@ -380,6 +382,9 @@ func CommitAdminImage(params models.CommitImageParams) error {
} }
return nil return nil
}) })
if err == nil {
notification.NotifyCreateImage(doer, image)
}
return err return err
} }




+ 21
- 0
modules/context/point.go View File

@@ -0,0 +1,21 @@
package context

import (
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/services/reward/point/account"
"gitea.com/macaron/macaron"
)

// PointAccount returns a macaron to get request user's point account
func PointAccount() macaron.Handler {
return func(ctx *Context) {
a, err := account.GetAccount(ctx.User.ID)
if err != nil {
ctx.ServerError("GetPointAccount", err)
return
}
ctx.Data["PointAccount"] = a
ctx.Data["CloudBrainPaySwitch"] = setting.CloudBrainPaySwitch
ctx.Next()
}
}

+ 26
- 0
modules/cron/tasks_basic.go View File

@@ -5,6 +5,7 @@
package cron package cron


import ( import (
"code.gitea.io/gitea/services/reward"
"code.gitea.io/gitea/services/cloudbrain/resource" "code.gitea.io/gitea/services/cloudbrain/resource"
"code.gitea.io/gitea/modules/modelarts" "code.gitea.io/gitea/modules/modelarts"
"context" "context"
@@ -209,6 +210,28 @@ func registerSyncCloudbrainStatus() {
}) })
} }


func registerRewardPeriodTask() {
RegisterTaskFatal("reward_period_task", &BaseConfig{
Enabled: true,
RunAtStart: true,
Schedule: "@every 1m",
}, func(ctx context.Context, _ *models.User, _ Config) error {
reward.StartRewardTask()
return nil
})
}

func registerCloudbrainPointDeductTask() {
RegisterTaskFatal("cloudbrain_point_deduct_task", &BaseConfig{
Enabled: true,
RunAtStart: true,
Schedule: "@every 1m",
}, func(ctx context.Context, _ *models.User, _ Config) error {
reward.StartCloudbrainPointDeductTask()
return nil
})
}

func registerSyncResourceSpecs() { func registerSyncResourceSpecs() {
RegisterTaskFatal("sync_grampus_specs", &BaseConfig{ RegisterTaskFatal("sync_grampus_specs", &BaseConfig{
Enabled: true, Enabled: true,
@@ -253,4 +276,7 @@ func initBasicTasks() {
registerHandleOrgStatistic() registerHandleOrgStatistic()
registerSyncResourceSpecs() registerSyncResourceSpecs()
registerSyncModelArtsTempJobs() registerSyncModelArtsTempJobs()

//registerRewardPeriodTask()
registerCloudbrainPointDeductTask()
} }

+ 22
- 0
modules/eventsource/manager_run.go View File

@@ -5,6 +5,7 @@
package eventsource package eventsource


import ( import (
"code.gitea.io/gitea/services/reward"
"context" "context"
"time" "time"


@@ -24,8 +25,28 @@ func (m *Manager) Init() {
func (m *Manager) Run(ctx context.Context) { func (m *Manager) Run(ctx context.Context) {
then := timeutil.TimeStampNow().Add(-2) then := timeutil.TimeStampNow().Add(-2)
timer := time.NewTicker(setting.UI.Notification.EventSourceUpdateTime) timer := time.NewTicker(setting.UI.Notification.EventSourceUpdateTime)
rewardThen := then
rewardTimer := time.NewTicker(setting.UI.Notification.RewardNotifyUpdateTime)
loop: loop:
for { for {
select {
case <-rewardTimer.C:
log.Debug("rewardTimer run")
now := timeutil.TimeStampNow().Add(-2)
list := reward.GetRewardOperation(rewardThen, now)
if list != nil {
log.Debug("GetRewardOperation list=%v", list)
for _, l := range list {
m.SendMessage(l.UserId, &Event{
Name: "reward-operation",
Data: l.Msg,
})
}
}

rewardThen = now
}

select { select {
case <-ctx.Done(): case <-ctx.Done():
timer.Stop() timer.Stop()
@@ -44,6 +65,7 @@ loop:
}) })
} }
then = now then = now
default:
} }
} }
m.UnregisterAll() m.UnregisterAll()


+ 3
- 2
modules/modelarts/modelarts.go View File

@@ -148,8 +148,9 @@ type VersionInfo struct {


type Flavor struct { type Flavor struct {
Info []struct { Info []struct {
Code string `json:"code"`
Value string `json:"value"`
Code string `json:"code"`
Value string `json:"value"`
UnitPrice int64 `json:"unitPrice"`
} `json:"flavor"` } `json:"flavor"`
} }




+ 99
- 0
modules/notification/action/action.go View File

@@ -5,6 +5,7 @@
package action package action


import ( import (
"code.gitea.io/gitea/modules/auth"
"encoding/json" "encoding/json"
"fmt" "fmt"
"path" "path"
@@ -345,3 +346,101 @@ func (a *actionNotifier) NotifyOtherTask(doer *models.User, repo *models.Reposit
log.Error("notifyWatchers: %v", err) log.Error("notifyWatchers: %v", err)
} }
} }

func (t *actionNotifier) NotifyWechatBind(user *models.User, wechatOpenId string) {
act := &models.Action{
ActUserID: user.ID,
ActUser: user,
OpType: models.ActionBindWechat,
IsPrivate: true,
Content: wechatOpenId,
}
if err := models.NotifyWatchers(act); err != nil {
log.Error("notifyWatchers: %v", err)
}
}

func (t *actionNotifier) NotifyDatasetRecommend(optUser *models.User, dataset *models.Dataset, action string) {
switch action {
case "recommend":
users, err := models.GetAllDatasetContributorByDatasetId(dataset.ID)
if err != nil {
return
}
var actions = make([]*models.Action, 0)
for _, user := range users {
actions = append(actions, &models.Action{
OpType: models.ActionDatasetRecommended,
ActUserID: user.ID,
ActUser: user,
RepoID: dataset.RepoID,
Repo: dataset.Repo,
Content: fmt.Sprintf("%d|%s", dataset.ID, dataset.Title),
})
}
if err := models.NotifyWatchers(actions...); err != nil {
log.Error("notifyWatchers: %v", err)
}
}
}

func (t *actionNotifier) NotifyCreateImage(doer *models.User, image models.Image) {
act := &models.Action{
ActUserID: doer.ID,
ActUser: doer,
OpType: models.ActionCreateImage,
IsPrivate: image.IsPrivate,
Content: fmt.Sprintf("%d|%s", image.ID, image.Tag),
}
if err := models.NotifyWatchers(act); err != nil {
log.Error("notifyWatchers: %v", err)
}
}

func (t *actionNotifier) NotifyImageRecommend(optUser *models.User, image *models.Image, action string) {
u, err := models.GetUserByID(image.UID)
if err != nil {
return
}
switch action {
case "recommend":
act := &models.Action{
ActUserID: u.ID,
ActUser: u,
OpType: models.ActionImageRecommend,
IsPrivate: false,
Content: fmt.Sprintf("%d|%s", image.ID, image.Tag),
}
if err := models.NotifyWatchers(act); err != nil {
log.Error("notifyWatchers: %v", err)
}
}
}

func (t *actionNotifier) NotifyChangeUserAvatar(user *models.User, form auth.AvatarForm) {
act := &models.Action{
ActUserID: user.ID,
ActUser: user,
OpType: models.ActionChangeUserAvatar,
IsPrivate: true,
}
if err := models.NotifyWatchers(act); err != nil {
log.Error("notifyWatchers: %v", err)
}
}

func (t *actionNotifier) NotifyPushCommits(pusher *models.User, repo *models.Repository, refName, oldCommitID, newCommitID string, commits *repository.PushCommits) {
act := &models.Action{
ActUserID: pusher.ID,
ActUser: pusher,
OpType: models.ActionPushCommits,
RepoID: repo.ID,
Repo: repo,
RefName: refName,
IsPrivate: repo.IsPrivate,
Content: fmt.Sprintf("%s|%s", oldCommitID, newCommitID),
}
if err := models.NotifyWatchers(act); err != nil {
log.Error("notifyWatchers: %v", err)
}
}

+ 6
- 0
modules/notification/base/notifier.go View File

@@ -6,6 +6,7 @@ package base


import ( import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/repository"
) )


@@ -56,6 +57,11 @@ type Notifier interface {
NotifySyncDeleteRef(doer *models.User, repo *models.Repository, refType, refFullName string) NotifySyncDeleteRef(doer *models.User, repo *models.Repository, refType, refFullName string)


NotifyOtherTask(doer *models.User, repo *models.Repository, id string, name string, optype models.ActionType) NotifyOtherTask(doer *models.User, repo *models.Repository, id string, name string, optype models.ActionType)
NotifyWechatBind(user *models.User, wechatOpenId string)
NotifyDatasetRecommend(optUser *models.User, dataset *models.Dataset, action string)
NotifyCreateImage(doer *models.User, image models.Image)
NotifyImageRecommend(optUser *models.User, image *models.Image, action string)
NotifyChangeUserAvatar(user *models.User, form auth.AvatarForm)


NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain, oldStatus string) NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain, oldStatus string)
} }

+ 18
- 0
modules/notification/base/null.go View File

@@ -6,6 +6,7 @@ package base


import ( import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/repository"
) )


@@ -159,6 +160,23 @@ func (*NullNotifier) NotifyOtherTask(doer *models.User, repo *models.Repository,


} }


func (*NullNotifier) NotifyWechatBind(user *models.User, wechatOpenId string) {

}

func (*NullNotifier) NotifyDatasetRecommend(optUser *models.User, dataset *models.Dataset, action string) {
}

func (*NullNotifier) NotifyCreateImage(doer *models.User, image models.Image) {
}

func (*NullNotifier) NotifyImageRecommend(optUser *models.User, image *models.Image, action string) {
}

func (*NullNotifier) NotifyChangeUserAvatar(user *models.User, form auth.AvatarForm) {

}

func (*NullNotifier) NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain, oldStatus string) { func (*NullNotifier) NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain, oldStatus string) {


} }

+ 38
- 0
modules/notification/notification.go View File

@@ -6,10 +6,12 @@ package notification


import ( import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/notification/action" "code.gitea.io/gitea/modules/notification/action"
"code.gitea.io/gitea/modules/notification/base" "code.gitea.io/gitea/modules/notification/base"
"code.gitea.io/gitea/modules/notification/indexer" "code.gitea.io/gitea/modules/notification/indexer"
"code.gitea.io/gitea/modules/notification/mail" "code.gitea.io/gitea/modules/notification/mail"
"code.gitea.io/gitea/modules/notification/reward"
"code.gitea.io/gitea/modules/notification/ui" "code.gitea.io/gitea/modules/notification/ui"
"code.gitea.io/gitea/modules/notification/webhook" "code.gitea.io/gitea/modules/notification/webhook"
wechatNotifier "code.gitea.io/gitea/modules/notification/wechat" wechatNotifier "code.gitea.io/gitea/modules/notification/wechat"
@@ -37,6 +39,7 @@ func NewContext() {
RegisterNotifier(webhook.NewNotifier()) RegisterNotifier(webhook.NewNotifier())
RegisterNotifier(action.NewNotifier()) RegisterNotifier(action.NewNotifier())
RegisterNotifier(wechatNotifier.NewNotifier()) RegisterNotifier(wechatNotifier.NewNotifier())
RegisterNotifier(reward.NewNotifier())
} }


// NotifyUploadAttachment notifies attachment upload message to notifiers // NotifyUploadAttachment notifies attachment upload message to notifiers
@@ -272,6 +275,41 @@ func NotifySyncDeleteRef(pusher *models.User, repo *models.Repository, refType,
} }
} }


// NotifyWechatBind notifies wechat bind
func NotifyWechatBind(user *models.User, wechatOpenId string) {
for _, notifier := range notifiers {
notifier.NotifyWechatBind(user, wechatOpenId)
}
}

// NotifyDatasetRecommend
func NotifyDatasetRecommend(optUser *models.User, dataset *models.Dataset, action string) {
for _, notifier := range notifiers {
notifier.NotifyDatasetRecommend(optUser, dataset, action)
}
}

// NotifyDatasetRecommend
func NotifyCreateImage(doer *models.User, image models.Image) {
for _, notifier := range notifiers {
notifier.NotifyCreateImage(doer, image)
}
}

// NotifyDatasetRecommend
func NotifyImageRecommend(optUser *models.User, image *models.Image, action string) {
for _, notifier := range notifiers {
notifier.NotifyImageRecommend(optUser, image, action)
}
}

// NotifyDatasetRecommend
func NotifyChangeUserAvatar(user *models.User, form auth.AvatarForm) {
for _, notifier := range notifiers {
notifier.NotifyChangeUserAvatar(user, form)
}
}

// NotifyChangeCloudbrainStatus // NotifyChangeCloudbrainStatus
func NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain, oldStatus string) { func NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain, oldStatus string) {
for _, notifier := range notifiers { for _, notifier := range notifiers {


+ 27
- 0
modules/notification/reward/point.go View File

@@ -0,0 +1,27 @@
package reward

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/notification/base"
)

type pointNotifier struct {
base.NullNotifier
}

var (
_ base.Notifier = &pointNotifier{}
)

// NewNotifier create a new wechatNotifier notifier
func NewNotifier() base.Notifier {
return &pointNotifier{}
}

func (*pointNotifier) NotifyChangeCloudbrainStatus(cloudbrain *models.Cloudbrain, oldStatus string) {
log.Info("pointNotifier NotifyChangeCloudbrainStatus cloudbrain.id=%d cloudbrain.status=%s oldStatus=%s", cloudbrain.ID, cloudbrain.Status, oldStatus)
if cloudbrain.IsRunning() || cloudbrain.IsTerminal() {
models.StatusChangeChan <- cloudbrain
}
}

+ 83
- 1
modules/redis/redis_client/client.go View File

@@ -76,7 +76,7 @@ func HEXISTS(conn redis.Conn, key string, subKey string) (bool, error) {


} }


func Expire(conn redis.Conn, key string, seconds int) error {
func EXPIRE(conn redis.Conn, key string, seconds int) error {
_, err := conn.Do("EXPIRE", key, seconds) _, err := conn.Do("EXPIRE", key, seconds)
return err return err


@@ -145,3 +145,85 @@ func TTL(key string) (int, error) {
return n, nil return n, nil


} }

func IncrBy(key string, n int64) (int64, error) {
redisClient := labelmsg.Get()
defer redisClient.Close()

reply, err := redisClient.Do("INCRBY", key, n)
if err != nil {
return 0, err
}
i, err := strconv.ParseInt(fmt.Sprint(reply), 10, 64)
return i, nil

}

func Expire(key string, expireTime time.Duration) error {
redisClient := labelmsg.Get()
defer redisClient.Close()

_, err := redisClient.Do("EXPIRE", key, int64(expireTime.Seconds()))
if err != nil {
return err
}
return nil

}

//GetInt64 get redis value by Get(key)
//and then parse the value to int64
//return {isExist(bool)} {value(int64)} {error(error)}
func GetInt64(key string) (bool, int64, error) {
str, err := Get(key)
if err != nil {
return false, 0, err
}
if str == "" {
return false, 0, nil
}

i, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return false, 0, err
}
return true, i, nil

}

func ZAdd(key, value string, score float64) error {
redisClient := labelmsg.Get()
defer redisClient.Close()

_, err := redisClient.Do("ZADD", key, score, value)
if err != nil {
return err
}
return nil
}

func ZRangeByScore(key string, min, max float64) ([]string, error) {
redisClient := labelmsg.Get()
defer redisClient.Close()

reply, err := redisClient.Do("ZRANGEBYSCORE", key, min, max)
if err != nil {
return nil, err
}
if reply == nil {
return nil, err
}
s, _ := redis.Strings(reply, nil)
return s, nil
}

func ZRemRangeByScore(key string, min, max float64) error {
redisClient := labelmsg.Get()
defer redisClient.Close()

_, err := redisClient.Do("ZREMRANGEBYSCORE", key, min, max)
if err != nil {
return err
}
return nil
}

+ 17
- 0
modules/redis/redis_key/account_redis_key.go View File

@@ -0,0 +1,17 @@
package redis_key

import "fmt"

const ACCOUNT_REDIS_PREFIX = "account"

func PointAccountOperateLock(userId int64) string {
return KeyJoin(ACCOUNT_REDIS_PREFIX, fmt.Sprint(userId), "point", "operate", "lock")
}

func PointAccountInfo(userId int64) string {
return KeyJoin(ACCOUNT_REDIS_PREFIX, fmt.Sprint(userId), "info")
}

func PointAccountInitLock(userId int64) string {
return KeyJoin(ACCOUNT_REDIS_PREFIX, fmt.Sprint(userId), "init", "lock")
}

+ 2
- 0
modules/redis/redis_key/key_base.go View File

@@ -4,6 +4,8 @@ import "strings"


const KEY_SEPARATE = ":" const KEY_SEPARATE = ":"


const EMPTY_REDIS_VAL = "Nil"

func KeyJoin(keys ...string) string { func KeyJoin(keys ...string) string {
var build strings.Builder var build strings.Builder
for _, v := range keys { for _, v := range keys {


+ 26
- 0
modules/redis/redis_key/limit_redis_key.go View File

@@ -0,0 +1,26 @@
package redis_key

import (
"code.gitea.io/gitea/models"
"fmt"
)

const LIMIT_REDIS_PREFIX = "limit"

func LimitCount(userId int64, limitCode string, limitType string, scope string, period *models.PeriodResult) string {
if scope == models.LimitScopeAllUsers.Name() {
if period == nil {
return KeyJoin(LIMIT_REDIS_PREFIX, limitCode, limitType, "count")
}
return KeyJoin(LIMIT_REDIS_PREFIX, limitCode, limitType, fmt.Sprint(period.StartTime.Unix()), fmt.Sprint(period.EndTime.Unix()), "count")
}
if period == nil {
return KeyJoin(LIMIT_REDIS_PREFIX, "uid", fmt.Sprint(userId), limitCode, limitType, "count")
}
return KeyJoin(LIMIT_REDIS_PREFIX, "uid", fmt.Sprint(userId), limitCode, limitType, fmt.Sprint(period.StartTime.Unix()), fmt.Sprint(period.EndTime.Unix()), "count")

}

func LimitConfig(limitType string) string {
return KeyJoin(LIMIT_REDIS_PREFIX, limitType, "config")
}

+ 21
- 0
modules/redis/redis_key/reward_redis_key.go View File

@@ -0,0 +1,21 @@
package redis_key

import (
"code.gitea.io/gitea/modules/setting"
"fmt"
"strings"
)

const REWARD_REDIS_PREFIX = "reward"

func RewardOperateLock(requestId string, sourceType string, operateType string) string {
return KeyJoin(REWARD_REDIS_PREFIX, requestId, sourceType, operateType, "send")
}

func RewardOperateNotification() string {
return KeyJoin(REWARD_REDIS_PREFIX, "operate", strings.ReplaceAll(setting.AppURL, "/", ""), "notification")
}

func RewardTaskRunningLock(taskId int64) string {
return KeyJoin(REWARD_REDIS_PREFIX, "periodic_task", fmt.Sprint(taskId), "lock")
}

+ 10
- 0
modules/redis/redis_key/serial_redis_key.go View File

@@ -0,0 +1,10 @@
package redis_key

import "time"

const SERIAL_REDIS_PREFIX = "serial"

func RewardSerialCounter(now time.Time) string {
h := now.Format("200601021504")
return KeyJoin(SERIAL_REDIS_PREFIX, "reward_operate", h, "counter")
}

+ 14
- 0
modules/redis/redis_key/task_redis_key.go View File

@@ -0,0 +1,14 @@
package redis_key

const TASK_REDIS_PREFIX = "task"

func TaskAccomplishLock(sourceId string, taskType string) string {
return KeyJoin(TASK_REDIS_PREFIX, sourceId, taskType, "accomplish")
}

func TaskConfigList() string {
return KeyJoin(TASK_REDIS_PREFIX, "config", "list")
}
func TaskConfigOperateLock(taskCode, rewardType string) string {
return KeyJoin(TASK_REDIS_PREFIX, "config", "operate", "lock")
}

+ 15
- 8
modules/redis/redis_lock/lock.go View File

@@ -14,25 +14,32 @@ func NewDistributeLock(lockKey string) *DistributeLock {
return &DistributeLock{lockKey: lockKey} return &DistributeLock{lockKey: lockKey}
} }


func (lock *DistributeLock) Lock(expireTime time.Duration) bool {
isOk, _ := redis_client.Setnx(lock.lockKey, "", expireTime)
return isOk
func (lock *DistributeLock) Lock(expireTime time.Duration) (bool, error) {
isOk, err := redis_client.Setnx(lock.lockKey, "", expireTime)
if err != nil {
return false, err
}
return isOk, nil
} }


func (lock *DistributeLock) LockWithWait(expireTime time.Duration, waitTime time.Duration) bool {
func (lock *DistributeLock) LockWithWait(expireTime time.Duration, waitTime time.Duration) (bool, error) {
start := time.Now().Unix() * 1000 start := time.Now().Unix() * 1000
duration := waitTime.Milliseconds() duration := waitTime.Milliseconds()
for { for {
isOk, _ := redis_client.Setnx(lock.lockKey, "", expireTime)
isOk, err := redis_client.Setnx(lock.lockKey, "", expireTime)
if err != nil {
return false, err
}
if isOk { if isOk {
return true
return true, nil
} }
if time.Now().Unix()*1000-start > duration { if time.Now().Unix()*1000-start > duration {
return false
return false, nil
} }
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
} }
return false

return false, nil
} }


func (lock *DistributeLock) UnLock() error { func (lock *DistributeLock) UnLock() error {


+ 30
- 13
modules/setting/setting.go View File

@@ -214,10 +214,11 @@ var (
UseServiceWorker bool UseServiceWorker bool


Notification struct { Notification struct {
MinTimeout time.Duration
TimeoutStep time.Duration
MaxTimeout time.Duration
EventSourceUpdateTime time.Duration
MinTimeout time.Duration
TimeoutStep time.Duration
MaxTimeout time.Duration
EventSourceUpdateTime time.Duration
RewardNotifyUpdateTime time.Duration
} `ini:"ui.notification"` } `ini:"ui.notification"`


Admin struct { Admin struct {
@@ -251,15 +252,17 @@ var (
Themes: []string{`gitea`, `arc-green`}, Themes: []string{`gitea`, `arc-green`},
Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`}, Reactions: []string{`+1`, `-1`, `laugh`, `hooray`, `confused`, `heart`, `rocket`, `eyes`},
Notification: struct { Notification: struct {
MinTimeout time.Duration
TimeoutStep time.Duration
MaxTimeout time.Duration
EventSourceUpdateTime time.Duration
MinTimeout time.Duration
TimeoutStep time.Duration
MaxTimeout time.Duration
EventSourceUpdateTime time.Duration
RewardNotifyUpdateTime time.Duration
}{ }{
MinTimeout: 10 * time.Second,
TimeoutStep: 10 * time.Second,
MaxTimeout: 60 * time.Second,
EventSourceUpdateTime: 10 * time.Second,
MinTimeout: 10 * time.Second,
TimeoutStep: 10 * time.Second,
MaxTimeout: 60 * time.Second,
EventSourceUpdateTime: 10 * time.Second,
RewardNotifyUpdateTime: 2 * time.Second,
}, },
Admin: struct { Admin: struct {
UserPagingNum int UserPagingNum int
@@ -610,6 +613,13 @@ var (
WechatQRCodeExpireSeconds int WechatQRCodeExpireSeconds int
WechatAuthSwitch bool WechatAuthSwitch bool


//point config
CloudBrainPaySwitch bool
CloudBrainPayDelay time.Duration
CloudBrainPayInterval time.Duration
DeductTaskRange time.Duration
DeductTaskRangeForFirst time.Duration

//wechat auto reply config //wechat auto reply config
UserNameOfWechatReply string UserNameOfWechatReply string
RepoNameOfWechatReply string RepoNameOfWechatReply string
@@ -1481,12 +1491,13 @@ func NewContext() {
WechatAppId = sec.Key("APP_ID").MustString("wxba77b915a305a57d") WechatAppId = sec.Key("APP_ID").MustString("wxba77b915a305a57d")
WechatAppSecret = sec.Key("APP_SECRET").MustString("") WechatAppSecret = sec.Key("APP_SECRET").MustString("")
WechatQRCodeExpireSeconds = sec.Key("QR_CODE_EXPIRE_SECONDS").MustInt(120) WechatQRCodeExpireSeconds = sec.Key("QR_CODE_EXPIRE_SECONDS").MustInt(120)
WechatAuthSwitch = sec.Key("AUTH_SWITCH").MustBool(true)
WechatAuthSwitch = sec.Key("AUTH_SWITCH").MustBool(false)
UserNameOfWechatReply = sec.Key("AUTO_REPLY_USER_NAME").MustString("OpenIOSSG") UserNameOfWechatReply = sec.Key("AUTO_REPLY_USER_NAME").MustString("OpenIOSSG")
RepoNameOfWechatReply = sec.Key("AUTO_REPLY_REPO_NAME").MustString("promote") RepoNameOfWechatReply = sec.Key("AUTO_REPLY_REPO_NAME").MustString("promote")
RefNameOfWechatReply = sec.Key("AUTO_REPLY_REF_NAME").MustString("master") RefNameOfWechatReply = sec.Key("AUTO_REPLY_REF_NAME").MustString("master")
TreePathOfAutoMsgReply = sec.Key("AUTO_REPLY_TREE_PATH").MustString("wechat/auto_reply.json") TreePathOfAutoMsgReply = sec.Key("AUTO_REPLY_TREE_PATH").MustString("wechat/auto_reply.json")
TreePathOfSubscribe = sec.Key("SUBSCRIBE_TREE_PATH").MustString("wechat/subscribe_reply.json") TreePathOfSubscribe = sec.Key("SUBSCRIBE_TREE_PATH").MustString("wechat/subscribe_reply.json")
WechatAuthSwitch = sec.Key("AUTH_SWITCH").MustBool(false)
CloudbrainStartedTemplateId = sec.Key("CLOUDBRAIN_STARTED_TEMPLATE_ID").MustString("") CloudbrainStartedTemplateId = sec.Key("CLOUDBRAIN_STARTED_TEMPLATE_ID").MustString("")
CloudbrainStartedNotifyList = strings.Split(sec.Key("CLOUDBRAIN_STARTED_NOTIFY_LIST").MustString("DEBUG"), ",") CloudbrainStartedNotifyList = strings.Split(sec.Key("CLOUDBRAIN_STARTED_NOTIFY_LIST").MustString("DEBUG"), ",")
CloudbrainStartedTitle = sec.Key("CLOUDBRAIN_STARTED_TITLE").MustString("您好,您提交的算力资源申请已通过,任务已启动,请您关注运行情况。") CloudbrainStartedTitle = sec.Key("CLOUDBRAIN_STARTED_TITLE").MustString("您好,您提交的算力资源申请已通过,任务已启动,请您关注运行情况。")
@@ -1496,6 +1507,12 @@ func NewContext() {
CloudbrainStoppedTitle = sec.Key("CLOUDBRAIN_STOPPED_TITLE").MustString("您好,您申请的算力资源已结束使用,任务已完成运行,状态为%s,请您关注运行结果") CloudbrainStoppedTitle = sec.Key("CLOUDBRAIN_STOPPED_TITLE").MustString("您好,您申请的算力资源已结束使用,任务已完成运行,状态为%s,请您关注运行结果")
CloudbrainStoppedRemark = sec.Key("CLOUDBRAIN_STOPPED_REMARK").MustString("感谢您的耐心等待。") CloudbrainStoppedRemark = sec.Key("CLOUDBRAIN_STOPPED_REMARK").MustString("感谢您的耐心等待。")


sec = Cfg.Section("point")
CloudBrainPaySwitch = sec.Key("CLOUDBRAIN_PAY_SWITCH").MustBool(false)
CloudBrainPayDelay = sec.Key("CLOUDBRAIN_PAY_DELAY").MustDuration(30 * time.Minute)
CloudBrainPayInterval = sec.Key("CLOUDBRAIN_PAY_INTERVAL").MustDuration(60 * time.Minute)
DeductTaskRange = sec.Key("DEDUCT_TASK_RANGE").MustDuration(30 * time.Minute)
DeductTaskRangeForFirst = sec.Key("DEDUCT_TASK_RANGE_FOR_FIRST").MustDuration(3 * time.Hour)
SetRadarMapConfig() SetRadarMapConfig()


sec = Cfg.Section("warn_mail") sec = Cfg.Section("warn_mail")


+ 1
- 1
modules/templates/helper.go View File

@@ -791,7 +791,7 @@ func GetRefName(ref string) string {
return reg.ReplaceAllString(ref, "") return reg.ReplaceAllString(ref, "")
} }


func MB2GB(size int64) string {
func MB2GB(size int) string {
s := strconv.FormatFloat(float64(size)/float64(1024), 'f', 2, 64) s := strconv.FormatFloat(float64(size)/float64(1024), 'f', 2, 64)
for strings.HasSuffix(s, "0") { for strings.HasSuffix(s, "0") {
s = strings.TrimSuffix(s, "0") s = strings.TrimSuffix(s, "0")


+ 10
- 0
modules/util/uuid_util.go View File

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

import (
gouuid "github.com/satori/go.uuid"
"strings"
)

func UUID() string {
return strings.ReplaceAll(gouuid.NewV4().String(), "-", "")
}

+ 14
- 3
options/locale/locale_en-US.ini View File

@@ -23,6 +23,7 @@ signed_in_as = Signed in as
enable_javascript = This website works better with JavaScript. enable_javascript = This website works better with JavaScript.
toc = Table of Contents toc = Table of Contents
return=Back OpenI return=Back OpenI
calculation_points = Calculation Points


username = Username username = Username
email = Email Address email = Email Address
@@ -1055,7 +1056,7 @@ image_delete_fail=Failed to delete image, please try again later.
image_overwrite=You had submitted the same name image before, are you sure to overwrite the original image? image_overwrite=You had submitted the same name image before, are you sure to overwrite the original image?
download=Download download=Download
score=Score score=Score
wait_count_start = There are currently
wait_count_start = There are currently
wait_count_end = tasks queued wait_count_end = tasks queued
file_limit_100 = Display up to 100 files or folders in a single directory file_limit_100 = Display up to 100 files or folders in a single directory
images.name = Image Tag images.name = Image Tag
@@ -1267,7 +1268,7 @@ model.manage.modellabel=Model label
model.manage.modeldesc=Model description model.manage.modeldesc=Model description
model.manage.baseinfo=Base Information model.manage.baseinfo=Base Information
modelconvert.notcreate=No model conversion task has been created. modelconvert.notcreate=No model conversion task has been created.
modelconvert.importfirst1=Please import the
modelconvert.importfirst1=Please import the
modelconvert.importfirst2=model modelconvert.importfirst2=model
modelconvert.importfirst3=first, then converts it. modelconvert.importfirst3=first, then converts it.
modelconvert.download=Download modelconvert.download=Download
@@ -3230,4 +3231,14 @@ load_code_failed=Fail to load code, please check if the right branch is selected


error.dataset_select = dataset select error:the count exceed the limit or has same name error.dataset_select = dataset select error:the count exceed the limit or has same name
new_train_gpu_tooltips = The code is storaged in <strong style="color:#010101">%s</strong>, the dataset is storaged in <strong style="color:#010101">%s</strong>, and please put your model into <strong style="color:#010101">%s</strong> then you can download it online new_train_gpu_tooltips = The code is storaged in <strong style="color:#010101">%s</strong>, the dataset is storaged in <strong style="color:#010101">%s</strong>, and please put your model into <strong style="color:#010101">%s</strong> then you can download it online
new_infer_gpu_tooltips = The dataset is stored in <strong style="color:#010101">%s</strong>, the model file is stored in <strong style="color:#010101">%s</strong>, please store the inference output in <strong style="color:#010101">%s</strong> for subsequent downloads.
new_infer_gpu_tooltips = The dataset is stored in <strong style="color:#010101">%s</strong>, the model file is stored in <strong style="color:#010101">%s</strong>, please store the inference output in <strong style="color:#010101">%s</strong> for subsequent downloads.

[points]
points = points
free = Free
points_hour = Points/hour
balance_of_points = Balance of Points:
hours = Hours
expected_time = , expected to be available for
points_acquisition_instructions = Points Acquisition Instructions
insufficient_points_balance = Insufficient points balance

+ 13
- 1
options/locale/locale_zh-CN.ini View File

@@ -23,6 +23,7 @@ signed_in_as=已登录用户
enable_javascript=使用 JavaScript能使本网站更好的工作。 enable_javascript=使用 JavaScript能使本网站更好的工作。
toc=目录 toc=目录
return=返回OpenI return=返回OpenI
calculation_points=算力积分


username=用户名 username=用户名
email=电子邮件地址 email=电子邮件地址
@@ -3248,5 +3249,16 @@ load_code_failed=代码加载失败,请确认选择了正确的分支。




error.dataset_select = 数据集选择错误:数量超过限制或者有同名数据集 error.dataset_select = 数据集选择错误:数量超过限制或者有同名数据集

[points]
points = 积分
free = 免费
points_hour = 积分/每小时
balance_of_points = 积分余额:
hours = 小时
expected_time = ,预计可用
points_acquisition_instructions = 积分获取说明
insufficient_points_balance = 积分余额不足

new_train_gpu_tooltips =训练脚本存储在<strong style="color:#010101">%s</strong>中,数据集存储在<strong style="color:#010101">%s</strong>中,训练输出请存储在<strong style="color:#010101">%s</strong>中以供后续下载。 new_train_gpu_tooltips =训练脚本存储在<strong style="color:#010101">%s</strong>中,数据集存储在<strong style="color:#010101">%s</strong>中,训练输出请存储在<strong style="color:#010101">%s</strong>中以供后续下载。
new_infer_gpu_tooltips = 数据集存储在<strong style="color:#010101">%s</strong>中,模型文件存储在<strong style="color:#010101">%s</strong>中,推理输出请存储在<strong style="color:#010101">%s</strong>中以供后续下载。
new_infer_gpu_tooltips = 数据集存储在<strong style="color:#010101">%s</strong>中,模型文件存储在<strong style="color:#010101">%s</strong>中,推理输出请存储在<strong style="color:#010101">%s</strong>中以供后续下载。

+ 1
- 1
package.json View File

@@ -80,4 +80,4 @@
"browserslist": [ "browserslist": [
"defaults" "defaults"
] ]
}
}

+ 3
- 0
routers/admin/dataset.go View File

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


import ( import (
"code.gitea.io/gitea/modules/notification"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@@ -111,6 +112,8 @@ func DatasetAction(ctx *context.Context) {
if err != nil { if err != nil {
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("repo.star_fail", ctx.Params(":action")))) ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("repo.star_fail", ctx.Params(":action"))))
} else { } else {
d, _ := models.GetDatasetByID(datasetId)
notification.NotifyDatasetRecommend(ctx.User, d, ctx.Params(":action"))
ctx.JSON(http.StatusOK, models.BaseOKMessage) ctx.JSON(http.StatusOK, models.BaseOKMessage)
} }
} }


+ 1
- 0
routers/authentication/wechat.go View File

@@ -31,6 +31,7 @@ func GetQRCode4Bind(ctx *context.Context) {


r, err := createQRCode4Bind(userId) r, err := createQRCode4Bind(userId)
if err != nil { if err != nil {
log.Error("GetQRCode4Bind failed,error=%v", err)
ctx.JSON(200, map[string]interface{}{ ctx.JSON(200, map[string]interface{}{
"code": "9999", "code": "9999",
"msg": "Get QR code failed", "msg": "Get QR code failed",


+ 1
- 1
routers/authentication/wechat_event.go View File

@@ -1,9 +1,9 @@
package authentication package authentication


import ( import (
"code.gitea.io/gitea/modules/auth/wechat"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
wechat "code.gitea.io/gitea/services/wechat"
"encoding/xml" "encoding/xml"
"io/ioutil" "io/ioutil"
"time" "time"


+ 5
- 0
routers/image/image.go View File

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


import ( import (
"code.gitea.io/gitea/modules/notification"
"net/http" "net/http"
"strconv" "strconv"


@@ -25,6 +26,10 @@ func Action(ctx *context.Context) {
if err != nil { if err != nil {
ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("repo.star_fail", ctx.Params(":action")))) ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("repo.star_fail", ctx.Params(":action"))))
} else { } else {
image, err := models.GetImageByID(imageId)
if err == nil {
notification.NotifyImageRecommend(ctx.User, image, ctx.Params(":action"))
}
ctx.JSON(http.StatusOK, models.BaseOKMessage) ctx.JSON(http.StatusOK, models.BaseOKMessage)
} }
} }

+ 57
- 15
routers/repo/cloudbrain.go View File

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


import ( import (
"bufio" "bufio"
"code.gitea.io/gitea/services/cloudbrain/resource"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -16,6 +15,9 @@ import (
"time" "time"
"unicode/utf8" "unicode/utf8"


"code.gitea.io/gitea/services/cloudbrain/resource"
"code.gitea.io/gitea/services/reward/point/account"

"code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/notification"


"code.gitea.io/gitea/modules/grampus" "code.gitea.io/gitea/modules/grampus"
@@ -204,9 +206,9 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {
} }


lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), jobType, displayJobName)) lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), jobType, displayJobName))
isOk := lock.Lock(models.CloudbrainKeyDuration)
isOk, err := lock.Lock(models.CloudbrainKeyDuration)
if !isOk { if !isOk {
log.Error("The task have been processed", ctx.Data["MsgID"])
log.Error("lock processed failed:%v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx) cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tpl, &form) ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tpl, &form)
return return
@@ -314,6 +316,13 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {
return return
} }


if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) {
log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID)
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tpl, &form)
return
}

req := cloudbrain.GenerateCloudBrainTaskReq{ req := cloudbrain.GenerateCloudBrainTaskReq{
Ctx: ctx, Ctx: ctx,
DisplayJobName: displayJobName, DisplayJobName: displayJobName,
@@ -392,9 +401,9 @@ func CloudBrainInferenceJobCreate(ctx *context.Context, form auth.CreateCloudBra
tpl := tplCloudBrainInferenceJobNew tpl := tplCloudBrainInferenceJobNew


lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), jobType, displayJobName)) lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), jobType, displayJobName))
isOk := lock.Lock(models.CloudbrainKeyDuration)
isOk, err := lock.Lock(models.CloudbrainKeyDuration)
if !isOk { if !isOk {
log.Error("The task have been processed", ctx.Data["MsgID"])
log.Error("lock processed failed:%v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx) cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tpl, &form) ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tpl, &form)
return return
@@ -486,6 +495,12 @@ func CloudBrainInferenceJobCreate(ctx *context.Context, form auth.CreateCloudBra
ctx.RenderWithErr("Resource specification not available", tpl, &form) ctx.RenderWithErr("Resource specification not available", tpl, &form)
return return
} }
if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) {
log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID)
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tpl, &form)
return
}
req := cloudbrain.GenerateCloudBrainTaskReq{ req := cloudbrain.GenerateCloudBrainTaskReq{
Ctx: ctx, Ctx: ctx,
DisplayJobName: displayJobName, DisplayJobName: displayJobName,
@@ -610,6 +625,13 @@ func CloudBrainRestart(ctx *context.Context) {
} }
task.Spec = spec task.Spec = spec


if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) {
log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID)
resultCode = "-1"
errorMsg = ctx.Tr("points.insufficient_points_balance")
break
}

count, err := models.GetCloudbrainCountByUserID(ctx.User.ID, string(models.JobTypeDebug)) count, err := models.GetCloudbrainCountByUserID(ctx.User.ID, string(models.JobTypeDebug))
if err != nil { if err != nil {
log.Error("GetCloudbrainCountByUserID failed:%v", err, ctx.Data["MsgID"]) log.Error("GetCloudbrainCountByUserID failed:%v", err, ctx.Data["MsgID"])
@@ -1026,7 +1048,7 @@ func CloudBrainAdminCommitImage(ctx *context.Context, form auth.CommitAdminImage
UID: ctx.User.ID, UID: ctx.User.ID,
Type: models.GetRecommondType(form.IsRecommend), Type: models.GetRecommondType(form.IsRecommend),
Place: form.Place, Place: form.Place,
})
}, ctx.User)
if err != nil { if err != nil {
log.Error("CommitImagefailed") log.Error("CommitImagefailed")
if models.IsErrImageTagExist(err) { if models.IsErrImageTagExist(err) {
@@ -1073,7 +1095,7 @@ func CloudBrainCommitImage(ctx *context.Context, form auth.CommitImageCloudBrain
CloudBrainType: form.Type, CloudBrainType: form.Type,
Topics: validTopics, Topics: validTopics,
UID: ctx.User.ID, UID: ctx.User.ID,
})
}, ctx.User)
if err != nil { if err != nil {
log.Error("CommitImage(%s) failed:%v", ctx.Cloudbrain.JobName, err.Error(), ctx.Data["msgID"]) log.Error("CommitImage(%s) failed:%v", ctx.Cloudbrain.JobName, err.Error(), ctx.Data["msgID"])
if models.IsErrImageTagExist(err) { if models.IsErrImageTagExist(err) {
@@ -1087,7 +1109,6 @@ func CloudBrainCommitImage(ctx *context.Context, form auth.CommitImageCloudBrain


return return
} }

ctx.JSON(200, models.BaseOKMessage) ctx.JSON(200, models.BaseOKMessage)
} }


@@ -1124,6 +1145,7 @@ func CloudBrainStop(ctx *context.Context) {
log.Error("the job(%s) has been stopped", task.JobName, ctx.Data["msgID"]) log.Error("the job(%s) has been stopped", task.JobName, ctx.Data["msgID"])
resultCode = "-1" resultCode = "-1"
errorMsg = "cloudbrain.Already_stopped" errorMsg = "cloudbrain.Already_stopped"
resultCode = task.Status
break break
} }


@@ -1150,7 +1172,6 @@ func CloudBrainStop(ctx *context.Context) {
errorMsg = "cloudbrain.Stopped_success_update_status_fail" errorMsg = "cloudbrain.Stopped_success_update_status_fail"
break break
} }

status = task.Status status = task.Status
break break
} }
@@ -1205,7 +1226,7 @@ func StopJobs(cloudBrains []*models.Cloudbrain) {
}) })


logErrorAndUpdateJobStatus(err, taskInfo) logErrorAndUpdateJobStatus(err, taskInfo)
} else {
} else if taskInfo.Type == models.TypeCloudBrainTwo {
if taskInfo.JobType == string(models.JobTypeTrain) { if taskInfo.JobType == string(models.JobTypeTrain) {
err := retry(3, time.Second*30, func() error { err := retry(3, time.Second*30, func() error {
_, err := modelarts.StopTrainJob(taskInfo.JobID, strconv.FormatInt(taskInfo.VersionID, 10)) _, err := modelarts.StopTrainJob(taskInfo.JobID, strconv.FormatInt(taskInfo.VersionID, 10))
@@ -1222,8 +1243,16 @@ func StopJobs(cloudBrains []*models.Cloudbrain) {
}) })
logErrorAndUpdateJobStatus(err, taskInfo) logErrorAndUpdateJobStatus(err, taskInfo)
} }
}
} else if taskInfo.Type == models.TypeC2Net {
if taskInfo.JobType == string(models.JobTypeTrain) {
err := retry(3, time.Second*30, func() error {
_, err := grampus.StopJob(taskInfo.JobID)
return err
})
logErrorAndUpdateJobStatus(err, taskInfo)
}


}
} }
} }


@@ -2234,9 +2263,9 @@ func BenchMarkAlgorithmCreate(ctx *context.Context, form auth.CreateCloudBrainFo
ctx.Data["benchmark_child_types_id_hidden"] = benchmarkChildTypeID ctx.Data["benchmark_child_types_id_hidden"] = benchmarkChildTypeID


lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), form.JobType, displayJobName)) lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), form.JobType, displayJobName))
isOk := lock.Lock(models.CloudbrainKeyDuration)
isOk, err := lock.Lock(models.CloudbrainKeyDuration)
if !isOk { if !isOk {
log.Error("The task have been processed", ctx.Data["MsgID"])
log.Error("lock processed failed:%v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx) cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplCloudBrainBenchmarkNew, &form) ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplCloudBrainBenchmarkNew, &form)
return return
@@ -2284,6 +2313,12 @@ func BenchMarkAlgorithmCreate(ctx *context.Context, form auth.CreateCloudBrainFo
ctx.RenderWithErr("Resource specification not available", tplCloudBrainBenchmarkNew, &form) ctx.RenderWithErr("Resource specification not available", tplCloudBrainBenchmarkNew, &form)
return return
} }
if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) {
log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID)
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tplCloudBrainBenchmarkNew, &form)
return
}


count, err := models.GetBenchmarkCountByUserID(ctx.User.ID) count, err := models.GetBenchmarkCountByUserID(ctx.User.ID)
if err != nil { if err != nil {
@@ -2418,9 +2453,9 @@ func ModelBenchmarkCreate(ctx *context.Context, form auth.CreateCloudBrainForm)
command := cloudbrain.GetCloudbrainDebugCommand() command := cloudbrain.GetCloudbrainDebugCommand()


lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), jobType, displayJobName)) lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), jobType, displayJobName))
isOk := lock.Lock(models.CloudbrainKeyDuration)
isOk, err := lock.Lock(models.CloudbrainKeyDuration)
if !isOk { if !isOk {
log.Error("The task have been processed", ctx.Data["MsgID"])
log.Error("lock processed failed:%v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx) cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tpl, &form) ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tpl, &form)
return return
@@ -2512,6 +2547,13 @@ func ModelBenchmarkCreate(ctx *context.Context, form auth.CreateCloudBrainForm)
ctx.RenderWithErr("Resource specification not available", tpl, &form) ctx.RenderWithErr("Resource specification not available", tpl, &form)
return return
} }

if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) {
log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID)
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tpl, &form)
return
}
log.Info("Command=" + command) log.Info("Command=" + command)
log.Info("ModelPath=" + storage.GetMinioPath(jobName, cloudbrain.ModelMountPath+"/")) log.Info("ModelPath=" + storage.GetMinioPath(jobName, cloudbrain.ModelMountPath+"/"))
req := cloudbrain.GenerateCloudBrainTaskReq{ req := cloudbrain.GenerateCloudBrainTaskReq{


+ 20
- 5
routers/repo/grampus.go View File

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


import ( import (
"code.gitea.io/gitea/services/cloudbrain/resource"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -13,6 +12,9 @@ import (
"strings" "strings"
"time" "time"


"code.gitea.io/gitea/services/cloudbrain/resource"
"code.gitea.io/gitea/services/reward/point/account"

"code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/grampus" "code.gitea.io/gitea/modules/grampus"
@@ -217,9 +219,9 @@ func GrampusTrainJobGpuCreate(ctx *context.Context, form auth.CreateGrampusTrain
image := strings.TrimSpace(form.Image) image := strings.TrimSpace(form.Image)


lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeTrain), displayJobName)) lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeTrain), displayJobName))
isOk := lock.Lock(models.CloudbrainKeyDuration)
isOk, err := lock.Lock(models.CloudbrainKeyDuration)
if !isOk { if !isOk {
log.Error("The task have been processed", ctx.Data["MsgID"])
log.Error("lock processed failed:%v", err, ctx.Data["MsgID"])
grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU) grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU)
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplGrampusTrainJobGPUNew, &form) ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplGrampusTrainJobGPUNew, &form)
return return
@@ -301,6 +303,13 @@ func GrampusTrainJobGpuCreate(ctx *context.Context, form auth.CreateGrampusTrain
return return
} }


if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) {
log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID)
grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeGPU)
ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tplGrampusTrainJobGPUNew, &form)
return
}

//check dataset //check dataset
attachment, err := models.GetAttachmentByUUID(uuid) attachment, err := models.GetAttachmentByUUID(uuid)
if err != nil { if err != nil {
@@ -429,9 +438,9 @@ func GrampusTrainJobNpuCreate(ctx *context.Context, form auth.CreateGrampusTrain
engineName := form.EngineName engineName := form.EngineName


lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeTrain), displayJobName)) lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeTrain), displayJobName))
isOk := lock.Lock(models.CloudbrainKeyDuration)
isOk, err := lock.Lock(models.CloudbrainKeyDuration)
if !isOk { if !isOk {
log.Error("The task have been processed", ctx.Data["MsgID"])
log.Error("lock processed failed:%v", err, ctx.Data["MsgID"])
grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeNPU) grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeNPU)
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplGrampusTrainJobNPUNew, &form) ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplGrampusTrainJobNPUNew, &form)
return return
@@ -512,6 +521,12 @@ func GrampusTrainJobNpuCreate(ctx *context.Context, form auth.CreateGrampusTrain
ctx.RenderWithErr("Resource specification not available", tplGrampusTrainJobNPUNew, &form) ctx.RenderWithErr("Resource specification not available", tplGrampusTrainJobNPUNew, &form)
return return
} }
if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) {
log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID)
grampusTrainJobNewDataPrepare(ctx, grampus.ProcessorTypeNPU)
ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tplGrampusTrainJobNPUNew, &form)
return
}


//check dataset //check dataset
attachment, err := models.GetAttachmentByUUID(uuid) attachment, err := models.GetAttachmentByUUID(uuid)


+ 41
- 10
routers/repo/modelarts.go View File

@@ -17,6 +17,7 @@ import (


"code.gitea.io/gitea/modules/modelarts_cd" "code.gitea.io/gitea/modules/modelarts_cd"
"code.gitea.io/gitea/services/cloudbrain/resource" "code.gitea.io/gitea/services/cloudbrain/resource"
"code.gitea.io/gitea/services/reward/point/account"


"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/auth"
@@ -213,9 +214,9 @@ func Notebook2Create(ctx *context.Context, form auth.CreateModelArtsNotebookForm
repo := ctx.Repo.Repository repo := ctx.Repo.Repository


lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeDebug), displayJobName)) lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeDebug), displayJobName))
isOk := lock.Lock(models.CloudbrainKeyDuration)
isOk, err := lock.Lock(models.CloudbrainKeyDuration)
if !isOk { if !isOk {
log.Error("The task have been processed", ctx.Data["MsgID"])
log.Error("lock processed failed:%v", err, ctx.Data["MsgID"])
notebookNewDataPrepare(ctx) notebookNewDataPrepare(ctx)
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplModelArtsNotebookNew, &form) ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplModelArtsNotebookNew, &form)
return return
@@ -267,6 +268,13 @@ func Notebook2Create(ctx *context.Context, form auth.CreateModelArtsNotebookForm
ctx.RenderWithErr("Resource specification not available", tplModelArtsNotebookNew, &form) ctx.RenderWithErr("Resource specification not available", tplModelArtsNotebookNew, &form)
return return
} }
if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) {
log.Error("point balance is not enough,userId=%d specId=%d ", ctx.User.ID, spec.ID)
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tplModelArtsNotebookNew, &form)
return
}

if setting.ModelartsCD.Enabled { if setting.ModelartsCD.Enabled {
err = modelarts_cd.GenerateNotebook(ctx, displayJobName, jobName, uuid, description, imageId, spec) err = modelarts_cd.GenerateNotebook(ctx, displayJobName, jobName, uuid, description, imageId, spec)
} else { } else {
@@ -474,7 +482,11 @@ func NotebookRestart(ctx *context.Context) {
errorMsg = "Resource specification not support any more" errorMsg = "Resource specification not support any more"
break break
} }

if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) {
log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID)
errorMsg = ctx.Tr("points.insufficient_points_balance")
break
}
createTime := timeutil.TimeStampNow() createTime := timeutil.TimeStampNow()
param := models.NotebookAction{ param := models.NotebookAction{
Action: models.ActionStart, Action: models.ActionStart,
@@ -1155,9 +1167,9 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm)
} }


lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeTrain), displayJobName)) lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeTrain), displayJobName))
isOk := lock.Lock(models.CloudbrainKeyDuration)
isOk, err := lock.Lock(models.CloudbrainKeyDuration)
if !isOk { if !isOk {
log.Error("The task have been processed", ctx.Data["MsgID"])
log.Error("lock processed failed:%v", err, ctx.Data["MsgID"])
trainJobErrorNewDataPrepare(ctx, form) trainJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplModelArtsTrainJobNew, &form) ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplModelArtsTrainJobNew, &form)
return return
@@ -1204,6 +1216,13 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm)
ctx.RenderWithErr("Resource specification not available", tplModelArtsTrainJobNew, &form) ctx.RenderWithErr("Resource specification not available", tplModelArtsTrainJobNew, &form)
return return
} }
if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) {
log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID)
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tplModelArtsTrainJobNew, &form)
return
}

//Determine whether the task name of the task in the project is duplicated //Determine whether the task name of the task in the project is duplicated
tasks, err := models.GetCloudbrainsByDisplayJobName(repo.ID, string(models.JobTypeTrain), displayJobName) tasks, err := models.GetCloudbrainsByDisplayJobName(repo.ID, string(models.JobTypeTrain), displayJobName)
if err == nil { if err == nil {
@@ -1530,9 +1549,9 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ
isLatestVersion := modelarts.IsLatestVersion isLatestVersion := modelarts.IsLatestVersion


lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeTrain), displayJobName)) lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeTrain), displayJobName))
isOk := lock.Lock(models.CloudbrainKeyDuration)
isOk, err := lock.Lock(models.CloudbrainKeyDuration)
if !isOk { if !isOk {
log.Error("The task have been processed", ctx.Data["MsgID"])
log.Error("lock processed failed:%v", err, ctx.Data["MsgID"])
versionErrorDataPrepare(ctx, form) versionErrorDataPrepare(ctx, form)
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplModelArtsTrainJobVersionNew, &form) ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplModelArtsTrainJobVersionNew, &form)
return return
@@ -1571,6 +1590,12 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ
ctx.RenderWithErr("Resource specification not available", tplModelArtsTrainJobVersionNew, &form) ctx.RenderWithErr("Resource specification not available", tplModelArtsTrainJobVersionNew, &form)
return return
} }
if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) {
log.Error("point balance is not enough,userId=%d specId=%d", ctx.User.ID, spec.ID)
versionErrorDataPrepare(ctx, form)
ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tplModelArtsTrainJobVersionNew, &form)
return
}


//todo: del the codeLocalPath //todo: del the codeLocalPath
_, err = ioutil.ReadDir(codeLocalPath) _, err = ioutil.ReadDir(codeLocalPath)
@@ -2036,7 +2061,6 @@ func TrainJobStop(ctx *context.Context) {
ctx.RenderWithErr(err.Error(), tplModelArtsTrainJobIndex, nil) ctx.RenderWithErr(err.Error(), tplModelArtsTrainJobIndex, nil)
return return
} }

ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/train-job?listType=" + listType) ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/train-job?listType=" + listType)
} }


@@ -2146,9 +2170,9 @@ func InferenceJobCreate(ctx *context.Context, form auth.CreateModelArtsInference
} }


lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeInference), displayJobName)) lock := redis_lock.NewDistributeLock(redis_key.CloudbrainBindingJobNameKey(fmt.Sprint(repo.ID), string(models.JobTypeInference), displayJobName))
isOk := lock.Lock(models.CloudbrainKeyDuration)
isOk, err := lock.Lock(models.CloudbrainKeyDuration)
if !isOk { if !isOk {
log.Error("The task have been processed", ctx.Data["MsgID"])
log.Error("lock processed failed:%v", err, ctx.Data["MsgID"])
inferenceJobErrorNewDataPrepare(ctx, form) inferenceJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplModelArtsInferenceJobNew, &form) ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_samejob_err"), tplModelArtsInferenceJobNew, &form)
return return
@@ -2213,6 +2237,13 @@ func InferenceJobCreate(ctx *context.Context, form auth.CreateModelArtsInference
ctx.RenderWithErr("Resource specification not available", tplModelArtsInferenceJobNew, &form) ctx.RenderWithErr("Resource specification not available", tplModelArtsInferenceJobNew, &form)
return return
} }
if !account.IsPointBalanceEnough(ctx.User.ID, spec.UnitPrice) {
log.Error("point balance is not enough,userId=%d specId=%d ", ctx.User.ID, spec.ID)
inferenceJobErrorNewDataPrepare(ctx, form)
ctx.RenderWithErr(ctx.Tr("points.insufficient_points_balance"), tplModelArtsInferenceJobNew, &form)
return
}

//todo: del the codeLocalPath //todo: del the codeLocalPath
_, err = ioutil.ReadDir(codeLocalPath) _, err = ioutil.ReadDir(codeLocalPath)
if err == nil { if err == nil {


+ 24
- 0
routers/reward/point/account.go View File

@@ -0,0 +1,24 @@
package point

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/routers/response"
"code.gitea.io/gitea/services/reward/point/account"
"net/http"
)

func SearchPointAccount(ctx *context.Context) {
q := ctx.Query("q")
page := ctx.QueryInt("page")
resopnse, err := account.SearchPointAccount(models.SearchPointAccountOpts{ListOptions: models.ListOptions{Page: page, PageSize: 20}, Keyword: q})
if err != nil {
log.Error("SearchPointAccount error.%v", err)
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
return
}

ctx.JSON(http.StatusOK, response.SuccessWithData(resopnse))
return
}

+ 45
- 0
routers/reward/point/limit.go View File

@@ -0,0 +1,45 @@
package point

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/routers/response"
"code.gitea.io/gitea/services/reward/limiter"
"net/http"
)

func GetSingleDailyPointLimitConfig(ctx *context.Context) {
r, err := limiter.GetSingleDailyPointLimitConfig()
if err != nil {
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
return
}
resultMap := make(map[string]interface{}, 0)
if r == nil {
resultMap["LimitNum"] = ""
} else {
resultMap["LimitNum"] = r.LimitNum
}
ctx.JSON(http.StatusOK, response.SuccessWithData(resultMap))
}

func SetSingleDailyPointLimitConfig(ctx *context.Context, config models.LimitConfigVO) {
err := limiter.SetSingleDailyPointLimitConfig(config.LimitNum, ctx.User)
if err != nil {
log.Error("Set single daily point limit config error. %v", err)
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
return
}
ctx.JSON(http.StatusOK, response.Success())
}

func DeletePointLimitConfig(ctx *context.Context) {
id := ctx.QueryInt64("id")
err := limiter.DeleteLimitConfig(id, ctx.User)
if err != nil {
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
return
}
ctx.JSON(http.StatusOK, response.Success())
}

+ 158
- 0
routers/reward/point/point.go View File

@@ -0,0 +1,158 @@
package point

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/reward"
"code.gitea.io/gitea/services/reward/point/account"
"errors"
"net/http"
)

const tplPoint base.TplName = "reward/point"
const tplPointRule base.TplName = "reward/point/rule"

type AccountResponse struct {
Balance int64
TotalEarned int64
TotalConsumed int64
}

func GetPointAccount(ctx *context.Context) {
userId := ctx.User.ID
a, err := account.GetAccount(userId)
if err != nil {
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
return
}
res := &AccountResponse{
Balance: a.Balance,
TotalEarned: a.TotalEarned,
TotalConsumed: a.TotalConsumed,
}
ctx.JSON(http.StatusOK, response.SuccessWithData(res))
}

func GetPointRecordList(ctx *context.Context) {
operateType := ctx.Query("Operate")
page := ctx.QueryInt("Page")
var orderBy models.RewardOperateOrderBy
switch ctx.Query("sort") {
default:
orderBy = models.RewardOrderByIDDesc
}
t := models.GetRewardOperateTypeInstance(operateType)
if t == "" {
ctx.JSON(http.StatusOK, response.ServerError("param error"))
return
}

r, err := reward.GetRewardRecordList(&models.RewardRecordListOpts{
ListOptions: models.ListOptions{PageSize: 10, Page: page},
UserId: ctx.User.ID,
OperateType: t,
RewardType: models.RewardTypePoint,
OrderBy: orderBy,
IsAdmin: false,
UserName: ctx.User.Name,
})
if err != nil {
log.Error("GetPointRecordList error.%v", err)
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
return
}

ctx.JSON(http.StatusOK, response.SuccessWithData(r))
return
}

func OperatePointAccountBalance(ctx *context.Context, req models.AdminRewardOperateReq) {
req.RewardType = models.RewardTypePoint
if req.OperateType.Name() == "" || req.Remark == "" {
ctx.JSON(http.StatusOK, "param error")
return
}
err := reward.AdminBalanceOperate(req, ctx.User)
if err != nil {
log.Error("OperatePointAccountBalance error.%v", err)
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
return
}
ctx.JSON(http.StatusOK, response.Success())
}

func GetPointPage(ctx *context.Context) {
ctx.HTML(200, tplPoint)
}

func GetRulePage(ctx *context.Context) {
ctx.HTML(200, tplPointRule)
}

func GetAdminRewardList(ctx *context.Context) {
opts, err := buildAdminRewardRecordListOpts(ctx)
if err != nil {
log.Error("buildAdminRewardRecordListOpts error.%v", err)
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
return
}

username := ctx.Query("userName")
if username != "" {
user, err := models.GetUserByName(username)
if err != nil {
log.Error("GetUserByName error.%v", err)
if models.IsErrUserNotExist(err) {
ctx.JSON(http.StatusOK, response.ServerError("user not exist"))
} else {
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
}
return
}
opts.UserId = user.ID
opts.UserName = user.Name
}

r, err := reward.GetRewardRecordList(opts)
if err != nil {
log.Error("GetRewardRecordList error.%v", err)
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
return
}

ctx.JSON(http.StatusOK, response.SuccessWithData(r))
}

func buildAdminRewardRecordListOpts(ctx *context.Context) (*models.RewardRecordListOpts, error) {
operateType := ctx.Query("operate")
sourceType := ctx.Query("source")
actionType := ctx.QueryInt("action")
serialNo := ctx.Query("serialNo")
status := ctx.Query("status")

page := ctx.QueryInt("page")
var orderBy models.RewardOperateOrderBy
switch ctx.Query("sort") {
default:
orderBy = models.RewardOrderByIDDesc
}
t := models.GetRewardOperateTypeInstance(operateType)
if t == "" {
return nil, errors.New("param error")
}
opts := &models.RewardRecordListOpts{
ListOptions: models.ListOptions{PageSize: 10, Page: page},
OperateType: t,
RewardType: models.RewardTypePoint,
OrderBy: orderBy,
SourceType: sourceType,
ActionType: actionType,
SerialNo: serialNo,
IsAdmin: true,
Status: status,
}
return opts, nil
}

+ 36
- 10
routers/routes/routes.go View File

@@ -6,6 +6,9 @@ package routes


import ( import (
"bytes" "bytes"
"code.gitea.io/gitea/routers/reward/point"
"code.gitea.io/gitea/routers/task"
"code.gitea.io/gitea/services/reward"
"encoding/gob" "encoding/gob"
"net/http" "net/http"
"path" "path"
@@ -328,6 +331,8 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/", routers.Home) m.Get("/", routers.Home)
m.Get("/dashboard", routers.Dashboard) m.Get("/dashboard", routers.Dashboard)
go routers.SocketManager.Run() go routers.SocketManager.Run()
go task.RunTask()
go reward.AcceptStatusChangeAction()
m.Get("/action/notification", routers.ActionNotification) m.Get("/action/notification", routers.ActionNotification)
m.Get("/recommend/home", routers.RecommendHomeInfo) m.Get("/recommend/home", routers.RecommendHomeInfo)
//m.Get("/recommend/org", routers.RecommendOrgFromPromote) //m.Get("/recommend/org", routers.RecommendOrgFromPromote)
@@ -640,6 +645,20 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Group("/operation", func() { m.Group("/operation", func() {
m.Get("/config/recommend_org", operation.Organizations) m.Get("/config/recommend_org", operation.Organizations)
m.Post("/config/recommend_org", bindIgnErr(operation.OrgInfos{}), operation.UpdateRecommendOrganizations) m.Post("/config/recommend_org", bindIgnErr(operation.OrgInfos{}), operation.UpdateRecommendOrganizations)

m.Group("/reward/point", func() {
m.Combo("/limiter/single-daily").Get(point.GetSingleDailyPointLimitConfig).Post(bindIgnErr(models.LimitConfigVO{}), point.SetSingleDailyPointLimitConfig)
m.Post("/limiter/delete", point.DeletePointLimitConfig)
m.Get("/account/search", point.SearchPointAccount)
m.Post("/account/operate", binding.Bind(models.AdminRewardOperateReq{}), point.OperatePointAccountBalance)
m.Get("/list", point.GetAdminRewardList)
})

m.Group("/task/config", func() {
m.Get("/list", task.GetTaskConfigList)
m.Post("/add/batch", bindIgnErr(models.BatchLimitConfigVO{}), task.BatchAddTaskConfig)
m.Post("/^:action(new|edit|del)$", bindIgnErr(models.TaskConfigWithLimit{}), task.OperateTaskConfig)
})
}, operationReq) }, operationReq)
// ***** END: Operation ***** // ***** END: Operation *****


@@ -1113,7 +1132,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/models", reqRepoCloudBrainReader, repo.CloudBrainShowModels) m.Get("/models", reqRepoCloudBrainReader, repo.CloudBrainShowModels)
m.Get("/download_model", cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainDownloadModel) m.Get("/download_model", cloudbrain.AdminOrJobCreaterRight, repo.CloudBrainDownloadModel)
}) })
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.CloudBrainNew)
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.CloudBrainNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate) m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate)


m.Group("/benchmark", func() { m.Group("/benchmark", func() {
@@ -1124,7 +1143,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.BenchmarkDel) m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.BenchmarkDel)
m.Get("/rate", reqRepoCloudBrainReader, repo.GetRate) m.Get("/rate", reqRepoCloudBrainReader, repo.GetRate)
}) })
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.CloudBrainBenchmarkNew)
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.CloudBrainBenchmarkNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainBenchmarkCreate) m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainBenchmarkCreate)
m.Get("/get_child_types", repo.GetChildTypes) m.Get("/get_child_types", repo.GetChildTypes)
}) })
@@ -1138,7 +1157,7 @@ func RegisterRoutes(m *macaron.Macaron) {
//m.Get("/get_log", cloudbrain.AdminOrJobCreaterRightForTrain, repo.GetLogFromModelDir) //m.Get("/get_log", cloudbrain.AdminOrJobCreaterRightForTrain, repo.GetLogFromModelDir)
//m.Post("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRightForTrain, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreateVersion) //m.Post("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRightForTrain, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreateVersion)
}) })
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.CloudBrainTrainJobNew)
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.CloudBrainTrainJobNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate) m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate)
}) })
m.Group("/inference-job", func() { m.Group("/inference-job", func() {
@@ -1148,7 +1167,7 @@ func RegisterRoutes(m *macaron.Macaron) {


m.Get("/downloadall", repo.DownloadInferenceResultFile) m.Get("/downloadall", repo.DownloadInferenceResultFile)
}) })
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.InferenceCloudBrainJobNew)
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.InferenceCloudBrainJobNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainInferencForm{}), repo.CloudBrainInferenceJobCreate) m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainInferencForm{}), repo.CloudBrainInferenceJobCreate)
}) })
}, context.RepoRef()) }, context.RepoRef())
@@ -1161,11 +1180,11 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/model_download", cloudbrain.AdminOrJobCreaterRightForTrain, repo.ModelDownload) m.Get("/model_download", cloudbrain.AdminOrJobCreaterRightForTrain, repo.ModelDownload)
}) })
m.Group("/gpu", func() { m.Group("/gpu", func() {
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.GrampusTrainJobGPUNew)
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.GrampusTrainJobGPUNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateGrampusTrainJobForm{}), repo.GrampusTrainJobGpuCreate) m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateGrampusTrainJobForm{}), repo.GrampusTrainJobGpuCreate)
}) })
m.Group("/npu", func() { m.Group("/npu", func() {
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.GrampusTrainJobNPUNew)
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.GrampusTrainJobNPUNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateGrampusTrainJobForm{}), repo.GrampusTrainJobNpuCreate) m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateGrampusTrainJobForm{}), repo.GrampusTrainJobNpuCreate)
}) })
}) })
@@ -1222,7 +1241,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.NotebookStop) m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.NotebookStop)
m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.NotebookDel) m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.NotebookDel)
}) })
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.NotebookNew)
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.NotebookNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsNotebookForm{}), repo.Notebook2Create) m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsNotebookForm{}), repo.Notebook2Create)
}) })


@@ -1234,10 +1253,10 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRightForTrain, repo.TrainJobDel) m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRightForTrain, repo.TrainJobDel)
m.Get("/model_download", cloudbrain.AdminOrJobCreaterRightForTrain, repo.ModelDownload) m.Get("/model_download", cloudbrain.AdminOrJobCreaterRightForTrain, repo.ModelDownload)
m.Get("/download_log_file", cloudbrain.AdminOrJobCreaterRightForTrain, repo.TrainJobDownloadLogFile) m.Get("/download_log_file", cloudbrain.AdminOrJobCreaterRightForTrain, repo.TrainJobDownloadLogFile)
m.Get("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRightForTrain, repo.TrainJobNewVersion)
m.Get("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRightForTrain, context.PointAccount(), repo.TrainJobNewVersion)
m.Post("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRightForTrain, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreateVersion) m.Post("/create_version", reqWechatBind, cloudbrain.AdminOrJobCreaterRightForTrain, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreateVersion)
}) })
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.TrainJobNew)
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.TrainJobNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreate) m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsTrainJobForm{}), repo.TrainJobCreate)


m.Get("/para-config-list", reqRepoCloudBrainReader, repo.TrainJobGetConfigList) m.Get("/para-config-list", reqRepoCloudBrainReader, repo.TrainJobGetConfigList)
@@ -1250,7 +1269,7 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("/result_download", cloudbrain.AdminOrJobCreaterRightForTrain, repo.ResultDownload) m.Get("/result_download", cloudbrain.AdminOrJobCreaterRightForTrain, repo.ResultDownload)
m.Get("/downloadall", repo.DownloadMultiResultFile) m.Get("/downloadall", repo.DownloadMultiResultFile)
}) })
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, repo.InferenceJobNew)
m.Get("/create", reqWechatBind, reqRepoCloudBrainWriter, context.PointAccount(), repo.InferenceJobNew)
m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsInferenceJobForm{}), repo.InferenceJobCreate) m.Post("/create", reqWechatBind, reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsInferenceJobForm{}), repo.InferenceJobCreate)
}) })
}, context.RepoRef()) }, context.RepoRef())
@@ -1410,6 +1429,13 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Post("/purge", user.NotificationPurgePost) m.Post("/purge", user.NotificationPurgePost)
}, reqSignIn) }, reqSignIn)


m.Group("/reward/point", func() {
m.Get("", point.GetPointPage)
m.Get("/rule", point.GetRulePage)
m.Get("/account", point.GetPointAccount)
m.Get("/record/list", point.GetPointRecordList)
}, reqSignIn)

if setting.API.EnableSwagger { if setting.API.EnableSwagger {
m.Get("/swagger.v1.json", templates.JSONRenderer(), routers.SwaggerV1Json) m.Get("/swagger.v1.json", templates.JSONRenderer(), routers.SwaggerV1Json)
} }


+ 68
- 0
routers/task/config.go View File

@@ -0,0 +1,68 @@
package task

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/routers/response"
"code.gitea.io/gitea/services/task"
"errors"
"net/http"
)

func GetTaskConfigList(ctx *context.Context) {
page := ctx.QueryInt("Page")
status := ctx.QueryInt("Status")
action := ctx.QueryInt("Action")
r, err := task.GetTaskConfigWithLimitList(models.GetTaskConfigOpts{
ListOptions: models.ListOptions{PageSize: 20, Page: page},
Status: status,
ActionType: action,
})
if err != nil {
log.Error("GetTaskConfigList error.%v", err)
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
return
}
ctx.JSON(http.StatusOK, response.SuccessWithData(r))
}

func OperateTaskConfig(ctx *context.Context, config models.TaskConfigWithLimit) {
action := ctx.Params(":action")

var err error
switch action {
case "edit":
err = task.EditTaskConfig(config, ctx.User)
case "new":
err = task.AddTaskConfig(config, ctx.User)
case "del":
err = task.DelTaskConfig(config.ID, ctx.User)
default:
err = errors.New("action type error")
}

if err != nil {
log.Error("OperateTaskConfig error ,%v", err)
ctx.JSON(http.StatusOK, response.ServerError(err.Error()))
return
}
ctx.JSON(http.StatusOK, response.Success())
}
func BatchAddTaskConfig(ctx *context.Context, list models.BatchLimitConfigVO) {
successCount := 0
failCount := 0
for _, config := range list.ConfigList {
err := task.AddTaskConfig(config, ctx.User)
if err != nil {
failCount++
} else {
successCount++
}
}
r := make(map[string]int, 2)
r["successCount"] = successCount
r["failCount"] = failCount
log.Debug("BatchAddTaskConfig success.result=%v", r)
ctx.JSON(http.StatusOK, response.SuccessWithData(r))
}

+ 15
- 0
routers/task/task.go View File

@@ -0,0 +1,15 @@
package task

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/services/task"
)

func RunTask() {
for {
select {
case action := <-models.ActionChan4Task:
task.Accomplish(action)
}
}
}

+ 2
- 0
routers/user/setting/profile.go View File

@@ -6,6 +6,7 @@
package setting package setting


import ( import (
"code.gitea.io/gitea/modules/notification"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@@ -179,6 +180,7 @@ func AvatarPost(ctx *context.Context, form auth.AvatarForm) {
if err := UpdateAvatarSetting(ctx, form, ctx.User); err != nil { if err := UpdateAvatarSetting(ctx, form, ctx.User); err != nil {
ctx.Flash.Error(err.Error()) ctx.Flash.Error(err.Error())
} else { } else {
notification.NotifyChangeUserAvatar(ctx.User, form)
ctx.Flash.Success(ctx.Tr("settings.update_avatar_success")) ctx.Flash.Success(ctx.Tr("settings.update_avatar_success"))
} }




+ 1
- 1
services/phone/phone.go View File

@@ -46,7 +46,7 @@ func SendVerifyCode(conn redis.Conn, phoneNumber string) error {
if err != nil { if err != nil {
return err return err
} }
err = redis_client.Expire(conn, timesKey, getRemainSecondOfDay(time.Now()))
err = redis_client.EXPIRE(conn, timesKey, getRemainSecondOfDay(time.Now()))
if err != nil { if err != nil {
return err return err
} }


+ 50
- 0
services/reward/admin_operate.go View File

@@ -0,0 +1,50 @@
package reward

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/util"
)

func AdminBalanceOperate(req models.AdminRewardOperateReq, doer *models.User) error {
logId := util.UUID()
_, err := models.InsertRewardAdminLog(&models.RewardAdminLog{
LogId: logId,
Amount: req.Amount,
RewardType: req.RewardType.Name(),
TargetUserId: req.TargetUserId,
CreatorId: doer.ID,
CreatorName: doer.Name,
Remark: req.Remark,
Status: models.RewardAdminLogProcessing,
})
if err != nil {
log.Error("AdminBalanceOperate InsertRewardAdminLog error.%v", err)
return err
}

//reward
err = Operate(&models.RewardOperateContext{
SourceType: models.SourceTypeAdminOperate,
SourceId: logId,
Title: "管理员操作",
Reward: models.Reward{
Amount: req.Amount,
Type: req.RewardType,
},
TargetUserId: req.TargetUserId,
RequestId: logId,
OperateType: req.OperateType,
Remark: req.Remark,
RejectPolicy: models.JustReject,
PermittedNegative: true,
})

if err != nil {
log.Error("AdminBalanceOperate operate error.%v", err)
models.UpdateRewardAdminLogStatus(logId, models.RewardAdminLogProcessing, models.RewardAdminLogFailed)
return err
}
models.UpdateRewardAdminLogStatus(logId, models.RewardAdminLogProcessing, models.RewardAdminLogSuccess)
return nil
}

+ 133
- 0
services/reward/cloudbrain_deduct.go View File

@@ -0,0 +1,133 @@
package reward

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"fmt"
"time"
)

var (
ResourceSpecs *models.ResourceSpecs
TrainResourceSpecs *models.ResourceSpecs
)

const RUN_CLOUDBRAIN_TASK_TITTLE = "运行云脑任务"

func AcceptStatusChangeAction() {
for {
select {
case task := <-models.StatusChangeChan:
DeductPoint4Cloudbrain(*task, time.Now())
}
}
}

func StartAndGetCloudBrainPointDeductTask(task models.Cloudbrain) (*models.RewardPeriodicTask, error) {
if !setting.CloudBrainPaySwitch {
return nil, nil
}

unitPrice, err := models.GetCloudbrainTaskUnitPrice(task.ID)
if err != nil {
return nil, err
}
if unitPrice == 0 {
log.Debug("finish StartAndGetCloudBrainPointDeductTask, UnitPrice = 0 task.ID=%d", task.ID)
return nil, nil
}

return StartAndGetPeriodicTask(&models.StartPeriodicTaskOpts{
SourceType: models.SourceTypeRunCloudbrainTask,
SourceId: getCloudBrainPointTaskSourceId(task),
TargetUserId: task.UserID,
RequestId: getCloudBrainPointTaskSourceId(task),
OperateType: models.OperateTypeDecrease,
Delay: setting.CloudBrainPayDelay,
Interval: setting.CloudBrainPayInterval,
UnitAmount: unitPrice,
RewardType: models.RewardTypePoint,
StartTime: time.Unix(int64(task.StartTime), 0),
Title: RUN_CLOUDBRAIN_TASK_TITTLE,
})
}

func StopCloudBrainPointDeductTask(task models.Cloudbrain) {
StopPeriodicTask(models.SourceTypeRunCloudbrainTask, getCloudBrainPointTaskSourceId(task), models.OperateTypeDecrease)
}

func getCloudBrainPointTaskSourceId(task models.Cloudbrain) string {
return fmt.Sprint(task.ID)
}

var firstTimeFlag = true

func StartCloudbrainPointDeductTask() {
defer func() {
if err := recover(); err != nil {
combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
log.Error("PANIC:%v", combinedErr)
}
}()
log.Debug("try to run CloudbrainPointDeductTask")
end := time.Now()
start := end.Add(-1 * setting.DeductTaskRange)
if firstTimeFlag {
//When it is executed for the first time, it needs to process the tasks of the last 3 hours.
//This is done to prevent the application from hanging for a long time
start = end.Add(-1 * setting.DeductTaskRangeForFirst)
firstTimeFlag = false
}
taskList, err := models.GetStartedCloudbrainTaskByUpdatedUnix(start, end)
if err != nil {
log.Error("GetStartedCloudbrainTaskByUpdatedUnix error. %v", err)
return
}
if taskList == nil || len(taskList) == 0 {
log.Debug("No cloudbrain task need handled")
return
}
for _, t := range taskList {
DeductPoint4Cloudbrain(t, end)
}
log.Debug("CloudbrainPointDeductTask completed")
}

func DeductPoint4Cloudbrain(t models.Cloudbrain, now time.Time) error {
defer func() {
if err := recover(); err != nil {
combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
log.Error("PANIC:%v", combinedErr)
}
}()
log.Debug("start to deduct point for cloudbrain[%d]", t.ID)
if t.StartTime == 0 {
log.Debug("cloudbrain[%d] task not start", t.ID)
return nil
}

task, err := StartAndGetCloudBrainPointDeductTask(t)
if err != nil {
log.Error("run cloudbrain point deduct task error,err=%v", err)
return err
}
if task == nil {
log.Debug("cloudbrain[%d] deduct task is nil")
return nil
}
if task.Status == models.PeriodicTaskStatusFinished {
log.Info("Periodic task is finished")
return nil
}

if t.EndTime > 0 {
endTime := time.Unix(int64(t.EndTime), 0)
RunRewardTask(*task, endTime)
models.StopPeriodicTask(task.ID, task.OperateSerialNo, endTime)
} else {
RunRewardTask(*task, now)
}
log.Debug("finished deduct point for cloudbrain[%d]", t.ID)
return nil
}

+ 100
- 0
services/reward/limiter/config.go View File

@@ -0,0 +1,100 @@
package limiter

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
)

func GetSingleDailyPointLimitConfig() (*models.LimitConfigVO, error) {
r, err := GetLimitConfigList(models.LimitConfigQueryOpts{
RefreshRate: models.PeriodDaily,
Scope: models.LimitScopeSingleUser,
LimitCode: models.SourceTypeAccomplishTask.Name(),
LimitType: models.LimitTypeRewardPoint,
})
if err != nil {
return nil, err
}
if r == nil || len(r) == 0 {
return nil, nil
}
return r[0], nil
}

func SetSingleDailyPointLimitConfig(limitNum int64, doer *models.User) error {
l := &models.LimitConfigVO{
RefreshRate: models.PeriodDaily,
Scope: models.LimitScopeSingleUser.Name(),
LimitCode: models.SourceTypeAccomplishTask.Name(),
LimitType: models.LimitTypeRewardPoint.Name(),
LimitNum: limitNum,
}
return AddLimitConfig(l, doer)
}

func GetLimitConfigList(opts models.LimitConfigQueryOpts) ([]*models.LimitConfigVO, error) {
r, err := GetLimitersByLimitType(opts.LimitType)
if err != nil {
log.Error("GetLimitConfigList error when getting limiters by limit type.err=%v", err)
return nil, err
}
result := make([]*models.LimitConfigVO, 0)
for _, v := range r {
if opts.LimitCode != "" && opts.LimitCode != v.LimitCode {
continue
}
if opts.Scope != "" && opts.Scope.Name() != v.Scope {
continue
}
if opts.RefreshRate != "" && opts.RefreshRate != v.RefreshRate {
continue
}
if opts.LimitType != "" && opts.LimitType.Name() != v.LimitType {
continue
}
result = append(result, v.ToLimitConfigVO())
}
return result, nil
}
func GetLimitConfigById(id int64) (*models.LimitConfig, error) {
return models.GetLimitConfigById(id)
}

func AddLimitConfig(config *models.LimitConfigVO, doer *models.User) error {
r := &models.LimitConfig{
Title: config.Title,
RefreshRate: config.RefreshRate,
Scope: config.Scope,
LimitNum: config.LimitNum,
LimitCode: config.LimitCode,
LimitType: config.LimitType,
CreatorId: doer.ID,
CreatorName: doer.Name,
}
err := models.AddLimitConfig(r)

if err != nil {
log.Error("add limit config error,config:%v err:%v", config, err)
return err
}
redis_client.Del(redis_key.LimitConfig(config.LimitType))
return nil
}

func DeleteLimitConfig(id int64, doer *models.User) error {
config, err := GetLimitConfigById(id)
if err != nil {
log.Error("GetLimitConfigById err,e=%v", err)
return err
}
err = models.DeleteLimitConfig(*config, doer.ID, doer.Name)

if err != nil {
log.Error("add limit config error,config:%v err:%v", config, err)
return err
}
redis_client.Del(redis_key.LimitConfig(config.LimitType))
return nil
}

+ 258
- 0
services/reward/limiter/limiter.go View File

@@ -0,0 +1,258 @@
package limiter

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
"code.gitea.io/gitea/services/task/period"
"encoding/json"
"errors"
"fmt"
"time"
)

type limiterRunner struct {
limiters []models.LimitConfig
index int
userId int64
amount int64
limitCode string
limitType models.LimitType
rejectPolicy models.LimiterRejectPolicy
resultMap map[int]limitResult
minRealAmount int64
}

type limitResult struct {
isLoss bool
planAmount int64
realAmount int64
}

func newLimitResult(isLoss bool, planAmount int64, realAmount int64) limitResult {
return limitResult{
isLoss: isLoss,
planAmount: planAmount,
realAmount: realAmount,
}
}

func newLimiterRunner(limitCode string, limitType models.LimitType, userId, amount int64, policy models.LimiterRejectPolicy) *limiterRunner {
return &limiterRunner{
userId: userId,
amount: amount,
limitCode: limitCode,
limitType: limitType,
index: 0,
rejectPolicy: policy,
resultMap: make(map[int]limitResult, 0),
}
}

//Run run all limiters
//return real used amount(when choose the FillUp reject policy, amount may only be partially used)
func (l *limiterRunner) Run() error {
if err := l.LoadLimiters(); err != nil {
return err
}
l.minRealAmount = l.amount
for l.index < len(l.limiters) {
err := l.limit(l.limiters[l.index])
if err != nil {
log.Info("limiter check failed,%v", err)
l.Rollback()
return err
}
result := l.resultMap[l.index]
if result.isLoss {
//find the minimum real amount
if l.minRealAmount > result.realAmount {
l.minRealAmount = result.realAmount
}
}
l.index += 1
}

//post process
l.PostProcess()
return nil
}

//Rollback rollback the usedNum from limiters[0] to limiters[index]
func (l *limiterRunner) Rollback() error {
for i := l.index - 1; i >= 0; i-- {
l.rollback(l.limiters[i], l.resultMap[i])
}
return nil
}

func (l *limiterRunner) rollback(r models.LimitConfig, result limitResult) error {
p, err := period.GetPeriod(r.RefreshRate)
if err != nil {
return err
}
redisKey := redis_key.LimitCount(l.userId, r.LimitCode, r.LimitType, r.Scope, p)
redis_client.IncrBy(redisKey, -1*result.realAmount)
return nil
}

//PostProcess process loss,if realAmount < planAmount
func (l *limiterRunner) PostProcess() error {
for i := l.index - 1; i >= 0; i-- {
l.postProcess(l.limiters[i], l.resultMap[i])
}
return nil
}

func (l *limiterRunner) postProcess(r models.LimitConfig, result limitResult) error {
if result.realAmount == l.minRealAmount {
return nil
}
p, err := period.GetPeriod(r.RefreshRate)
if err != nil {
return err
}
diff := result.realAmount - l.minRealAmount
redisKey := redis_key.LimitCount(l.userId, r.LimitCode, r.LimitType, r.Scope, p)
redis_client.IncrBy(redisKey, -1*diff)
return nil
}

func (l *limiterRunner) limit(r models.LimitConfig) error {
p, err := period.GetPeriod(r.RefreshRate)
if err != nil {
return err
}
redisKey := redis_key.LimitCount(l.userId, r.LimitCode, r.LimitType, r.Scope, p)
usedNum, err := redis_client.IncrBy(redisKey, l.amount)
if err != nil {
return err
}
//if usedNum equals amount,it is the first operation in period or redis cache deleted
//count in database to distinguish the two cases
if usedNum == l.amount {
n, err := l.countInPeriod(r, p)
if err != nil {
return err
}
if n > 0 {
//means redis cache deleted,incr the cache with real value
usedNum, err = redis_client.IncrBy(redisKey, n)
}
if p != nil {
redis_client.Expire(redisKey, p.LeftTime)
} else {
//add default expire time if no period set
redis_client.Expire(redisKey, 24*time.Hour)
}
}
if usedNum > r.LimitNum {
if usedNum-r.LimitNum >= l.amount {
redis_client.IncrBy(redisKey, -1*l.amount)
return errors.New(fmt.Sprintf("over limit,congfigId=%d", r.ID))
}
switch l.rejectPolicy {
case models.FillUp:
exceed := usedNum - r.LimitNum
realAmount := l.amount - exceed
redis_client.IncrBy(redisKey, -1*exceed)
l.resultMap[l.index] = newLimitResult(true, l.amount, realAmount)
return nil
case models.JustReject:
redis_client.IncrBy(redisKey, -1*l.amount)
return errors.New(fmt.Sprintf("over limit,congfigId=%d", r.ID))
case models.PermittedOnce:
l.resultMap[l.index] = newLimitResult(false, l.amount, l.amount)
return nil
}

}
l.resultMap[l.index] = newLimitResult(false, l.amount, l.amount)
return nil
}

func (l *limiterRunner) LoadLimiters() error {
limiters, err := GetLimiters(l.limitCode, l.limitType)
if err != nil {
return err
}
if limiters != nil {
l.limiters = limiters
}
return nil
}

func (l *limiterRunner) countInPeriod(r models.LimitConfig, p *models.PeriodResult) (int64, error) {
switch r.LimitType {
case models.LimitTypeTask.Name():
return models.CountTaskAccomplishLogInTaskPeriod(r.LimitCode, l.userId, p)
case models.LimitTypeRewardPoint.Name():
return models.SumRewardAmountInTaskPeriod(models.RewardTypePoint.Name(), r.LimitCode, l.userId, p)
default:
return 0, nil

}
}

func CheckLimit(limitCode string, limitType models.LimitType, userId, amount int64, rejectPolicy models.LimiterRejectPolicy) (int64, error) {
if rejectPolicy == "" {
rejectPolicy = models.JustReject
}
r := newLimiterRunner(limitCode, limitType, userId, amount, rejectPolicy)
err := r.Run()
if err != nil {
return 0, err
}
return r.minRealAmount, nil
}

func GetLimiters(limitCode string, limitType models.LimitType) ([]models.LimitConfig, error) {
limiters, err := GetLimitersByLimitType(limitType)
if err != nil {
return nil, err
}
result := make([]models.LimitConfig, 0)
for i, v := range limiters {
if v.LimitCode == "" || v.LimitCode == limitCode {
result = append(result, limiters[i])
}
}
return result, nil
}

func GetLimitersByLimitType(limitType models.LimitType) ([]models.LimitConfig, error) {
redisKey := redis_key.LimitConfig(limitType.Name())
val, _ := redis_client.Get(redisKey)
if val != "" {
if val == redis_key.EMPTY_REDIS_VAL {
return nil, nil
}
limiters := make([]models.LimitConfig, 0)
json.Unmarshal([]byte(val), &limiters)
return limiters, nil
}
limiters, err := models.GetLimitConfigByLimitType(limitType)
if err != nil {
if models.IsErrRecordNotExist(err) {
redis_client.Setex(redisKey, redis_key.EMPTY_REDIS_VAL, 5*time.Second)
return nil, nil
}
return nil, err
}
jsonStr, _ := json.Marshal(limiters)
redis_client.Setex(redisKey, string(jsonStr), 30*24*time.Hour)

return limiters, nil
}

func GetLimitersByRelatedIdWithDeleted(limitType models.LimitType) ([]models.LimitConfig, error) {
limiters, err := models.GetLimitersByRelatedIdWithDeleted(limitType)
if err != nil {
if models.IsErrRecordNotExist(err) {
return nil, nil
}
return nil, err
}
return limiters, nil
}

+ 54
- 0
services/reward/notify.go View File

@@ -0,0 +1,54 @@
package reward

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
"code.gitea.io/gitea/modules/timeutil"
"encoding/json"
"fmt"
"time"
)

func NotifyRewardOperation(userId, amount int64, sourceType models.SourceType, rewardType models.RewardType, operateType models.RewardOperateType) {
switch sourceType {
case models.SourceTypeRunCloudbrainTask:
return
}
data := &models.UserRewardOperationRedis{
UserId: userId,
Amount: amount,
RewardType: rewardType,
OperateType: operateType,
}
b, _ := json.Marshal(data)
redis_client.ZAdd(redis_key.RewardOperateNotification(), string(b), float64(time.Now().Unix()))
}

func GetRewardOperation(since, until timeutil.TimeStamp) []models.UserRewardOperation {
list, err := redis_client.ZRangeByScore(redis_key.RewardOperateNotification(), float64(since), float64(until))
if err != nil {
log.Error("GetRewardOperation ZRangeByScore error. %v", err)
return nil
}
if len(list) == 0 {
log.Debug("GetRewardOperation list length = 0")
return nil
}
r := make([]models.UserRewardOperation, len(list))
for _, v := range list {
t := models.UserRewardOperationRedis{}
json.Unmarshal([]byte(v), &t)
r = append(r, models.UserRewardOperation{
UserId: t.UserId,
Msg: v,
})
}
redis_client.ZRemRangeByScore(redis_key.RewardOperateNotification(), float64(since), float64(until))
return r
}

func GetRewardOperateMsg(u models.UserRewardOperationRedis) string {
return u.OperateType.Show() + fmt.Sprint(u.Amount) + u.RewardType.Show()
}

+ 280
- 0
services/reward/operator.go View File

@@ -0,0 +1,280 @@
package reward

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/redis/redis_key"
"code.gitea.io/gitea/modules/redis/redis_lock"
"code.gitea.io/gitea/services/reward/point"
"errors"
"fmt"
"time"
)

var RewardOperatorMap = map[string]RewardOperator{
fmt.Sprint(models.RewardTypePoint): new(point.PointOperator),
}

type RewardOperator interface {
IsLimited(ctx *models.RewardOperateContext) error
Operate(ctx *models.RewardOperateContext) error
}

func Operate(ctx *models.RewardOperateContext) error {
defer func() {
if err := recover(); err != nil {
combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
log.Error("PANIC:%v", combinedErr)
}
}()
if !checkRewardOperationParam(ctx) {
log.Error("send reward error,param incorrect")
return errors.New("param incorrect")
}
//add lock
var rewardLock = redis_lock.NewDistributeLock(redis_key.RewardOperateLock(ctx.RequestId, ctx.SourceType.Name(), ctx.OperateType.Name()))
isOk, err := rewardLock.Lock(3 * time.Second)
if err != nil {
return err
}
if !isOk {
log.Info("duplicated reward request,targetUserId=%d requestId=%s", ctx.TargetUserId, ctx.RequestId)
return nil
}
defer rewardLock.UnLock()

//is handled before?
isHandled, err := isHandled(ctx.SourceType.Name(), ctx.RequestId, ctx.OperateType.Name())
if err != nil {
log.Error("reward is handled error,%v", err)
return err
}
if isHandled {
log.Info("reward has been handled,ctx=%+v", ctx)
return nil
}

//get operator
operator := GetOperator(ctx.Reward.Type)
if operator == nil {
log.Error("operator of reward type is not exist,ctx=%v", ctx)
return errors.New("operator of reward type is not exist")
}

if ctx.OperateType == models.OperateTypeIncrease {
//is limited?
if err := operator.IsLimited(ctx); err != nil {
log.Info("operator IsLimited, err=%v", err)
return err
}
}

//new reward operate record
recordId, err := initRewardOperateRecord(ctx)
if err != nil {
log.Error("initRewardOperateRecord error,err=%v", err)
return err
}

ctx.SourceId = recordId

//operate
if err := operator.Operate(ctx); err != nil {
log.Error("operator Operate error,err=%v", err)
UpdateRewardRecordToFinalStatus(ctx.SourceType.Name(), ctx.RequestId, models.OperateStatusFailed)
return err
}

UpdateRewardRecordToFinalStatus(ctx.SourceType.Name(), ctx.RequestId, models.OperateStatusSucceeded)
NotifyRewardOperation(ctx.TargetUserId, ctx.Reward.Amount, ctx.SourceType, ctx.Reward.Type, ctx.OperateType)
return nil
}

func checkRewardOperationParam(ctx *models.RewardOperateContext) bool {
if ctx.Reward.Type == "" {
return false
}
return true
}

func GetOperator(rewardType models.RewardType) RewardOperator {
return RewardOperatorMap[rewardType.Name()]
}

func isHandled(sourceType string, requestId string, operateType string) (bool, error) {
_, err := models.GetPointOperateRecordBySourceTypeAndRequestId(sourceType, requestId, operateType)
if err != nil {
log.Error("operator isHandled error. %v", err)
if models.IsErrRecordNotExist(err) {
return false, nil
}
log.Error("GetPointOperateRecordBySourceTypeAndRequestId ZRangeByScore error. %v", err)
return false, err
}
return true, nil

}

func initRewardOperateRecord(ctx *models.RewardOperateContext) (string, error) {
sn, err := generateOperateSerialNo()
if err != nil {
log.Error("generateOperateSerialNo error. %v", err)
return "", err
}
record := &models.RewardOperateRecord{
UserId: ctx.TargetUserId,
Amount: ctx.Reward.Amount,
LossAmount: ctx.LossAmount,
RewardType: ctx.Reward.Type.Name(),
SourceType: ctx.SourceType.Name(),
SourceId: ctx.SourceId,
SourceTemplateId: ctx.SourceTemplateId,
RequestId: ctx.RequestId,
OperateType: ctx.OperateType.Name(),
Status: models.OperateStatusOperating,
Remark: ctx.Remark,
Title: ctx.Title,
SerialNo: sn,
}
_, err = models.InsertRewardOperateRecord(record)
if err != nil {
log.Error("InsertRewardOperateRecord error. %v", err)
return "", err
}
return record.SerialNo, nil
}

func createPeriodicRewardOperateRecord(ctx *models.StartPeriodicTaskOpts) (string, error) {
sn, err := generateOperateSerialNo()
if err != nil {
log.Error("createPeriodic generateOperateSerialNo error. %v", err)
return "", err
}
record := &models.RewardOperateRecord{
UserId: ctx.TargetUserId,
Amount: 0,
RewardType: ctx.RewardType.Name(),
SourceType: ctx.SourceType.Name(),
SourceId: ctx.SourceId,
RequestId: ctx.RequestId,
OperateType: ctx.OperateType.Name(),
Status: models.OperateStatusOperating,
Remark: ctx.Remark,
Title: ctx.Title,
SerialNo: sn,
}
_, err = models.InsertRewardOperateRecord(record)
if err != nil {
log.Error("createPeriodic InsertRewardOperateRecord error. %v", err)
return "", err
}
return record.SerialNo, nil
}

func UpdateRewardRecordToFinalStatus(sourceType, requestId, newStatus string) error {
_, err := models.UpdateRewardRecordToFinalStatus(sourceType, requestId, newStatus)
if err != nil {
log.Error("UpdateRewardRecord UpdateRewardRecordToFinalStatus error. %v", err)
return err
}
return nil
}

func StartPeriodicTaskAsyn(opts *models.StartPeriodicTaskOpts) {
go StartAndGetPeriodicTask(opts)
}

func StartAndGetPeriodicTask(opts *models.StartPeriodicTaskOpts) (*models.RewardPeriodicTask, error) {
defer func() {
if err := recover(); err != nil {
combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
log.Error("PANIC:%v", combinedErr)
}
}()
//add lock
var rewardLock = redis_lock.NewDistributeLock(redis_key.RewardOperateLock(opts.RequestId, opts.SourceType.Name(), opts.OperateType.Name()))
isOk, err := rewardLock.Lock(3 * time.Second)
if err != nil {
log.Error("StartAndGetPeriodicTask RewardOperateLock error. %v", err)
return nil, err
}
if !isOk {
log.Info("duplicated operate request,targetUserId=%d requestId=%s", opts.TargetUserId, opts.RequestId)
return nil, nil
}
defer rewardLock.UnLock()

_, err = models.GetPointOperateRecordBySourceTypeAndRequestId(opts.SourceType.Name(), opts.RequestId, opts.OperateType.Name())
if err == nil {
task, err := models.GetPeriodicTaskBySourceIdAndType(opts.SourceType, opts.SourceId, opts.OperateType)
if err != nil {
log.Error("GetPeriodicTaskBySourceIdAndType error,%v", err)
return nil, err
}
return task, nil
}

if err != nil && !models.IsErrRecordNotExist(err) {
log.Error("operate is handled error,%v", err)
return nil, err
}

//new reward operate record
recordId, err := createPeriodicRewardOperateRecord(opts)
if err != nil {
log.Error("StartAndGetPeriodicTask createPeriodicRewardOperateRecord error. %v", err)
return nil, err
}

if err = NewRewardPeriodicTask(recordId, opts); err != nil {
log.Error("StartAndGetPeriodicTask NewRewardPeriodicTask error. %v", err)
UpdateRewardRecordToFinalStatus(opts.SourceType.Name(), opts.RequestId, models.OperateStatusFailed)
return nil, err
}

task, err := models.GetPeriodicTaskBySourceIdAndType(opts.SourceType, opts.SourceId, opts.OperateType)
if err != nil {
log.Error("GetPeriodicTaskBySourceIdAndType error,%v", err)
return nil, err
}
return task, nil
}

func StopPeriodicTaskAsyn(sourceType models.SourceType, sourceId string, operateType models.RewardOperateType) {
go StopPeriodicTask(sourceType, sourceId, operateType)
}

func StopPeriodicTask(sourceType models.SourceType, sourceId string, operateType models.RewardOperateType) error {
defer func() {
if err := recover(); err != nil {
combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
log.Error("PANIC:%v", combinedErr)
}
}()
task, err := models.GetPeriodicTaskBySourceIdAndType(sourceType, sourceId, operateType)
if err != nil {
log.Error("StopPeriodicTask. GetPeriodicTaskBySourceIdAndType error. %v", err)
return err
}
if task == nil {
log.Info("Periodic task is not exist")
return nil
}
if task.Status == models.PeriodicTaskStatusFinished {
log.Info("Periodic task is finished")
return nil
}
now := time.Now()
RunRewardTask(*task, now)
return models.StopPeriodicTask(task.ID, task.OperateSerialNo, now)
}

func generateOperateSerialNo() (string, error) {
s, err := GetSerialNoByRedis()
if err != nil {
log.Error("generateOperateSerialNo error. %v", err)

return "", err
}
return s, nil
}

+ 131
- 0
services/reward/period_task.go View File

@@ -0,0 +1,131 @@
package reward

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/redis/redis_key"
"code.gitea.io/gitea/modules/redis/redis_lock"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/routers/repo"
"errors"
"fmt"
"time"
)

func NewRewardPeriodicTask(operateRecordId string, opts *models.StartPeriodicTaskOpts) error {
task := &models.RewardPeriodicTask{}
task.DelaySeconds = int64(opts.Delay.Seconds())
task.IntervalSeconds = int64(opts.Interval.Seconds())
task.Amount = int64(opts.UnitAmount)
task.OperateSerialNo = operateRecordId
task.Status = models.PeriodicTaskStatusRunning
task.NextExecuteTime = timeutil.TimeStamp(opts.StartTime.Add(opts.Delay).Unix())

_, err := models.InsertPeriodicTask(task)
return err
}

func StartRewardTask() {
defer func() {
if err := recover(); err != nil {
combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
log.Error("PANIC:%v", combinedErr)
}
}()
log.Debug("try to run reward tasks")
now := time.Now()
taskList, err := models.GetRunningRewardTask(now)
if err != nil {
log.Error("GetRunningRewardTask error. %v", err)
return
}
if taskList == nil || len(taskList) == 0 {
log.Debug("No GetRunningRewardTask need handled")
return
}
for _, t := range taskList {
RunRewardTask(t, now)
}
}

func RunRewardTask(t models.RewardPeriodicTask, now time.Time) error {
lock := redis_lock.NewDistributeLock(redis_key.RewardTaskRunningLock(t.ID))
isOk, _ := lock.LockWithWait(5*time.Second, 5*time.Second)
if !isOk {
log.Error("get RewardTaskRunningLock failed,t=%+v", t)
return errors.New("get RewardTaskRunningLock failed")
}
defer lock.UnLock()
record, err := models.GetPointOperateRecordBySerialNo(t.OperateSerialNo)
if err != nil {
log.Error("RunRewardTask. GetPointOperateRecordBySerialNo error. %v", err)
return errors.New("GetPointOperateRecordBySerialNo error")
}
if record.Status != models.OperateStatusOperating {
log.Info("RunRewardTask. operate record is finished,record=%+v", record)
return nil
}
n, _ := countExecuteTimes(t, now)
if n == 0 {
log.Info("countExecuteTimes result is 0")
return nil
}

//get operator
operator := GetOperator(models.GetRewardTypeInstance(record.RewardType))
if operator == nil {
log.Error("RunRewardTask. operator of reward type is not exist")
return errors.New("operator of reward type is not exist")
}
nextTime := timeutil.TimeStamp(int64(t.NextExecuteTime) + t.IntervalSeconds)
log.Debug("RunRewardTask n=%d", n)
for i := 1; int64(i) <= n; i++ {
log.Debug("operator.Operate i=%d n=%d", i, n)
err = operator.Operate(&models.RewardOperateContext{
SourceType: models.SourceTypeRunCloudbrainTask,
SourceId: t.OperateSerialNo,
Reward: models.Reward{
Amount: t.Amount,
Type: models.GetRewardTypeInstance(record.RewardType),
},
TargetUserId: record.UserId,
OperateType: models.GetRewardOperateTypeInstance(record.OperateType),
})
if err != nil {
log.Error("RunRewardTask.operator operate error.%v", err)
if models.IsErrInsufficientPointsBalance(err) {
task, err := models.GetCloudbrainByID(record.SourceId)
if err != nil {
log.Error("RunRewardTask GetCloudbrainByID error. %v", err)
return err
}
repo.StopJobs([]*models.Cloudbrain{task})
models.StopPeriodicTask(task.ID, t.OperateSerialNo, time.Now())
return nil
}
return nil
}
models.IncrRewardTaskSuccessCount(t, 1, nextTime)
nextTime = timeutil.TimeStamp(int64(nextTime) + t.IntervalSeconds)
}
return nil

}

func countExecuteTimes(t models.RewardPeriodicTask, now time.Time) (int64, timeutil.TimeStamp) {
interval := t.IntervalSeconds
nextTime := int64(t.NextExecuteTime)
if nextTime > now.Unix() {
return 0, 0
}
diff := now.Unix() - nextTime
var n int64
if diff%interval == 0 {
n = diff / interval
} else {
n = diff/interval + 1
}

newNextTime := timeutil.TimeStamp(nextTime + n*interval)
return n, newNextTime
}

+ 150
- 0
services/reward/point/account/point_account.go View File

@@ -0,0 +1,150 @@
package account

import (
"bytes"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
"code.gitea.io/gitea/modules/redis/redis_lock"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"encoding/json"
"strings"
"time"
)

func GetAccount(userId int64) (*models.PointAccount, error) {
redisKey := redis_key.PointAccountInfo(userId)
val, _ := redis_client.Get(redisKey)
if val != "" {
account := &models.PointAccount{}
json.Unmarshal([]byte(val), account)
return account, nil
}
account, err := models.GetAccountByUserId(userId)
if err != nil {
if models.IsErrRecordNotExist(err) {
a, err := InitAccount(userId)
if err != nil {
log.Error("InitAccount error,err=%v", err)
return nil, err
}
return a, nil
}
log.Error("GetAccountByUserId error,err=%v", err)
return nil, err
}
jsonStr, _ := json.Marshal(account)
redis_client.Setex(redisKey, string(jsonStr), 24*time.Hour)
return account, nil
}

func InitAccount(userId int64) (*models.PointAccount, error) {
lock := redis_lock.NewDistributeLock(redis_key.PointAccountInitLock(userId))
isOk, err := lock.LockWithWait(3*time.Second, 3*time.Second)
if err != nil {
log.Error("PointAccountInitLock error,err=%v", err)
return nil, err
}
if isOk {
defer lock.UnLock()
account, _ := models.GetAccountByUserId(userId)
if account == nil {
models.InsertAccount(&models.PointAccount{
Balance: 0,
TotalEarned: 0,
TotalConsumed: 0,
UserId: userId,
Status: models.PointAccountNormal,
Version: 0,
AccountCode: util.UUID(),
})
return models.GetAccountByUserId(userId)
}
return account, nil
}
return nil, nil

}

//IsPointBalanceEnough check whether the user's point balance is bigger than task unit price
func IsPointBalanceEnough(targetUserId int64, unitPrice int) bool {
if !setting.CloudBrainPaySwitch {
return true
}
if unitPrice == 0 {
return true
}
a, err := GetAccount(targetUserId)
if err != nil {
log.Error("IsPointBalanceEnough GetAccount error,err=%v", err)
return false
}
return a.Balance >= int64(unitPrice)
}

func SearchPointAccount(opt models.SearchPointAccountOpts) (*models.SearchPointAccountResponse, error) {
var result = &models.SearchPointAccountResponse{
Records: make([]*models.UserPointAccount, 0),
PageSize: opt.PageSize,
Page: opt.Page,
Total: 0,
}

userSearch := &models.SearchUserOptions{
Type: models.UserTypeIndividual,
ListOptions: models.ListOptions{
PageSize: 20,
},
SearchByEmail: true,
OrderBy: models.SearchOrderByAlphabetically,
}

userSearch.Page = opt.Page
if userSearch.Page <= 0 {
userSearch.Page = 1
}
userSearch.Keyword = strings.Trim(opt.Keyword, " ")
if len(userSearch.Keyword) == 0 || isKeywordValid(userSearch.Keyword) {
users, count, err := models.SearchUsers(userSearch)
if err != nil {
log.Error("SearchPointAccount SearchUsers error.%v", err)
return nil, err
}
userIds := make([]int64, 0)
for _, v := range users {
userIds = append(userIds, v.ID)
}
accountMap, err := models.GetPointAccountMapByUserIds(userIds)
if err != nil {
return nil, err
}

records := make([]*models.UserPointAccount, 0)
for _, v := range users {
upa := &models.UserPointAccount{
UserId: v.ID,
UserName: v.Name,
Email: v.Email,
Balance: 0,
TotalEarned: 0,
TotalConsumed: 0,
}
a := accountMap[v.ID]
if a != nil {
upa.Balance = a.Balance
upa.TotalConsumed = a.TotalConsumed
upa.TotalEarned = a.TotalEarned
}
records = append(records, upa)
}
result.Records = records
result.Total = count
}
return result, nil
}

func isKeywordValid(keyword string) bool {
return !bytes.Contains([]byte(keyword), []byte{0x00})
}

+ 65
- 0
services/reward/point/point_operate.go View File

@@ -0,0 +1,65 @@
package point

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
"code.gitea.io/gitea/modules/redis/redis_lock"
"code.gitea.io/gitea/services/reward/limiter"
"code.gitea.io/gitea/services/reward/point/account"
"errors"
"time"
)

type PointOperator struct {
}

func (operator *PointOperator) IsLimited(ctx *models.RewardOperateContext) error {
realAmount, err := limiter.CheckLimit(ctx.SourceType.Name(), models.LimitTypeRewardPoint, ctx.TargetUserId, ctx.Reward.Amount, ctx.RejectPolicy)
if err != nil {
log.Error("PointOperator IsLimited error,err=%v", err)
return err
}
if realAmount < ctx.Reward.Amount {
ctx.LossAmount = ctx.Reward.Amount - realAmount
ctx.Reward.Amount = realAmount
}
return nil
}

func (operator *PointOperator) Operate(ctx *models.RewardOperateContext) error {
lock := redis_lock.NewDistributeLock(redis_key.PointAccountOperateLock(ctx.TargetUserId))
isOk, err := lock.LockWithWait(3*time.Second, 3*time.Second)
if err != nil {
log.Error("Get PointAccountOperateLock error,err=%v", err)
return err
}
if isOk {
defer lock.UnLock()
na, err := account.GetAccount(ctx.TargetUserId)
if err != nil || na == nil {
log.Error("operator get account error error,err=%v", err)
return errors.New("get account error")
}
if ctx.OperateType == models.OperateTypeIncrease {
err = na.Increase(ctx.Reward.Amount, ctx.SourceId)
} else if ctx.OperateType == models.OperateTypeDecrease {
if !ctx.PermittedNegative && na.Balance < ctx.Reward.Amount {
log.Info("account balance is not enough,ctx=%v", ctx)
return models.ErrInsufficientPointsBalance{}
}
err = na.Decrease(ctx.Reward.Amount, ctx.SourceId)
}
if err != nil {
log.Error("operate account balance error,err=%v", err)
return err
}
redis_client.Del(redis_key.PointAccountInfo(ctx.TargetUserId))

} else {
log.Error("Get account operate lock failed,ctx=%v", ctx)
return errors.New("Get account operate lock failed")
}
return nil
}

+ 47
- 0
services/reward/record.go View File

@@ -0,0 +1,47 @@
package reward

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
)

type RecordResponse struct {
Records []*models.RewardOperateRecordShow
Total int64
PageSize int
Page int
}

func GetRewardRecordList(opts *models.RewardRecordListOpts) (*RecordResponse, error) {
var l models.RewardRecordShowList
var n int64
var err error
if opts.IsAdmin {
l, n, err = models.GetAdminRewardRecordShowList(opts)
} else {
l, n, err = models.GetRewardRecordShowList(opts)
}
if err != nil {
log.Error("GetRewardRecordList error. %v", err)

return nil, err
}
if len(l) == 0 {
return &RecordResponse{Records: make([]*models.RewardOperateRecordShow, 0), Total: n, Page: opts.Page, PageSize: opts.PageSize}, nil
}
return &RecordResponse{Records: l, Total: n, Page: opts.Page, PageSize: opts.PageSize}, nil
}

func handleRecordResponse(opts *models.RewardRecordListOpts, list models.RewardRecordShowList) {
if opts.IsAdmin {
for _, v := range list {
v.UserName = opts.UserName
}
} else {
for _, v := range list {
if v.Cloudbrain != nil {
v.Cloudbrain.AiCenter = ""
}
}
}
}

+ 28
- 0
services/reward/serial.go View File

@@ -0,0 +1,28 @@
package reward

import (
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
"fmt"
"math/rand"
"time"
)

func GetSerialNoByRedis() (string, error) {
now := time.Now()
r := int64(rand.Intn(3)) + 1
n, err := redis_client.IncrBy(redis_key.RewardSerialCounter(now), r)
if err != nil {
log.Error("GetSerialNoByRedis RewardSerialCounter error. %v", err)
return "", err
}
if n == r {
redis_client.Expire(redis_key.RewardSerialCounter(now), 2*time.Minute)
}
//when the counter n exceeds 1000, the length of the serial number will become longer
if n >= 1000 {
return now.Format("200601021504") + fmt.Sprintf("%d", n) + fmt.Sprint(rand.Intn(10)), nil
}
return now.Format("200601021504") + fmt.Sprintf("%03d", n) + fmt.Sprint(rand.Intn(10)), nil
}

+ 50
- 0
services/task/period/handler.go View File

@@ -0,0 +1,50 @@
package period

import (
"code.gitea.io/gitea/models"
"errors"
"time"
)

var PeriodHandlerMap = map[string]PeriodHandler{
models.PeriodNotCycle: new(NoCycleHandler),
models.PeriodDaily: new(DailyHandler),
}

type PeriodHandler interface {
GetCurrentPeriod() *models.PeriodResult
}

type NoCycleHandler struct {
}

func (l *NoCycleHandler) GetCurrentPeriod() *models.PeriodResult {
return nil
}

type DailyHandler struct {
}

func (l *DailyHandler) GetCurrentPeriod() *models.PeriodResult {
t := time.Now()
startTime := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location())
endTime := startTime.Add(24 * time.Hour)
leftTime := endTime.Sub(t)
return &models.PeriodResult{
StartTime: startTime,
EndTime: endTime,
LeftTime: leftTime,
}
}

func getPeriodHandler(refreshRateype string) PeriodHandler {
return PeriodHandlerMap[refreshRateype]
}

func GetPeriod(refreshRate string) (*models.PeriodResult, error) {
handler := getPeriodHandler(refreshRate)
if handler == nil {
return nil, errors.New("task config incorrect")
}
return handler.GetCurrentPeriod(), nil
}

+ 111
- 0
services/task/task.go View File

@@ -0,0 +1,111 @@
package task

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/services/reward"
"code.gitea.io/gitea/services/reward/limiter"
"fmt"
)

func Accomplish(action models.Action) {
defer func() {
if err := recover(); err != nil {
combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
log.Error("PANIC:%v", combinedErr)
}
}()
action.OpType = models.GetTaskOptType(action)
switch action.OpType {
//only creating public repo can be rewarded
case models.ActionCreateRepo:
if action.Repo.IsPrivate {
return
}
//only creating public image can be rewarded
case models.ActionCreateImage:
if action.IsPrivate {
return
}
case models.ActionBindWechat:
n, err := models.CountWechatBindLog(action.Content, models.WECHAT_BIND)
if err != nil {
log.Error("CountWechatBindLog error when accomplish task,err=%v", err)
return
}
//if wechatOpenId has been bound before,the action can not get reward
if n > 1 {
log.Debug("the wechat account has been bound before,wechatOpenId = %s", action.Content)
return
}

}
go accomplish(action)
}

func accomplish(action models.Action) error {
defer func() {
if err := recover(); err != nil {
combinedErr := fmt.Errorf("%s\n%s", err, log.Stack(2))
log.Error("PANIC:%v", combinedErr)
}
}()
userId := action.ActUserID
taskType := fmt.Sprint(action.OpType)

//get task config
config, err := GetTaskConfig(taskType)
if err != nil {
log.Error("GetTaskConfig error,%v", err)
return err
}
if config == nil {
log.Info("task config not exist,userId=%d taskType=%s", userId, taskType)
return nil
}

//is limited?
if isLimited(userId, config, models.JustReject) {
log.Info("task accomplish maximum times are reached,userId=%d taskType=%s", userId, taskType)
return nil
}

//add log
_, err = models.InsertTaskAccomplishLog(&models.TaskAccomplishLog{
ConfigId: config.ID,
TaskCode: config.TaskCode,
UserId: userId,
ActionId: action.ID,
})
if err != nil {
log.Error("InsertTaskAccomplishLog error,%v", err)
return err
}

//reward
reward.Operate(&models.RewardOperateContext{
SourceType: models.SourceTypeAccomplishTask,
SourceId: fmt.Sprint(action.ID),
SourceTemplateId: fmt.Sprint(action.OpType),
Title: config.Title,
Reward: models.Reward{
Amount: config.AwardAmount,
Type: models.GetRewardTypeInstance(config.AwardType),
},
TargetUserId: userId,
RequestId: fmt.Sprint(action.ID),
OperateType: models.OperateTypeIncrease,
RejectPolicy: models.FillUp,
})
log.Debug("accomplish success,action=%v", action)
return nil
}

func isLimited(userId int64, config *models.TaskConfig, rejectPolicy models.LimiterRejectPolicy) bool {
if _, err := limiter.CheckLimit(config.TaskCode, models.LimitTypeTask, userId, 1, rejectPolicy); err != nil {
log.Error(" isLimited CheckLimit error. %v", err)
return true
}
return false

}

+ 183
- 0
services/task/task_config.go View File

@@ -0,0 +1,183 @@
package task

import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
"code.gitea.io/gitea/modules/redis/redis_lock"
"encoding/json"
"errors"
"time"
)

//GetTaskConfig get task config from redis cache first
// if not exist in redis, find in db and refresh the redis key
func GetTaskConfig(taskType string) (*models.TaskConfig, error) {
list, err := GetTaskConfigList()
if err != nil {
log.Error(" GetTaskConfigList error. %v", err)
return nil, err
}
for _, v := range list {
if v.TaskCode == taskType {
return v, nil
}
}
return nil, nil
}

func GetTaskConfigList() ([]*models.TaskConfig, error) {
redisKey := redis_key.TaskConfigList()
configStr, _ := redis_client.Get(redisKey)
if configStr != "" {
if configStr == redis_key.EMPTY_REDIS_VAL {
return nil, nil
}
config := make([]*models.TaskConfig, 0)
json.Unmarshal([]byte(configStr), &config)
return config, nil
}
config, err := models.GetTaskConfigList()
if err != nil {
log.Error(" GetTaskConfigList from model error. %v", err)
if models.IsErrRecordNotExist(err) {
redis_client.Setex(redisKey, redis_key.EMPTY_REDIS_VAL, 5*time.Second)
return nil, nil
}
return nil, err
}
jsonStr, _ := json.Marshal(config)
redis_client.Setex(redisKey, string(jsonStr), 30*24*time.Hour)
return config, nil
}

func GetTaskConfigPageWithDeleted(opt models.GetTaskConfigOpts) ([]*models.TaskAndLimiterConfig, int64, error) {
config, count, err := models.GetTaskConfigPageWithDeleted(opt)
if err != nil {
log.Error(" GetTaskConfigPageWithDeleted from model error. %v", err)
if models.IsErrRecordNotExist(err) {
return nil, 0, nil
}
return nil, 0, err
}
return config, count, nil
}

func GetTaskConfigWithLimitList(opt models.GetTaskConfigOpts) (*models.TaskConfigWithLimitResponse, error) {
list, n, err := GetTaskConfigPageWithDeleted(opt)
if err != nil {
return nil, err
}
if len(list) == 0 {
return nil, nil
}
r := make([]*models.TaskConfigWithSingleLimit, 0)
for i := 0; i < len(list); i++ {
li := list[i]
t := &models.TaskConfigWithSingleLimit{
ID: li.TaskConfig.ID,
TaskCode: li.TaskConfig.TaskCode,
AwardType: li.TaskConfig.AwardType,
AwardAmount: li.TaskConfig.AwardAmount,
Creator: li.TaskConfig.CreatorName,
CreatedUnix: li.TaskConfig.CreatedUnix,
IsDeleted: li.TaskConfig.DeletedAt > 0,
DeleteAt: li.TaskConfig.DeletedAt,
LimitNum: li.LimitConfig.LimitNum,
RefreshRate: li.LimitConfig.RefreshRate,
}
r = append(r, t)
}

return &models.TaskConfigWithLimitResponse{
Records: r,
Page: opt.Page,
PageSize: opt.PageSize,
Total: n,
}, nil
}

func AddTaskConfig(config models.TaskConfigWithLimit, doer *models.User) error {
if config.TaskCode == "" || config.AwardType == "" {
log.Error(" EditTaskConfig param error")
return errors.New("param error")
}
var lock = redis_lock.NewDistributeLock(redis_key.TaskConfigOperateLock(config.TaskCode, config.AwardType))
isOk, _ := lock.LockWithWait(3*time.Second, 3*time.Second)
if !isOk {
return errors.New("Get lock failed")
}
defer lock.UnLock()

t, err := models.GetTaskConfigByTaskCode(config.TaskCode)
if err != nil && !models.IsErrRecordNotExist(err) {
return err
}
if t != nil {
return errors.New("task config is exist")
}

for i, l := range config.Limiters {
if l.Scope == "" {
config.Limiters[i].Scope = models.LimitScopeSingleUser.Name()
}
}
err = models.NewTaskConfig(config, doer)
if err != nil {
log.Error("add task config error,config:%v err:%v", config, err)
return err
}
redis_client.Del(redis_key.LimitConfig(models.LimitTypeTask.Name()))
redis_client.Del(redis_key.TaskConfigList())
return nil
}

func EditTaskConfig(config models.TaskConfigWithLimit, doer *models.User) error {
if config.TaskCode == "" || config.AwardType == "" || config.ID <= 0 {
log.Error(" EditTaskConfig param error")
return errors.New("param error")
}
var lock = redis_lock.NewDistributeLock(redis_key.TaskConfigOperateLock(config.TaskCode, config.AwardType))
isOk, _ := lock.LockWithWait(3*time.Second, 3*time.Second)
if !isOk {
return errors.New("Get lock failed")
}
defer lock.UnLock()
t, err := models.GetTaskConfigByID(config.ID)
if err != nil {
return err
}
if t == nil {
return errors.New("task config is not exist")
}

for i, l := range config.Limiters {
if l.Scope == "" {
config.Limiters[i].Scope = models.LimitScopeSingleUser.Name()
}
}
err = models.EditTaskConfig(config, doer)
if err != nil {
log.Error("add task config error,config:%v err:%v", config, err)
return err
}
redis_client.Del(redis_key.LimitConfig(models.LimitTypeTask.Name()))
redis_client.Del(redis_key.TaskConfigList())
return nil
}

func DelTaskConfig(id int64, doer *models.User) error {
if id == 0 {
log.Error(" EditTaskConfig param error")
return errors.New("param error")
}
err := models.DelTaskConfig(id, doer)
if err != nil {
log.Error("del task config error,err:%v", err)
return err
}
redis_client.Del(redis_key.LimitConfig(models.LimitTypeTask.Name()))
redis_client.Del(redis_key.TaskConfigList())
return nil
}

modules/auth/wechat/auto_reply.go → services/wechat/auto_reply.go View File


modules/auth/wechat/event_handle.go → services/wechat/event_handle.go View File

@@ -1,6 +1,9 @@
package wechat package wechat


import ( import (
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth/wechat"
"code.gitea.io/gitea/modules/notification"
"code.gitea.io/gitea/modules/redis/redis_client" "code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key" "code.gitea.io/gitea/modules/redis/redis_key"
"encoding/json" "encoding/json"
@@ -142,22 +145,26 @@ func HandleScanEvent(we WechatMsg) string {
if val == "" { if val == "" {
return "" return ""
} }
qrCache := new(QRCode4BindCache)
qrCache := new(wechat.QRCode4BindCache)
json.Unmarshal([]byte(val), qrCache) json.Unmarshal([]byte(val), qrCache)
if qrCache.Status == BIND_STATUS_UNBIND {
err := BindWechat(qrCache.UserId, we.FromUserName)
if qrCache.Status == wechat.BIND_STATUS_UNBIND {
err := wechat.BindWechat(qrCache.UserId, we.FromUserName)
if err != nil { if err != nil {
if err, ok := err.(WechatBindError); ok {
if err, ok := err.(wechat.WechatBindError); ok {
return err.Reply return err.Reply
} }
return BIND_REPLY_FAILED_DEFAULT
return wechat.BIND_REPLY_FAILED_DEFAULT
} }
qrCache.Status = BIND_STATUS_BOUND
qrCache.Status = wechat.BIND_STATUS_BOUND
jsonStr, _ := json.Marshal(qrCache) jsonStr, _ := json.Marshal(qrCache)
redis_client.Setex(redis_key.WechatBindingUserIdKey(sceneStr), string(jsonStr), 60*time.Second) redis_client.Setex(redis_key.WechatBindingUserIdKey(sceneStr), string(jsonStr), 60*time.Second)
} }
u, err := models.GetUserByID(qrCache.UserId)
if err == nil {
notification.NotifyWechatBind(u, we.FromUserName)
}


return BIND_REPLY_SUCCESS
return wechat.BIND_REPLY_SUCCESS
} }


func HandleSubscribeEvent(we WechatMsg) *WechatReplyContent { func HandleSubscribeEvent(we WechatMsg) *WechatReplyContent {

+ 5
- 5
templates/base/footer_content.tmpl View File

@@ -20,7 +20,7 @@
<div class="column ui vertical text menu"> <div class="column ui vertical text menu">
<div class="header item">{{.i18n.Tr "custom.foot.help"}}</div> <div class="header item">{{.i18n.Tr "custom.foot.help"}}</div>
<div class="ui language bottom floating slide up dropdown link item"> <div class="ui language bottom floating slide up dropdown link item">
<i class="world icon"></i>
<i class="globe icon"></i>
<div class="text">{{.LangName}}</div> <div class="text">{{.LangName}}</div>
<div class="menu"> <div class="menu">
{{range .AllLangs}} {{range .AllLangs}}
@@ -29,12 +29,12 @@
</div> </div>
</div> </div>


<a href="https://git.openi.org.cn/zeizei/OpenI_Learning" class=" item a_margin" target="_blank"><i class="ri-creative-commons-by-line footer_icon" ></i><p class="footer_icon">{{.i18n.Tr "custom.Platform_Tutorial"}}</p> </a>
{{if .EnableSwagger}}<a href="/api/swagger" class=" item a_margin"><i class="ri-exchange-line footer_icon" > </i><p class="footer_icon">API</p> </a>{{end}}
<a href="https://git.openi.org.cn/zeizei/OpenI_Learning" class="item" target="_blank"><i class="compass icon" ></i> {{.i18n.Tr "custom.Platform_Tutorial"}}</a>
{{if .EnableSwagger}}<a href="/api/swagger" class="item"><i class="plug icon"></i> API</a>{{end}}
{{if .IsSigned}} {{if .IsSigned}}
<a href="https://git.openi.org.cn/zeizei/OpenI_Learning/issues/new" class=" item a_margin" target="_blank"><i class="ri-mail-send-line footer_icon"></i><p class="footer_icon">{{.i18n.Tr "custom.foot.advice_feedback"}}</p></a>
<a href="https://git.openi.org.cn/zeizei/OpenI_Learning/issues/new" class="item" target="_blank"><i class="envelope icon"></i> {{.i18n.Tr "custom.foot.advice_feedback"}}</a>
{{else}} {{else}}
<a href="{{AppSubUrl}}/user/login" class=" item a_margin" ><i class="ri-mail-send-line footer_icon" ></i><p class="footer_icon">{{.i18n.Tr "custom.foot.advice_feedback"}}</p></a>
<a href="{{AppSubUrl}}/user/login" class="item"><i class="envelope icon"></i> {{.i18n.Tr "custom.foot.advice_feedback"}}</a>
{{end}} {{end}}


{{template "custom/extra_links_footer" .}} {{template "custom/extra_links_footer" .}}


+ 5
- 5
templates/base/footer_content_fluid.tmpl View File

@@ -18,7 +18,7 @@
<div class="column ui vertical text menu"> <div class="column ui vertical text menu">
<div class="header item">{{.i18n.Tr "custom.foot.help"}}</div> <div class="header item">{{.i18n.Tr "custom.foot.help"}}</div>
<div class="ui language bottom floating slide up dropdown link item"> <div class="ui language bottom floating slide up dropdown link item">
<i class="world icon"></i>
<i class="globe icon"></i>
<div class="text">{{.LangName}}</div> <div class="text">{{.LangName}}</div>
<div class="menu"> <div class="menu">
{{range .AllLangs}} {{range .AllLangs}}
@@ -26,12 +26,12 @@
{{end}} {{end}}
</div> </div>
</div> </div>
<a href="https://git.openi.org.cn/zeizei/OpenI_Learning" class=" item a_margin" target="_blank"><i class="ri-creative-commons-by-line footer_icon" ></i><p class="footer_icon">{{.i18n.Tr "custom.Platform_Tutorial"}}</p> </a>
{{if .EnableSwagger}}<a href="/api/swagger" class=" item a_margin"><i class="ri-exchange-line footer_icon" > </i><p class="footer_icon">API</p> </a>{{end}}
<a href="https://git.openi.org.cn/zeizei/OpenI_Learning" class="item" target="_blank"><i class="compass icon"></i> {{.i18n.Tr "custom.Platform_Tutorial"}} </a>
{{if .EnableSwagger}}<a href="/api/swagger" class="item"><i class="plug icon" ></i> API</a>{{end}}
{{if .IsSigned}} {{if .IsSigned}}
<a href="https://git.openi.org.cn/zeizei/OpenI_Learning/issues/new" class=" item a_margin" target="_blank"><i class="ri-mail-send-line footer_icon"></i><p class="footer_icon">{{.i18n.Tr "custom.foot.advice_feedback"}}</p></a>
<a href="https://git.openi.org.cn/zeizei/OpenI_Learning/issues/new" class="item" target="_blank"><i class="envelope icon"></i> {{.i18n.Tr "custom.foot.advice_feedback"}}</a>
{{else}} {{else}}
<a href="{{AppSubUrl}}/user/login" class=" item a_margin" ><i class="ri-mail-send-line footer_icon" ></i><p class="footer_icon footer_icon">{{.i18n.Tr "custom.foot.advice_feedback"}}</p></a>
<a href="{{AppSubUrl}}/user/login" class="item"><i class="envelope icon"></i> {{.i18n.Tr "custom.foot.advice_feedback"}}</a>
{{end}} {{end}}
{{template "custom/extra_links_footer" .}} {{template "custom/extra_links_footer" .}}
</div> </div>


+ 4
- 2
templates/base/head_navbar.tmpl View File

@@ -27,7 +27,8 @@
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a> <a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a>
</div> </div>
</div> </div>
</div> </div>
@@ -59,7 +60,8 @@
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "issues"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a> <a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a>
</div> </div>
</div> </div>
</div> </div>


+ 2
- 0
templates/base/head_navbar_fluid.tmpl View File

@@ -28,6 +28,7 @@
<a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a> <a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a>
</div> </div>
</div> </div>
</div> </div>
@@ -59,6 +60,7 @@
<a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a> <a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a>
</div> </div>
</div> </div>
</div> </div>


+ 4
- 2
templates/base/head_navbar_home.tmpl View File

@@ -19,7 +19,8 @@
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a> <a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a>
</div> </div>
</div> </div>
</div> </div>
@@ -50,7 +51,8 @@
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "issues"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a> <a style="border: none;color: #000; white-space: nowrap;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a>
</div> </div>
</div> </div>
</div> </div>


+ 4
- 2
templates/base/head_navbar_pro.tmpl View File

@@ -28,7 +28,8 @@
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/issues">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000;white-space: nowrap;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a> <a style="border: none;color: #000;white-space: nowrap;" class=" item" href="{{AppSubUrl}}/pulls">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/milestones">{{.i18n.Tr "milestones"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a>
</div> </div>
</div> </div>
</div> </div>
@@ -60,7 +61,8 @@
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "issues"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "issues"}}</a>
<a style="border: none;color: #000;white-space: nowrap;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a> <a style="border: none;color: #000;white-space: nowrap;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "pull_requests"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a> <a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/user/login">{{.i18n.Tr "milestones"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/cloudbrains">{{.i18n.Tr "repo.cloudbrain.task"}}</a>
<a style="border: none;color: #000;" class=" item" href="{{AppSubUrl}}/reward/point">{{.i18n.Tr "calculation_points"}}</a>
</div> </div>
</div> </div>
</div> </div>


+ 24
- 4
templates/repo/cloudbrain/benchmark/new.tmpl View File

@@ -131,8 +131,18 @@
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "cloudbrain.resource_specification"}}</label> <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "cloudbrain.resource_specification"}}</label>
<select id="__specs__" class="ui search dropdown width48" <select id="__specs__" class="ui search dropdown width48"
placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' ovalue="{{.spec_id}}" placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' ovalue="{{.spec_id}}"
{{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}
name="spec_id"> name="spec_id">
</select> </select>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>
<span style="float:right;">
<i class="question circle icon link" data-position="right center" data-variation="mini"></i>
<a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a>
</span>
</div>
{{end}}
</div> </div>
<div class="inline min_title field"> <div class="inline min_title field">
<label class="label-fix-width" style="font-weight: normal;"></label> <label class="label-fix-width" style="font-weight: normal;"></label>
@@ -192,7 +202,7 @@
<div class="required unite inline min_title fields" style="width: 90%;margin-left: 5.7rem;">&nbsp; <div class="required unite inline min_title fields" style="width: 90%;margin-left: 5.7rem;">&nbsp;
<div class="required eight wide field"> <div class="required eight wide field">
<label style="font-weight: normal;white-space: nowrap;">{{.i18n.Tr "repo.cloudbrain.benchmark.evaluate_type"}}</label> <label style="font-weight: normal;white-space: nowrap;">{{.i18n.Tr "repo.cloudbrain.benchmark.evaluate_type"}}</label>
<select class="ui fluid selection search dropdown" id="benchmark_types_id" <select class="ui fluid selection search dropdown" id="benchmark_types_id"
name="benchmark_types_id"> name="benchmark_types_id">
{{range .benchmark_types}} {{range .benchmark_types}}
@@ -231,8 +241,18 @@
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "cloudbrain.resource_specification"}}</label> <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "cloudbrain.resource_specification"}}</label>
<select id="__specs__" class="ui search dropdown width48" <select id="__specs__" class="ui search dropdown width48"
placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' ovalue="{{.spec_id}}" placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' ovalue="{{.spec_id}}"
{{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}
name="spec_id"> name="spec_id">
</select> </select>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>
<span style="float:right;">
<i class="question circle icon link" data-position="right center" data-variation="mini"></i>
<a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a>
</span>
</div>
{{end}}
</div> </div>


<div class="inline min_title field required"> <div class="inline min_title field required">
@@ -373,8 +393,8 @@


;(function() { ;(function() {
var SPECS = {{ .benchmark_specs }}; var SPECS = {{ .benchmark_specs }};
var showPoint = true;
renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
var showPoint = {{ .CloudBrainPaySwitch }};
window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},
point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}},
@@ -382,4 +402,4 @@
shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}},
}); });
})(); })();
</script>
</script>

+ 4
- 8
templates/repo/cloudbrain/benchmark/show.tmpl View File

@@ -455,9 +455,7 @@
</td> </td>


<td class="ti-text-form-content resorce_type"> <td class="ti-text-form-content resorce_type">
<div class="text-span text-span-w">
{{$.resource_type}}
</div>
<div class="text-span text-span-w"></div>
</td> </td>
</tr> </tr>
<tr class="ti-no-ng-animate"> <tr class="ti-no-ng-animate">
@@ -466,9 +464,7 @@
</td> </td>


<td class="ti-text-form-content spec"> <td class="ti-text-form-content spec">
<div class="text-span text-span-w">
{{$.resource_spec}}
</div>
<div class="text-span text-span-w"></div>
</td> </td>
</tr> </tr>
<tr class="ti-no-ng-animate"> <tr class="ti-no-ng-animate">
@@ -599,7 +595,7 @@
<script> <script>
;(function() { ;(function() {
var SPEC = {{ $.Spec }}; var SPEC = {{ $.Spec }};
var showPoint = true;
var showPoint = false;
var specStr = window.renderSpecStr(SPEC, showPoint, { var specStr = window.renderSpecStr(SPEC, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},
@@ -608,7 +604,7 @@
shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}},
}); });
$('td.ti-text-form-content.spec div').text(specStr); $('td.ti-text-form-content.spec div').text(specStr);
$('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType));
SPEC && $('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType));
})(); })();
</script> </script>
<script> <script>


+ 13
- 4
templates/repo/cloudbrain/inference/new.tmpl View File

@@ -254,8 +254,17 @@
</div>--> </div>-->
<div class="required min_title inline field"> <div class="required min_title inline field">
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "cloudbrain.resource_specification"}}</label> <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "cloudbrain.resource_specification"}}</label>
<select id="__specs__" class="ui search dropdown width48" placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" ovalue="{{.spec_id}}" name="spec_id">
<select id="__specs__" class="ui search dropdown width48" placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" ovalue="{{.spec_id}}" name="spec_id" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}>
</select> </select>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>
<span style="float:right;">
<i class="question circle icon link" data-position="right center" data-variation="mini"></i>
<a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a>
</span>
</div>
{{end}}
</div> </div>
<!-- 表单操作 --> <!-- 表单操作 -->
<div class="inline min_title field"> <div class="inline min_title field">
@@ -443,7 +452,7 @@
msg = JSON.stringify(msg) msg = JSON.stringify(msg)
$('#store_run_para').val(msg) $('#store_run_para').val(msg)
} }
var isValidate = false; var isValidate = false;
function validate(){ function validate(){
$('.ui.form') $('.ui.form')
@@ -526,8 +535,8 @@
}) })
;(function() { ;(function() {
var SPECS = {{ .inference_specs }}; var SPECS = {{ .inference_specs }};
var showPoint = true;
renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
var showPoint = {{ .CloudBrainPaySwitch }};
window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},
point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}},


+ 4
- 8
templates/repo/cloudbrain/inference/show.tmpl View File

@@ -342,9 +342,7 @@
{{$.i18n.Tr "repo.modelarts.train_job.resource_type"}} {{$.i18n.Tr "repo.modelarts.train_job.resource_type"}}
</td> </td>
<td class="ti-text-form-content resorce_type"> <td class="ti-text-form-content resorce_type">
<div class="text-span text-span-w">
{{$.resource_type}}
</div>
<div class="text-span text-span-w"></div>
</td> </td>


</tr> </tr>
@@ -484,9 +482,7 @@
</td> </td>


<td class="ti-text-form-content spec"> <td class="ti-text-form-content spec">
<div class="text-span text-span-w">
{{$.i18n.Tr "cloudbrain.gpu_num"}}:{{$.GpuNum}},{{$.i18n.Tr "cloudbrain.cpu_num"}}:{{$.CpuNum}},{{$.i18n.Tr "cloudbrain.memory"}}(MB):{{$.MemMiB}},{{$.i18n.Tr "cloudbrain.shared_memory"}}(MB):{{$.ShareMemMiB}}
</div>
<div class="text-span text-span-w"></div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -662,7 +658,7 @@


;(function() { ;(function() {
var SPEC = {{ .Spec }}; var SPEC = {{ .Spec }};
var showPoint = true;
var showPoint = false;
var specStr = window.renderSpecStr(SPEC, showPoint, { var specStr = window.renderSpecStr(SPEC, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},
@@ -671,6 +667,6 @@
shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}},
}); });
$('td.ti-text-form-content.spec div').text(specStr); $('td.ti-text-form-content.spec div').text(specStr);
$('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType));
SPEC && $('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType));
})(); })();
</script> </script>

+ 24
- 14
templates/repo/cloudbrain/new.tmpl View File

@@ -1,6 +1,6 @@
{{template "base/head" .}} {{template "base/head" .}}
<style> <style>
/* 遮罩层css效果图 */ /* 遮罩层css效果图 */
.inline.required.field.cloudbrain_benchmark { .inline.required.field.cloudbrain_benchmark {
display: none; display: none;
@@ -13,7 +13,7 @@
.inline.required.field.cloudbrain_brainscore { .inline.required.field.cloudbrain_brainscore {
display: none; display: none;
} }


</style> </style>


@@ -55,8 +55,8 @@
d="M3 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z" /> d="M3 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z" />
</svg> </svg>
Ascend NPU</a> Ascend NPU</a>
</div>
</div>
</div>
</div>
<div class="inline field"> <div class="inline field">
<label></label> <label></label>
{{template "custom/task_wait_count" .}} {{template "custom/task_wait_count" .}}
@@ -111,7 +111,7 @@
{{end}} {{end}}
{{end}} {{end}}
</select> </select>
</div>
</div>
<!--<div class="inline required field"> <!--<div class="inline required field">
<label>{{.i18n.Tr "cloudbrain.gpu_type"}}</label> <label>{{.i18n.Tr "cloudbrain.gpu_type"}}</label>
<select id="cloudbrain_gpu_type" class="ui search dropdown gpu-type" placeholder="选择GPU类型" <select id="cloudbrain_gpu_type" class="ui search dropdown gpu-type" placeholder="选择GPU类型"
@@ -128,7 +128,7 @@
<div id="select-multi-dataset"> <div id="select-multi-dataset">


</div> </div>
<!--<div class="inline required field"> <!--<div class="inline required field">
<label>{{.i18n.Tr "cloudbrain.resource_specification"}}</label> <label>{{.i18n.Tr "cloudbrain.resource_specification"}}</label>
<select id="cloudbrain_resource_spec" class="ui search dropdown" <select id="cloudbrain_resource_spec" class="ui search dropdown"
@@ -141,13 +141,23 @@
{{end}} {{end}}
</select> </select>
</div>--> </div>-->
<div class="inline required field"> <div class="inline required field">
<label>{{.i18n.Tr "cloudbrain.resource_specification"}}</label> <label>{{.i18n.Tr "cloudbrain.resource_specification"}}</label>
<select id="__specs__" class="ui search dropdown" <select id="__specs__" class="ui search dropdown"
placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' ovalue="{{.spec_id}}" placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' ovalue="{{.spec_id}}"
{{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}
name="spec_id"> name="spec_id">
</select> </select>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip" style="padding:0 5px;margin:6px 0;margin-left:265px;font-size:12px;width: 50%!important;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>
<span style="float:right;">
<i class="question circle icon link" data-position="right center" data-variation="mini"></i>
<a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a>
</span>
</div>
{{end}}
</div> </div>


<div class="inline required field"> <div class="inline required field">
@@ -212,7 +222,7 @@
function validate(){ function validate(){
$('.ui.form').form({ $('.ui.form').form({
on: 'blur', on: 'blur',
fields: {
fields: {
display_job_name:{ display_job_name:{
identifier : 'display_job_name', identifier : 'display_job_name',
rules: [ rules: [
@@ -226,7 +236,7 @@
rules: [{ type: 'empty' }] rules: [{ type: 'empty' }]
} }
}, },
onSuccess: function(){
onSuccess: function(){
isValidate = true; isValidate = true;
}, },
onFailure: function(e){ onFailure: function(e){
@@ -262,7 +272,7 @@
if (document.readyState === "complete") { if (document.readyState === "complete") {
document.getElementById("mask").style.display = "none" document.getElementById("mask").style.display = "none"
} }
} }


$('#cloudbrain_benchmark_category') $('#cloudbrain_benchmark_category')
@@ -299,7 +309,7 @@
} }
}) })
}) })
$('.ui.green.button').click(function () { $('.ui.green.button').click(function () {
if (!$('input[name="isBranches"]').val()) { if (!$('input[name="isBranches"]').val()) {
return false return false
@@ -311,13 +321,13 @@


;(function() { ;(function() {
var SPECS = {{ .debug_specs }}; var SPECS = {{ .debug_specs }};
var showPoint = true;
var showPoint = {{ .CloudBrainPaySwitch }};
window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, { window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},
point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}},
memory: {{$.i18n.Tr "cloudbrain.memory"}}, memory: {{$.i18n.Tr "cloudbrain.memory"}},
shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}},
});
});
})(); })();
</script>
</script>

+ 3
- 5
templates/repo/cloudbrain/show.tmpl View File

@@ -346,9 +346,7 @@
</td> </td>


<td class="ti-text-form-content resorce_type"> <td class="ti-text-form-content resorce_type">
<div class="text-span text-span-w">
{{$.resource_type}}
</div>
<div class="text-span text-span-w"></div>
</td> </td>
</tr> </tr>
<tr class="ti-no-ng-animate"> <tr class="ti-no-ng-animate">
@@ -601,7 +599,7 @@
} }
;(function() { ;(function() {
var SPEC = {{ .Spec }}; var SPEC = {{ .Spec }};
var showPoint = true;
var showPoint = false;
var specStr = window.renderSpecStr(SPEC, showPoint, { var specStr = window.renderSpecStr(SPEC, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},
@@ -610,6 +608,6 @@
shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}},
}); });
$('td.ti-text-form-content.spec div').text(specStr); $('td.ti-text-form-content.spec div').text(specStr);
$('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType));
SPEC && $('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType));
})(); })();
</script> </script>

+ 13
- 3
templates/repo/cloudbrain/trainjob/new.tmpl View File

@@ -259,8 +259,18 @@
<div class="required min_title inline field"> <div class="required min_title inline field">
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "cloudbrain.resource_specification"}}</label> <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "cloudbrain.resource_specification"}}</label>
<select id="__specs__" class="ui dropdown width48" placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" ovalue="{{.spec_id}}" <select id="__specs__" class="ui dropdown width48" placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" ovalue="{{.spec_id}}"
{{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}
name="spec_id"> name="spec_id">
</select> </select>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>
<span style="float:right;">
<i class="question circle icon link" data-position="right center" data-variation="mini"></i>
<a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a>
</span>
</div>
{{end}}
</div> </div>


<div class="inline field" style="padding: 1rem 0;"> <div class="inline field" style="padding: 1rem 0;">
@@ -354,7 +364,7 @@


$('select.dropdown') $('select.dropdown')
.dropdown(); .dropdown();
var isValidate = false; var isValidate = false;
function validate() { function validate() {
$('.ui.form') $('.ui.form')
@@ -442,8 +452,8 @@
}) })
;(function() { ;(function() {
var SPECS = {{ .train_specs }}; var SPECS = {{ .train_specs }};
var showPoint = true;
renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
var showPoint = {{ .CloudBrainPaySwitch }};
window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},
point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}},


+ 4
- 8
templates/repo/cloudbrain/trainjob/show.tmpl View File

@@ -360,9 +360,7 @@
</td> </td>


<td class="ti-text-form-content resorce_type"> <td class="ti-text-form-content resorce_type">
<div class="text-span text-span-w">
{{$.resource_type}}
</div>
<div class="text-span text-span-w"></div>
</td> </td>
</tr> </tr>
<tr class="ti-no-ng-animate"> <tr class="ti-no-ng-animate">
@@ -371,9 +369,7 @@
</td> </td>


<td class="ti-text-form-content spec"> <td class="ti-text-form-content spec">
<div class="text-span text-span-w">
{{$.i18n.Tr "cloudbrain.gpu_num"}}:{{$.GpuNum}},{{$.i18n.Tr "cloudbrain.cpu_num"}}:{{$.CpuNum}},{{$.i18n.Tr "cloudbrain.memory"}}(MB):{{$.MemMiB}},{{$.i18n.Tr "cloudbrain.shared_memory"}}(MB):{{$.ShareMemMiB}}
</div>
<div class="text-span text-span-w"></div>
</td> </td>
</tr> </tr>


@@ -990,7 +986,7 @@


;(function() { ;(function() {
var SPEC = {{ .Spec }}; var SPEC = {{ .Spec }};
var showPoint = true;
var showPoint = false;
var specStr = window.renderSpecStr(SPEC, showPoint, { var specStr = window.renderSpecStr(SPEC, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},
@@ -999,6 +995,6 @@
shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}},
}); });
$('td.ti-text-form-content.spec div').text(specStr); $('td.ti-text-form-content.spec div').text(specStr);
$('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType));
SPEC && $('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType));
})(); })();
</script> </script>

+ 14
- 5
templates/repo/grampus/trainjob/gpu/new.tmpl View File

@@ -106,7 +106,7 @@
<path d="M3 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z"/> <path d="M3 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z"/>
</svg> </svg>
Ascend NPU</a> Ascend NPU</a>
</div>
</div>
</div> </div>
<div class="min_title inline field" style="margin-top:-10px;"> <div class="min_title inline field" style="margin-top:-10px;">
<label class="label-fix-width" style="font-weight: normal;"></label> <label class="label-fix-width" style="font-weight: normal;"></label>
@@ -206,7 +206,16 @@
</div>--> </div>-->
<div class="required min_title inline field" id="flavor_name"> <div class="required min_title inline field" id="flavor_name">
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label> <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label>
<select class="ui dropdown width48" id="__specs__" style='width:385px' name="spec_id" ovalue="{{.spec_id}}"></select>
<select class="ui dropdown width48" id="__specs__" style='width:385px' name="spec_id" ovalue="{{.spec_id}}" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}></select>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>
<span style="float:right;">
<i class="question circle icon link" data-position="right center" data-variation="mini"></i>
<a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a>
</span>
</div>
{{end}}
</div> </div>


<div class="inline min_title field"> <div class="inline min_title field">
@@ -343,7 +352,7 @@
type : 'integer[1..25]', type : 'integer[1..25]',
} }
] ]
},
},
spec_id: { spec_id: {
identifier: 'spec_id', identifier: 'spec_id',
rules: [{ type: 'empty' }] rules: [{ type: 'empty' }]
@@ -393,8 +402,8 @@


;(function() { ;(function() {
var SPECS = {{ .Specs }}; var SPECS = {{ .Specs }};
var showPoint = true;
renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
var showPoint = {{ .CloudBrainPaySwitch }};
window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},
point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}},


+ 12
- 3
templates/repo/grampus/trainjob/npu/new.tmpl View File

@@ -218,7 +218,16 @@
</div>--> </div>-->
<div class="required min_title inline field" id="flavor_name"> <div class="required min_title inline field" id="flavor_name">
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label> <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label>
<select class="ui dropdown width48" id="__specs__" style='width:385px' name="spec_id" ovalue="{{.spec_id}}"></select>
<select class="ui dropdown width48" id="__specs__" style='width:385px' name="spec_id" ovalue="{{.spec_id}}" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}></select>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>
<span style="float:right;">
<i class="question circle icon link" data-position="right center" data-variation="mini"></i>
<a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a>
</span>
</div>
{{end}}
</div> </div>
<div class="inline required min_title field"> <div class="inline required min_title field">
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</label> <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</label>
@@ -414,8 +423,8 @@


;(function() { ;(function() {
var SPECS = {{ .Specs }}; var SPECS = {{ .Specs }};
var showPoint = true;
renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
var showPoint = {{ .CloudBrainPaySwitch }};
window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},
point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}},


+ 2
- 4
templates/repo/grampus/trainjob/show.tmpl View File

@@ -358,9 +358,7 @@
</td> </td>


<td class="ti-text-form-content spec"> <td class="ti-text-form-content spec">
<div class="text-span text-span-w">
{{.FlavorName}}
</div>
<div class="text-span text-span-w"></div>
</td> </td>
</tr> </tr>
<tr class="ti-no-ng-animate"> <tr class="ti-no-ng-animate">
@@ -638,7 +636,7 @@
<script> <script>
;(function() { ;(function() {
var SPEC = {{ .Spec }}; var SPEC = {{ .Spec }};
var showPoint = true;
var showPoint = false;
var specStr = window.renderSpecStr(SPEC, showPoint, { var specStr = window.renderSpecStr(SPEC, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},


+ 12
- 3
templates/repo/modelarts/inferencejob/new.tmpl View File

@@ -275,7 +275,16 @@
</div>--> </div>-->
<div class="required min_title inline field" id="flaver_name"> <div class="required min_title inline field" id="flaver_name">
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label> <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label>
<select class="ui dropdown width48" id="__specs__" name="spec_id" ovalue="{{.spec_id}}"></select>
<select class="ui dropdown width48" id="__specs__" name="spec_id" ovalue="{{.spec_id}}" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}></select>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>
<span style="float:right;">
<i class="question circle icon link" data-position="right center" data-variation="mini"></i>
<a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a>
</span>
</div>
{{end}}
</div> </div>
<!-- 计算节点 --> <!-- 计算节点 -->
<div class="inline required min_title field"> <div class="inline required min_title field">
@@ -550,8 +559,8 @@


;(function() { ;(function() {
var SPECS = {{ .Specs }}; var SPECS = {{ .Specs }};
var showPoint = true;
renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
var showPoint = {{ .CloudBrainPaySwitch }};
window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},
point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}},


+ 2
- 5
templates/repo/modelarts/inferencejob/show.tmpl View File

@@ -424,9 +424,7 @@ td, th {
</td> </td>


<td class="ti-text-form-content spec"> <td class="ti-text-form-content spec">
<div class="text-span text-span-w">
{{.FlavorName}}
</div>
<div class="text-span text-span-w"></div>
</td> </td>
</tr> </tr>
</tbody> </tbody>
@@ -541,7 +539,7 @@ $(document).ready(function(){
}) })
;(function() { ;(function() {
var SPEC = {{ .Spec }}; var SPEC = {{ .Spec }};
var showPoint = true;
var showPoint = false;
var specStr = window.renderSpecStr(SPEC, showPoint, { var specStr = window.renderSpecStr(SPEC, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},
@@ -550,6 +548,5 @@ $(document).ready(function(){
shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}},
}); });
$('td.ti-text-form-content.spec div').text(specStr); $('td.ti-text-form-content.spec div').text(specStr);
// $('td.ti-text-form-content.resorce_type').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType));
})(); })();
</script> </script>

+ 15
- 4
templates/repo/modelarts/notebook/new.tmpl View File

@@ -79,7 +79,18 @@
</div>--> </div>-->
<div class="inline required field"> <div class="inline required field">
<label>{{.i18n.Tr "cloudbrain.specification"}}</label> <label>{{.i18n.Tr "cloudbrain.specification"}}</label>
<select id="__specs__" class="ui search dropdown" placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' name="spec_id" ovalue="{{.spec_id}}"></select>
<select id="__specs__" class="ui search dropdown" ovalue="{{.spec_id}}"
{{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}
placeholder="{{.i18n.Tr "cloudbrain.select_specification"}}" style='width:385px' name="spec_id"></select>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip" style="padding:0 5px;margin:6px 0;margin-left:265px;font-size:12px;width: 50%!important;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>
<span style="float:right;">
<i class="question circle icon link" data-position="right center" data-variation="mini"></i>
<a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a>
</span>
</div>
{{end}}
</div> </div>
<!--<div class="inline required field"> <!--<div class="inline required field">
<label>数据集存放路径</label> <label>数据集存放路径</label>
@@ -113,7 +124,7 @@
function validate(){ function validate(){
$('.ui.form').form({ $('.ui.form').form({
on: 'blur', on: 'blur',
fields: {
fields: {
display_job_name:{ display_job_name:{
identifier : 'display_job_name', identifier : 'display_job_name',
rules: [ rules: [
@@ -189,8 +200,8 @@


;(function() { ;(function() {
var SPECS = {{ .Specs }}; var SPECS = {{ .Specs }};
var showPoint = true;
renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
var showPoint = {{ .CloudBrainPaySwitch }};
window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},
point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}},


+ 2
- 5
templates/repo/modelarts/notebook/show.tmpl View File

@@ -368,9 +368,7 @@
</td> </td>


<td class="ti-text-form-content spec"> <td class="ti-text-form-content spec">
<div class="text-span text-span-w">
{{$.resource_spec}}
</div>
<div class="text-span text-span-w"></div>
</td> </td>
</tr> </tr>
<tr class="ti-no-ng-animate"> <tr class="ti-no-ng-animate">
@@ -502,7 +500,7 @@


;(function() { ;(function() {
var SPEC = {{ .Spec }}; var SPEC = {{ .Spec }};
var showPoint = true;
var showPoint = false;
var specStr = window.renderSpecStr(SPEC, showPoint, { var specStr = window.renderSpecStr(SPEC, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},
@@ -511,6 +509,5 @@
shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}}, shared_memory: {{$.i18n.Tr "cloudbrain.shared_memory"}},
}); });
$('td.ti-text-form-content.spec div').text(specStr); $('td.ti-text-form-content.spec div').text(specStr);
$('td.ti-text-form-content.resorce_type div').text(getListValueWithKey(ACC_CARD_TYPE, SPEC.AccCardType));
})(); })();
</script> </script>

+ 13
- 4
templates/repo/modelarts/trainjob/new.tmpl View File

@@ -109,7 +109,7 @@
d="M3 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z" /> d="M3 2.992C3 2.444 3.445 2 3.993 2h16.014a1 1 0 0 1 .993.992v18.016a.993.993 0 0 1-.993.992H3.993A1 1 0 0 1 3 21.008V2.992zM19 11V4H5v7h14zm0 2H5v7h14v-7zM9 6h6v2H9V6zm0 9h6v2H9v-2z" />
</svg> </svg>
Ascend NPU</a> Ascend NPU</a>
</div>
</div>
</div> </div>
<div class="min_title inline field" style="margin-top:-10px;"> <div class="min_title inline field" style="margin-top:-10px;">
<label class="label-fix-width" style="font-weight: normal;"></label> <label class="label-fix-width" style="font-weight: normal;"></label>
@@ -283,7 +283,16 @@
</div>--> </div>-->
<div class="required inline min_title field" id="flaver_name"> <div class="required inline min_title field" id="flaver_name">
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label> <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.standard"}}</label>
<select class="ui dropdown width48" id="__specs__" name="spec_id" ovalue="{{.spec_id}}"></select>
<select class="ui dropdown width48" id="__specs__" name="spec_id" ovalue="{{.spec_id}}" {{if .CloudBrainPaySwitch}}blance="{{.PointAccount.Balance}}"{{end}}></select>
{{if .CloudBrainPaySwitch}}
<div class="cloudbrain_resource_spec_blance_tip width48" style="padding:0 5px;margin:6px 0;margin-left:155px;font-size:12px;">
<span>{{$.i18n.Tr "points.balance_of_points"}}<span style="color:red;margin: 0 3px">{{.PointAccount.Balance}}</span>{{$.i18n.Tr "points.points"}}</span><span>{{$.i18n.Tr "points.expected_time"}}<span style="color:red;margin: 0 3px" class="can-use-time"></span>{{$.i18n.Tr "points.hours"}}</span>
<span style="float:right;">
<i class="question circle icon link" data-position="right center" data-variation="mini"></i>
<a href="{{AppSubUrl}}/reward/point/rule" target="_blank">{{$.i18n.Tr "points.points_acquisition_instructions"}}</a>
</span>
</div>
{{end}}
</div> </div>
<div class="inline required min_title field"> <div class="inline required min_title field">
<label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</label> <label class="label-fix-width" style="font-weight: normal;">{{.i18n.Tr "repo.modelarts.train_job.amount_of_compute_node"}}</label>
@@ -556,8 +565,8 @@


;(function() { ;(function() {
var SPECS = {{ .Specs }}; var SPECS = {{ .Specs }};
var showPoint = true;
renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
var showPoint = {{ .CloudBrainPaySwitch }};
window.renderSpecsSelect($('#__specs__'), SPECS, showPoint, {
gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}}, gpu_memory: {{$.i18n.Tr "cloudbrain.gpu_memory"}},
free: {{$.i18n.Tr "cloudbrain.free"}}, free: {{$.i18n.Tr "cloudbrain.free"}},
point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}}, point_hr: {{$.i18n.Tr "cloudbrain.point_hr"}},


Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save