#5435 V20240423

Merged
ychao_1983 merged 92 commits from V20240423 into develop 3 weeks ago
  1. +2
    -0
      entity/ai_task.go
  2. +1
    -0
      entity/cluster.go
  3. +2
    -0
      entity/creation.go
  4. +2
    -2
      manager/client/cloudbrain_two/resty.go
  5. +2
    -2
      manager/client/cloudbrain_two_cd/resty.go
  6. +2
    -2
      manager/client/grampus/grampus.go
  7. +25
    -22
      models/cloudbrain.go
  8. +116
    -0
      models/dataset.go
  9. +2
    -0
      models/dataset_permission.go
  10. +10
    -0
      models/role.go
  11. +26
    -9
      models/user.go
  12. +3
    -3
      models/user_analysis_for_activity.go
  13. +13
    -6
      models/user_business_analysis.go
  14. +1
    -0
      modules/auth/admin.go
  15. +7
    -0
      modules/setting/setting.go
  16. +1
    -5
      modules/urfs_client/objectstorage/mocks/objectstorage_mock.go
  17. +8
    -1
      options/locale/locale_en-US.ini
  18. +8
    -1
      options/locale/locale_zh-CN.ini
  19. +27
    -1
      routers/admin/users.go
  20. +4
    -4
      routers/ai_task/ai_task.go
  21. +1
    -0
      routers/api/v1/api.go
  22. +6
    -2
      routers/api/v1/repo/attachments.go
  23. +5
    -0
      routers/api/v1/user/user.go
  24. +20
    -8
      routers/home.go
  25. +9
    -1
      routers/private/hook.go
  26. +55
    -17
      routers/repo/ai_model_convert.go
  27. +3
    -0
      routers/repo/ai_model_manage.go
  28. +1
    -1
      routers/repo/attachment.go
  29. +13
    -1
      routers/repo/cloudbrain.go
  30. +30
    -20
      routers/repo/dataset.go
  31. +5
    -0
      routers/repo/user_data_analysis.go
  32. +8
    -13
      services/ai_task_service/cluster/c2net.go
  33. +1
    -1
      services/ai_task_service/cluster/cloudbrain_one.go
  34. +5
    -5
      services/ai_task_service/cluster/cloudbrain_two.go
  35. +1
    -1
      services/ai_task_service/cluster/cluster_base.go
  36. +3
    -2
      services/ai_task_service/task/cloudbrain_one_notebook_task.go
  37. +3
    -2
      services/ai_task_service/task/cloudbrain_two_notebook_task.go
  38. +17
    -2
      services/ai_task_service/task/grampus_notebook_task.go
  39. +5
    -5
      services/ai_task_service/task/grampus_online_infer_task.go
  40. +12
    -2
      services/ai_task_service/task/opt_handler.go
  41. +8
    -4
      services/ai_task_service/task/task_creation_info.go
  42. +20
    -0
      services/ai_task_service/task/task_service.go
  43. +2
    -1
      services/repository/contributor.go
  44. +5
    -0
      services/role/role.go
  45. +6
    -0
      templates/admin/user/edit.tmpl
  46. +66
    -0
      templates/admin/user/list.tmpl
  47. +23
    -4
      web_src/js/components/Model.vue
  48. +10
    -1
      web_src/js/components/dataset/referenceDataset.vue
  49. +4
    -2
      web_src/js/features/i18nVue.js
  50. +31
    -27
      web_src/js/index.js
  51. +1
    -1
      web_src/less/vendor/fancyapp.less
  52. +16
    -11
      web_src/vuepages/components/CommonTipsDialog.vue
  53. +155
    -0
      web_src/vuepages/components/cloudbrain/RunTimeLimit.vue
  54. +15
    -3
      web_src/vuepages/components/cloudbrain/details/ConfigInfo.vue
  55. +46
    -16
      web_src/vuepages/components/cloudbrain/details/ResourceUseage.vue
  56. +22
    -2
      web_src/vuepages/langs/config/en-US.js
  57. +22
    -2
      web_src/vuepages/langs/config/zh-CN.js
  58. +16
    -8
      web_src/vuepages/pages/cloudbrain/configs.js
  59. +18
    -5
      web_src/vuepages/pages/cloudbrain/create/index.vue
  60. +3
    -3
      web_src/vuepages/pages/cloudbrain/detail/index.vue
  61. +1
    -1
      web_src/vuepages/pages/cloudbrain/list/index.vue
  62. +1
    -1
      web_src/vuepages/pages/dataset/codedialog/index.vue
  63. +250
    -0
      web_src/vuepages/pages/modelmanage/components/FolderUploadSelect.vue
  64. +3
    -1
      web_src/vuepages/pages/modelmanage/components/ModelHeader.vue
  65. +21
    -4
      web_src/vuepages/pages/modelmanage/files/index.vue
  66. +87
    -41
      web_src/vuepages/pages/modelmanage/fileupload/index.vue
  67. +6
    -2
      web_src/vuepages/pages/supercompute/create/index.vue
  68. +7
    -1
      web_src/vuepages/pages/supercompute/detail/index.vue
  69. +7
    -1
      web_src/vuepages/pages/supercompute/list/index.vue
  70. +8
    -0
      web_src/vuepages/utils/index.js

+ 2
- 0
entity/ai_task.go View File

@@ -45,6 +45,7 @@ type CreateReq struct {
SourceCloudbrainId int64 `json:"source_cloudbrain_id"`
AppName string `json:"app_name"`
HasInternet models.SpecInternetQuery `json:"has_internet"` //0 all;1 no internet;2 has internet
TimeLimit int `json:"time_limit" binding:"Range(-1,24)"`
ParamArray models.Parameters
ComputeSource *models.ComputeSource
ReqCommitID string
@@ -145,6 +146,7 @@ type AITaskDetailInfo struct {
UserId int64 `json:"-"`
AppName string `json:"app_name"`
HasInternet int `json:"has_internet"`
TimeLimit int `json:"time_limit"`
}

func (a *AITaskDetailInfo) Tr(language string) {


+ 1
- 0
entity/cluster.go View File

@@ -19,6 +19,7 @@ type CreateNoteBookTaskRequest struct {
Tasks []NoteBookTask
PrimitiveDatasetName string
RepoName string
IsSubscriber bool
}

type NoteBookTask struct {


+ 2
- 0
entity/creation.go View File

@@ -14,11 +14,13 @@ type CreationRequiredInfo struct {
DefaultBranch string `json:"default_branch"`
WaitCount int64 `json:"wait_count"`
NotStopTaskCount int `json:"not_stop_task_count"`
CanCreateMore bool `json:"can_create_more"`
DisplayJobName string `json:"display_job_name"`
PointAccount *PointAccountInfo `json:"point_account"`
PaySwitch bool `json:"pay_switch"`
Config AITaskCreationConfig `json:"config"`
AllowedWorkerNum []int `json:"allowed_worker_num"`
IsSubscriber bool `json:"is_subscriber"`
}

type ImageRequiredInfo struct {


+ 2
- 2
manager/client/cloudbrain_two/resty.go View File

@@ -273,7 +273,7 @@ sendjob:
return &result, nil
}

func ManageNotebook2(jobID string, param models.NotebookAction) (*models.NotebookActionResult, error) {
func ManageNotebook2(jobID string, param models.NotebookAction, autoStopDuration int) (*models.NotebookActionResult, error) {
checkSetting()
client := getRestyClient()
var result models.NotebookActionResult
@@ -285,7 +285,7 @@ sendjob:
SetHeader("Content-Type", "application/json").
SetAuthToken(TOKEN).
SetResult(&result).
Post(HOST + "/v1/" + setting.ProjectID + urlNotebook2 + "/" + jobID + "/" + param.Action + "?duration=" + strconv.Itoa(AutoStopDurationMs))
Post(HOST + "/v1/" + setting.ProjectID + urlNotebook2 + "/" + jobID + "/" + param.Action + "?duration=" + strconv.Itoa(autoStopDuration))

if err != nil {
return &result, fmt.Errorf("resty ManageNotebook2: %v", err)


+ 2
- 2
manager/client/cloudbrain_two_cd/resty.go View File

@@ -92,7 +92,7 @@ func GetNotebook(jobID string) (*models.GetNotebook2Result, error) {
return &result, nil
}

func ManageNotebook(jobID string, param models.NotebookAction) (*models.NotebookActionResult, error) {
func ManageNotebook(jobID string, param models.NotebookAction, autoStopDuration int) (*models.NotebookActionResult, error) {
var result models.NotebookActionResult

client := getHttpClient()
@@ -101,7 +101,7 @@ func ManageNotebook(jobID string, param models.NotebookAction) (*models.Notebook
Secret: setting.ModelartsCD.SecretKey,
}
r, _ := http.NewRequest(http.MethodPost,
setting.ModelartsCD.EndPoint+"/v1/"+setting.ModelartsCD.ProjectID+urlNotebook2+"/"+jobID+"/"+param.Action+"?duration="+strconv.Itoa(autoStopDurationMs),
setting.ModelartsCD.EndPoint+"/v1/"+setting.ModelartsCD.ProjectID+urlNotebook2+"/"+jobID+"/"+param.Action+"?duration="+strconv.Itoa(autoStopDuration),
nil)

r.Header.Add("content-type", "application/json")


+ 2
- 2
manager/client/grampus/grampus.go View File

@@ -561,7 +561,7 @@ sendjob:
return &result, nil
}

func RestartNotebookJob(jobID string) (*models.GrampusNotebookRestartResponse, error) {
func RestartNotebookJob(jobID string, autoStopDuration int64) (*models.GrampusNotebookRestartResponse, error) {
checkSetting()
client := getRestyClient()
var restartResponse *models.GrampusNotebookRestartResponse
@@ -571,7 +571,7 @@ sendjob:
res, err := client.R().
SetAuthToken(TOKEN).
SetResult(&restartResponse).
Post(HOST + urlNotebookJob + "/" + jobID + "/start")
Post(HOST + urlNotebookJob + "/" + jobID + "/start?autoStopDuration=" + strconv.FormatInt(autoStopDuration, 10))

if err != nil {
return nil, fmt.Errorf("resty grampus restart note book job: %v", err)


+ 25
- 22
models/cloudbrain.go View File

@@ -332,6 +332,7 @@ type Cloudbrain struct {
Config *CloudbrainConfig `xorm:"-"`
AppName string //超算任务的应用类型
HasInternet int
TimeLimit int `xorm:"DEFAULT 0"` //just for subscriber
}

type CloudbrainShow struct {
@@ -2111,7 +2112,8 @@ type GetGrampusJobListResponse struct {

type GrampusNotebookResponse struct {
GrampusResult
JobInfo GrampusNotebookInfo `json:"otJob"`
JobInfo GrampusNotebookInfo `json:"otJob"`
ExitDiagnostics string `json:"exitDiagnostics"`
}

type GrampusNotebookRestartResponse struct {
@@ -2169,26 +2171,27 @@ type GrampusTasks struct {
RunParams map[string]interface{} `json:"runParams"`
}
type GrampusNotebookTask struct {
AutoStopDuration int64 `json:"autoStopDuration"`
Name string `json:"name"`
Capacity int `json:"capacity"`
CenterID []string `json:"centerID"`
CenterName []string `json:"centerName"`
PoolId string `json:"poolId"`
Code GrampusDataset `json:"code"`
Datasets []GrampusDataset `json:"datasets"`
PreTrainModel []GrampusDataset `json:"models"`
OutPut GrampusDataset `json:"output"`
CodeUrl string `json:"codeUrl"`
DataUrl string `json:"dataUrl"`
ImageId string `json:"imageId"`
ImageUrl string `json:"imageUrl"`
ResourceSpecId string `json:"resourceSpecId"`
Token string `json:"token"`
Url string `json:"url"`
Status string `json:"status"`
Command string `json:"command"`
EnvVariables GrampusEnvVarReq `json:"envVariables"`
AutoStopDuration int64 `json:"autoStopDuration"`
Name string `json:"name"`
Capacity int `json:"capacity"`
CenterID []string `json:"centerID"`
CenterName []string `json:"centerName"`
PoolId string `json:"poolId"`
Code GrampusDataset `json:"code"`
Datasets []GrampusDataset `json:"datasets"`
PreTrainModel []GrampusDataset `json:"models"`
OutPut GrampusDataset `json:"output"`
CodeUrl string `json:"codeUrl"`
DataUrl string `json:"dataUrl"`
ImageId string `json:"imageId"`
ImageUrl string `json:"imageUrl"`
ResourceSpecId string `json:"resourceSpecId"`
Token string `json:"token"`
Url string `json:"url"`
Status string `json:"status"`
Command string `json:"command"`
EnvVariables GrampusEnvVarReq `json:"envVariables"`
NoActAutoShutDownTimeout int `json:"noActAutoShutDownTimeout"`
}

type GrampusInferenceTask struct {
@@ -2890,7 +2893,7 @@ func GetCloudBrainUnStoppedJob() ([]*Cloudbrain, error) {
NotIn("status",
JobStopped, JobSucceeded, JobFailed, ModelArtsCreateFailed, ModelArtsStartFailed, ModelArtsUnavailable, ModelArtsResizFailed, ModelArtsDeleted,
ModelArtsStopped, ModelArtsTrainJobCanceled, ModelArtsTrainJobCheckFailed, ModelArtsTrainJobCompleted, ModelArtsTrainJobDeleteFailed, ModelArtsTrainJobDeployServiceFailed,
ModelArtsTrainJobFailed, ModelArtsTrainJobImageFailed, ModelArtsTrainJobKilled, ModelArtsTrainJobLost, ModelArtsTrainJobSubmitFailed, ModelArtsTrainJobSubmitModelFailed).
ModelArtsTrainJobFailed, ModelArtsTrainJobImageFailed, ModelArtsTrainJobKilled, ModelArtsTrainJobLost, ModelArtsTrainJobSubmitFailed, ModelArtsTrainJobSubmitModelFailed, LocalStatusFailed).
// Limit(1000).
Find(&cloudbrains)
}


+ 116
- 0
models/dataset.go View File

@@ -189,6 +189,7 @@ type SearchDatasetOptions struct {
JustNeedZipFile bool
NeedAttachment bool
UploadAttachmentByMe bool
ExploreMine bool
QueryReference bool
}

@@ -224,6 +225,91 @@ func RecommendDataset(dataSetId int64, recommend bool) error {
return err
}

func SearchMyDataset(opts *SearchDatasetOptions) (DatasetList, int64, error) {
cond := SearchMyDatasetCondition(opts)
return SearchMyDatasetByCondition(opts, cond)
}

func SearchMyDatasetCondition(opts *SearchDatasetOptions) builder.Cond {
var cond = builder.NewCond()
cond = cond.And(builder.Neq{"dataset.status": DatasetStatusDeleted})

cond = generateBasicFilterCond(opts, cond)

if opts.RepoID > 0 {
cond = cond.And(builder.Eq{"dataset.repo_id": opts.RepoID})
}

if opts.ExcludeDatasetId > 0 {
cond = cond.And(builder.Neq{"dataset.id": opts.ExcludeDatasetId})
}

if opts.PublicOnly {
cond = cond.And(builder.Eq{"dataset.status": DatasetStatusPublic})
} else if opts.IncludePublic {
cond = cond.And(builder.Eq{"dataset.status": DatasetStatusPublic})
if opts.OwnerID > 0 {
subCon := builder.NewCond()
subCon = subCon.And(builder.Eq{"repository.owner_id": opts.OwnerID})
subCon = generateBasicFilterCond(opts, subCon)
cond = cond.Or(subCon)

}
} else if opts.OwnerID > 0 && !opts.StarByMe && !opts.UploadAttachmentByMe {
cond = cond.And(builder.Eq{"repository.owner_id": opts.OwnerID})
if !opts.IsOwner {
cond = cond.And(builder.Eq{"dataset.status": DatasetStatusPublic})
}
}
if len(opts.DatasetIDs) > 0 && opts.UploadAttachmentByMe {
cond = cond.And(builder.In("dataset.id", opts.DatasetIDs))
}

return cond
}
func SearchMyDatasetByCondition(opts *SearchDatasetOptions, cond builder.Cond) (DatasetList, int64, error) {
if opts.Page <= 0 {
opts.Page = 1
}

var err error
sess := x.NewSession()
defer sess.Close()

datasets := make(DatasetList, 0, opts.PageSize)
selectColumnsSql := "distinct dataset.id,dataset.title, dataset.status, dataset.category, dataset.description, dataset.download_times, dataset.license, dataset.task, dataset.release_id, dataset.user_id, dataset.repo_id, dataset.created_unix,dataset.updated_unix,dataset.num_stars,dataset.recommend,dataset.use_count"

count, err := sess.Distinct("dataset.id").Join("INNER", "repository", "repository.id = dataset.repo_id").
Where(cond).Count(new(Dataset))

if err != nil {
return nil, 0, fmt.Errorf("Count: %v", err)
}

builderQuery := builder.Dialect(setting.Database.Type).Select("id", "title", "status", "category", "description", "download_times", "license", "task", "release_id", "user_id", "repo_id", "created_unix", "updated_unix", "num_stars", "recommend", "use_count").From(builder.Dialect(setting.Database.Type).Select(selectColumnsSql).From("dataset").Join("INNER", "repository", "repository.id = dataset.repo_id").
Where(cond), "d").OrderBy(opts.SearchOrderBy.String())

if opts.PageSize > 0 {
builderQuery.Limit(opts.PageSize, (opts.Page-1)*opts.PageSize)
}

if err = sess.SQL(builderQuery).Find(&datasets); err != nil {
return nil, 0, fmt.Errorf("Dataset: %v", err)
}

if err = datasets.loadAttributes(sess); err != nil {
return nil, 0, fmt.Errorf("LoadAttributes: %v", err)
}

if opts.NeedAttachment {
if err = datasets.loadAttachmentAttributes(opts); err != nil {
return nil, 0, fmt.Errorf("LoadAttributes: %v", err)
}
}

return datasets, count, nil
}

func SearchDataset(opts *SearchDatasetOptions) (DatasetList, int64, error) {
cond := SearchDatasetCondition(opts)
return SearchDatasetByCondition(opts, cond)
@@ -281,6 +367,29 @@ func SearchDatasetCondition(opts *SearchDatasetOptions) builder.Cond {
return cond
}

func generateBasicFilterCond(opts *SearchDatasetOptions, cond builder.Cond) builder.Cond {
if len(opts.Keyword) > 0 {
cond = cond.And(builder.Or(builder.Like{"LOWER(dataset.title)", strings.ToLower(opts.Keyword)}, builder.Like{"LOWER(dataset.description)", strings.ToLower(opts.Keyword)}))
}

if len(opts.Category) > 0 {
cond = cond.And(builder.Eq{"dataset.category": opts.Category})
}

if len(opts.Task) > 0 {
cond = cond.And(builder.Eq{"dataset.task": opts.Task})
}
if len(opts.License) > 0 {
cond = cond.And(builder.Eq{"dataset.license": opts.License})
}

if opts.RecommendOnly {
cond = cond.And(builder.Eq{"dataset.recommend": opts.RecommendOnly})
}

return cond
}

func generateFilterCond(opts *SearchDatasetOptions, cond builder.Cond) builder.Cond {
if len(opts.Keyword) > 0 {
cond = cond.And(builder.Or(builder.Like{"LOWER(dataset.title)", strings.ToLower(opts.Keyword)}, builder.Like{"LOWER(dataset.description)", strings.ToLower(opts.Keyword)}))
@@ -586,6 +695,13 @@ func GetCollaboratorDatasetIdsByUserID(userID int64) []int64 {
return datasets
}

func GetOwnedDatasetIdsByUserID(userID int64) []int64 {
var datasets []int64
_ = x.Table("dataset").Join("INNER", "repository", "dataset.repo_id = repository.id and repository.owner_id=?", userID).
Cols("dataset.id").Find(&datasets)
return datasets
}

func GetTeamDatasetIdsByUserID(userID int64) []int64 {
var datasets []int64
_ = x.Table("dataset").Join("INNER", "team_repo", "dataset.repo_id = team_repo.repo_id").


+ 2
- 0
models/dataset_permission.go View File

@@ -25,6 +25,8 @@ func GetUserDataSetPermission(dataSet *Dataset, user *User) (isPermit bool, err
default:
log.Error("the status of data_set is wrong")
}
} else if !dataSet.IsPrivate() {
isPermit = true
}

return isPermit, nil


+ 10
- 0
models/role.go View File

@@ -12,6 +12,7 @@ const (
TechProgramAdmin RoleType = "TechProgramAdmin"
RewardPointAdmin RoleType = "RewardPointAdmin"
MonitorAdmin RoleType = "MonitorAdmin"
Subscriber RoleType = "Subscriber"
)

type Role struct {
@@ -45,6 +46,15 @@ func GetUserRoleByUserAndRole(userId int64, roleType RoleType) (*UserRole, error
return r, nil
}

func GetUserRoleList(userId int64) ([]UserRole, error) {
r := make([]UserRole, 0)
err := x.Where("user_id = ?", userId).Find(&r)
if err != nil {
return nil, err
}
return r, nil
}

func GetRoleByCode(code string) (*Role, error) {
r := &Role{}
has, err := x.Where("code = ?", code).Get(r)


+ 26
- 9
models/user.go View File

@@ -186,7 +186,8 @@ type User struct {
WechatOpenId string `xorm:"INDEX"`
WechatBindUnix timeutil.TimeStamp
//Mobile phone
PhoneNumber string `xorm:"VARCHAR(255)"`
PhoneNumber string `xorm:"VARCHAR(255)"`
IsSubscriber bool `xorm:"-"`
}

type UserShow struct {
@@ -1775,14 +1776,16 @@ func GetUser(user *User) (bool, error) {
// SearchUserOptions contains the options for searching
type SearchUserOptions struct {
ListOptions
Keyword string
Type UserType
UID int64
OrderBy SearchOrderBy
Visible []structs.VisibleType
Actor *User // The user doing the search
IsActive util.OptionalBool
SearchByEmail bool // Search by email as well as username/full name
Keyword string
Type UserType
UID int64
OrderBy SearchOrderBy
Visible []structs.VisibleType
Actor *User // The user doing the search
IsActive util.OptionalBool
SearchByEmail bool // Search by email as well as username/full name
AdminType int // 0 all 1 admin 2 normal
SubscriberType int //0 all 1 subscriber 2 normal
}

func (opts *SearchUserOptions) toConds() builder.Cond {
@@ -1827,6 +1830,20 @@ func (opts *SearchUserOptions) toConds() builder.Cond {
}
cond = cond.And(accessCond)
}
if opts.AdminType > 0 {
if opts.AdminType == 1 {
cond = cond.And(builder.Eq{"is_admin": true})
} else {
cond = cond.And(builder.Eq{"is_admin": false})
}
}
if opts.SubscriberType > 0 {
if opts.SubscriberType == 1 {
cond = cond.And(builder.In("id", builder.Select("user_id").From("user_role").Where(builder.Eq{"role_type": Subscriber})))
} else {
cond = cond.And(builder.NotIn("id", builder.Select("user_id").From("user_role").Where(builder.Eq{"role_type": Subscriber})))
}
}

if opts.UID > 0 {
cond = cond.And(builder.Eq{"id": opts.UID})


+ 3
- 3
models/user_analysis_for_activity.go View File

@@ -593,10 +593,10 @@ func QueryUserAnnualReport(userId int64) *UserSummaryCurrentYear {
func GetLastModifyTime() string {
statictisSess := xStatistic.NewSession()
defer statictisSess.Close()
userBusinessAnalysisLastMonth := &UserBusinessAnalysisLastMonth{}
err := statictisSess.Select("*").Table(new(UserBusinessAnalysisLastMonth)).Limit(1, 0).Find(userBusinessAnalysisLastMonth)
reList := make([]*UserBusinessAnalysisLastMonth, 0)
err := statictisSess.Select("*").Table(new(UserBusinessAnalysisLastMonth)).Limit(1, 0).Find(&reList)
if err == nil {
return userBusinessAnalysisLastMonth.DataDate
return reList[0].DataDate
}
return ""
}

+ 13
- 6
models/user_business_analysis.go View File

@@ -336,6 +336,7 @@ func QueryUserStaticDataForUserDefine(opts *UserBusinessAnalysisQueryOptions, wi
SolveIssueCountMap := querySolveIssue(start_unix, end_unix)
CreateRepoCountMap, _, _, _ := queryUserCreateRepo(start_unix, end_unix)
LoginCountMap := queryLoginCount(start_unix, end_unix)
LoginActionCountMap := queryLoginActionCount(start_unix, end_unix)
OpenIIndexMap := queryUserRepoOpenIIndex(start_unix, end_unix)
CloudBrainTaskMap, CloudBrainTaskItemMap, _ := queryCloudBrainTask(start_unix, end_unix)
AiModelManageMap := queryUserModel(start_unix, end_unix)
@@ -396,7 +397,7 @@ func QueryUserStaticDataForUserDefine(opts *UserBusinessAnalysisQueryOptions, wi
dateRecord.CreateRepoCount = getMapValue(dateRecord.ID, CreateRepoCountMap)

dateRecord.LoginCount = getMapValue(dateRecord.ID, LoginCountMap)
dateRecord.LoginActionCount = getMapValue(dateRecord.ID, LoginActionCountMap)
if _, ok := OpenIIndexMap[dateRecord.ID]; !ok {
dateRecord.OpenIIndex = 0
} else {
@@ -2222,15 +2223,21 @@ func queryLoginActionCount(start_unix int64, end_unix int64) map[int64]int {
var indexTotal int64
indexTotal = 0
for {
statictisSess.Select("id,u_id").Table("user_login_action_log").Where(cond).OrderBy("id asc").Limit(PAGE_SIZE, int(indexTotal))
statictisSess.Select("id,u_id,created_unix").Table("user_login_action_log").Where(cond).OrderBy("id asc").Limit(PAGE_SIZE, int(indexTotal))
userLoginActionLogList := make([]*UserLoginActionLog, 0)
statictisSess.Find(&userLoginActionLogList)
log.Info("query user login action size=" + fmt.Sprint(len(userLoginActionLogList)))
cachemap := make(map[string]int)

for _, loginRecord := range userLoginActionLogList {
if _, ok := resultMap[loginRecord.UId]; !ok {
resultMap[loginRecord.UId] = 1
} else {
resultMap[loginRecord.UId] += 1
strkey := loginRecord.CreatedUnix.FormatShort() + fmt.Sprintf("d%", loginRecord.UId)
if _, ok := cachemap[strkey]; !ok {
if _, ok := resultMap[loginRecord.UId]; !ok {
resultMap[loginRecord.UId] = 1
} else {
resultMap[loginRecord.UId] += 1
}
cachemap[strkey] = 1
}
}
indexTotal += PAGE_SIZE


+ 1
- 0
modules/auth/admin.go View File

@@ -42,6 +42,7 @@ type AdminEditUserForm struct {
AllowImportLocal bool
AllowCreateOrganization bool
ProhibitLogin bool
Subscriber bool
}

// Validate validates form fields


+ 7
- 0
modules/setting/setting.go View File

@@ -748,6 +748,7 @@ var (
SPECIFICATION_SPECIAL_QUEUE string
DEBUG_MODEL_NUM_LIMIT int
DEBUG_MODEL_SIZE_LIMIT_GB int
ROLE_MULTI_LIMIT_MAP map[string]map[string]int

//wenxin url
BaiduWenXin = struct {
@@ -1625,6 +1626,12 @@ func NewContext() {
SPECIFICATION_SPECIAL_QUEUE = sec.Key("SPECIFICATION_SPECIAL_QUEUE").MustString("{}")
DEBUG_MODEL_NUM_LIMIT = sec.Key("DEBUG_MODEL_NUM_LIMIT").MustInt(5)
DEBUG_MODEL_SIZE_LIMIT_GB = sec.Key("DEBUG_MODEL_SIZE_LIMIT_GB").MustInt(20)
DEBUG_MODEL_SIZE_LIMIT_GB = sec.Key("DEBUG_MODEL_SIZE_LIMIT_GB").MustInt(20)

roleLimitMap := map[string]map[string]int{}
tmpRoleLimitStr := sec.Key("ROLE_MULTI_LIMIT_MAP").MustString("")
json.Unmarshal([]byte(tmpRoleLimitStr), &roleLimitMap)
ROLE_MULTI_LIMIT_MAP = roleLimitMap

sec = Cfg.Section("benchmark")
IsBenchmarkEnabled = sec.Key("ENABLED").MustBool(false)


+ 1
- 5
modules/urfs_client/objectstorage/mocks/objectstorage_mock.go View File

@@ -1,9 +1,5 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: objectstorage.go

// Package mocks is a generated GoMock package.
package mocks

import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)

+ 8
- 1
options/locale/locale_en-US.ini View File

@@ -548,6 +548,7 @@ still_has_org = "Your account is a member of one or more organizations; leave th
org_still_own_repo = "This organization still owns one or more repositories; delete or transfer them first."

target_branch_not_exist = Target branch does not exist.
fail_set_subscriber="Fail to config subscriber parameter."

[user]
change_avatar = Change your avatar…
@@ -584,6 +585,7 @@ static.commentcount=Comment Count
static.focusrepocount=Focus Repo Count
static.starrepocount=Repo Star Count
static.logincount=Login Count
static.loginactioncount=Activity Day
static.watchedcount=Watched Count
static.commitcodesize=Commit Code Line
static.solveissuecount=Solve Issue Count
@@ -2772,7 +2774,9 @@ users.name = Username
users.full_name = Full Name
users.activated = Activated
users.bind_phone = Bind Phone
users.subscriber = Subscriber
users.admin = Admin
users.common = Common User
users.restricted = Restricted
users.repos = Repos
users.created = Created
@@ -2802,6 +2806,9 @@ users.delete_account = Delete User Account
users.still_own_repo = This user still owns one or more repositories. Delete or transfer these repositories first.
users.still_has_org = This user is a member of an organization. Remove the user from any organizations first.
users.deletion_success = The user account has been deleted.
users.all_subscriber_or_not = All Subscribers or Not
users.free_users = Free Users
users.all_admin_or_not = All Admin or Not

emails.email_manage_panel = User Email Management
emails.primary = Primary
@@ -3488,7 +3495,7 @@ to_migrate_repo_exists = The project has been migrated to OpenI, please use the
task_not_exists = AI task is not exists
task_not_finished = AI task not finished
spec_not_available = Specification not available
multi_task = You have already a running or waiting task, can not create more
multi_task = You have already %d running or waiting task(s), can not create more
job_name_already_used = The job name did already exist
insufficient_point_balance = Insufficient point balance
create_failed = Create AI task failed


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

@@ -552,6 +552,7 @@ still_has_org=此帐户仍隶属于一个或多个组织,您需要退出他们
org_still_own_repo=该组织仍然是某些项目的拥有者,您必须先转移或删除它们才能执行删除组织操作!

target_branch_not_exist=目标分支不存在。
fail_set_subscriber=设置付费用户属性失败。

[user]
change_avatar=修改头像
@@ -588,6 +589,7 @@ static.commentcount=评论数
static.focusrepocount=关注项目数
static.starrepocount=点赞项目数
static.logincount=登录次数
static.loginactioncount=活跃天数
static.watchedcount=关注者数
static.commitcodesize=commit代码行数
static.solveissuecount=已解决任务数
@@ -2790,7 +2792,9 @@ users.name=用户名
users.full_name=全名
users.activated=已激活
users.bind_phone=手机验证
users.subscriber=付费用户
users.admin=管理员
users.common=普通用户
users.restricted=受限
users.repos=项目数
users.created=创建时间
@@ -2820,6 +2824,9 @@ users.delete_account=删除帐户
users.still_own_repo=此用户仍然拥有一个或多个项目。必须首先删除或转让这些项目。
users.still_has_org=此用户是组织的成员。必须先从组织中删除用户。
users.deletion_success=用户帐户已被删除。
users.all_subscriber_or_not=全部付费免费用户
users.free_users=免费用户
users.all_admin_or_not=全部管理员和普通用户

emails.email_manage_panel=邮件管理
emails.primary=主要的
@@ -3511,7 +3518,7 @@ to_migrate_repo_exists = 该项目已迁移到启智,请使用启智社区方
task_not_exists = AI任务不存在
task_not_finished = AI任务未结束
spec_not_available = 资源规格不可用
multi_task = 您已经有个正在等待或运行中的任务,请结束该任务再试
multi_task = 您已经有%d个正在等待或运行中的任务,请结束该任务再试
job_name_already_used = 任务名已被使用,请换一个名称
insufficient_point_balance = 积分余额不足
create_failed = 创建AI任务失败


+ 27
- 1
routers/admin/users.go View File

@@ -8,6 +8,8 @@ package admin
import (
"strings"

"code.gitea.io/gitea/services/role"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
@@ -29,16 +31,24 @@ const (

// Users show all the users
func Users(ctx *context.Context) {
AdminType := ctx.QueryInt("adminType")
SubscriberType := ctx.QueryInt("subscriberType")

ctx.Data["Title"] = ctx.Tr("admin.users")
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminUsers"] = true

ctx.Data["AdminType"] = AdminType
ctx.Data["SubscriberType"] = SubscriberType

routers.RenderUserSearch(ctx, &models.SearchUserOptions{
Type: models.UserTypeIndividual,
ListOptions: models.ListOptions{
PageSize: setting.UI.Admin.UserPagingNum,
},
SearchByEmail: true,
AdminType: AdminType,
SubscriberType: SubscriberType,
SearchByEmail: true,
}, tplUsers)
}

@@ -149,6 +159,7 @@ func prepareUserInfo(ctx *context.Context) *models.User {
ctx.ServerError("GetUserByID", err)
return nil
}
u.IsSubscriber = role.UserHasRole(u.ID, models.Subscriber)
ctx.Data["User"] = u

if u.LoginSource > 0 {
@@ -260,6 +271,21 @@ func EditUserPost(ctx *context.Context, form auth.AdminEditUserForm) {
}
return
}
if form.Subscriber && !u.IsSubscriber {
if err := role.AddUserRole(u.ID, models.Subscriber); err != nil {
log.Error("fail_set_subscriber", err)
ctx.RenderWithErr(ctx.Tr("form.fail_set_subscriber"), tplUserEdit, &form)
return
}
}
if !form.Subscriber && u.IsSubscriber {
if err := role.DeleteUserRole(u.ID, models.Subscriber); err != nil {
log.Error("fail_set_subscriber", err)
ctx.RenderWithErr(ctx.Tr("form.fail_set_subscriber"), tplUserEdit, &form)
return
}
}

log.Trace("Account profile updated by admin (%s): %s", ctx.User.Name, u.Name)

ctx.Flash.Success(ctx.Tr("admin.users.update_profile_success"))


+ 4
- 4
routers/ai_task/ai_task.go View File

@@ -433,10 +433,10 @@ func GetImageInfoBySelectedSpec(ctx *context.Context) {
func GetCreationRequiredInfo(ctx *context.Context) {
jobType := ctx.Query("job_type")
var isOnlineType bool
if models.JobType(jobType) == (models.JobTypeOnlineInference) {
isOnlineType = true
jobType = string(models.JobTypeDebug)
}
// if models.JobType(jobType) == (models.JobTypeOnlineInference) {
// isOnlineType = true
// jobType = string(models.JobTypeDebug)
// }
log.Info("required jobType=" + jobType)
computeSourceName := ctx.Query("compute_source")
clusterType := ctx.Query("cluster_type")


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

@@ -926,6 +926,7 @@ func RegisterRoutes(m *macaron.Macaron) {

m.Group("/user", func() {
m.Get("", user.GetAuthenticatedUser)
m.Get("/subscriber", user.IsSubscriber)
m.Get("/point_account", user.GetPointAccount)
m.Combo("/emails").Get(user.ListEmails).
Post(bind(api.CreateEmailOption{}), user.AddEmail).


+ 6
- 2
routers/api/v1/repo/attachments.go View File

@@ -101,7 +101,9 @@ func NewMultipart(ctx *context.APIContext) {
"msg": err.Error(),
})
} else {
routeRepo.AddFileNameToCache(datasetId, fileName, ctx.User.ID)
if !ignore {
routeRepo.AddFileNameToCache(datasetId, fileName, ctx.User.ID)
}
re["result_code"] = "0"
ctx.JSON(200, re)
}
@@ -193,7 +195,9 @@ func NewModelMultipart(ctx *context.APIContext) {
"msg": err.Error(),
})
} else {
routeRepo.AddModelFileNameToCache(modeluuid, fileName, ctx.User.ID)
if !ignore {
routeRepo.AddModelFileNameToCache(modeluuid, fileName, ctx.User.ID)
}
re["result_code"] = "0"
ctx.JSON(200, re)
}


+ 5
- 0
routers/api/v1/user/user.go View File

@@ -175,3 +175,8 @@ func IsRewardPointAdmin(ctx *context.APIContext) {
r["is_admin"] = isAdmin
ctx.JSON(http.StatusOK, response.OuterSuccessWithData(r))
}
func IsSubscriber(ctx *context.APIContext) {
r := map[string]interface{}{}
r["isSubscriber"] = role.UserHasRole(ctx.User.ID, models.Subscriber)
ctx.JSON(http.StatusOK, response.OuterSuccessWithData(r))
}

+ 20
- 8
routers/home.go View File

@@ -14,6 +14,8 @@ import (
"strings"
"time"

"code.gitea.io/gitea/services/role"

"code.gitea.io/gitea/routers/repo"

"code.gitea.io/gitea/routers/response"
@@ -55,10 +57,10 @@ const (
tplRepoSquare base.TplName = "explore/repos/square"
tplRepoSearch base.TplName = "explore/repos/search"
tplRoshmci base.TplName = "explore/ros-hmci"
tplExploreCenterMap base.TplName = "explore/center_map"
tplExploreCenterMap base.TplName = "explore/center_map"

tplComputingPowerDemand base.TplName = "computingpower/demand"
tplComputingPowerDomestic base.TplName = "computingpower/domestic"
tplComputingPowerDemand base.TplName = "computingpower/demand"
tplComputingPowerDomestic base.TplName = "computingpower/domestic"
)

// Home render home page
@@ -447,16 +449,20 @@ func ExploreMyDatasets(ctx *context.Context) {
NeedAttachment: false,
CloudBrainType: -1,
SearchOrderBy: getDatasetOrderBy(ctx),
JustNeedZipFile: false,
DatasetIDs: models.GetOwnedDatasetIdsByUserID(ctx.User.ID),
ExploreMine: true,
}
repo.DatasetMultiple(ctx, opts)
}
func ExploreFavoriteDatasets(ctx *context.Context) {
opts := &models.SearchDatasetOptions{
StarByMe: true,
DatasetIDs: models.GetDatasetIdsStarByUser(ctx.User.ID),
NeedAttachment: false,
CloudBrainType: -1,
SearchOrderBy: getDatasetOrderBy(ctx),
StarByMe: true,
DatasetIDs: models.GetDatasetIdsStarByUser(ctx.User.ID),
NeedAttachment: false,
CloudBrainType: -1,
SearchOrderBy: getDatasetOrderBy(ctx),
JustNeedZipFile: false,
}
repo.DatasetMultiple(ctx, opts)

@@ -627,6 +633,10 @@ func RenderUserSearch(ctx *context.Context, opts *models.SearchUserOptions, tplN
ctx.ServerError("SearchUsers", err)
return
}
for _, user := range users {
user.IsSubscriber = role.UserHasRole(user.ID, models.Subscriber)
}

}
ctx.Data["Keyword"] = opts.Keyword
ctx.Data["Total"] = count
@@ -636,6 +646,8 @@ func RenderUserSearch(ctx *context.Context, opts *models.SearchUserOptions, tplN

pager := context.NewPagination(int(count), opts.PageSize, opts.Page, 5)
pager.SetDefaultParams(ctx)
pager.AddParam(ctx, "adminType", "AdminType")
pager.AddParam(ctx, "subscriberType", "SubscriberType")
ctx.Data["Page"] = pager

ctx.HTML(200, tplName)


+ 9
- 1
routers/private/hook.go View File

@@ -7,6 +7,8 @@ package private

import (
"bufio"
"code.gitea.io/gitea/modules/redis/redis_client"
"code.gitea.io/gitea/modules/redis/redis_key"
"context"
"fmt"
"io"
@@ -567,10 +569,16 @@ func updateRepoCommitCnt(ctx *macaron.Context, repo *models.Repository) error {
log.Error("UpdateRepositoryCommitNum failed:%v", err.Error(), ctx.Data["MsgID"])
return err
}

//删除缓存
go deleteCommitRelatedCache(repo)
return nil
}

func deleteCommitRelatedCache(repo *models.Repository) {
k := redis_key.RepoTopNContributors(repo.ID, 6)
redis_client.Del(k)
}

// SetDefaultBranch updates the default branch
func SetDefaultBranch(ctx *macaron.Context) {
ownerName := ctx.Params(":owner")


+ 55
- 17
routers/repo/ai_model_convert.go View File

@@ -385,33 +385,44 @@ func createGpuTrainJob(modelConvert *models.AiModelConvert, ctx *context.Context
dataActualPath = setting.Attachment.Minio.RealPath + setting.Attachment.Minio.Bucket + "/" + setting.CBCodePathPrefix + modelConvert.ID + "/dataset"
}
log.Info("dataActualPath=" + dataActualPath)

bootfile := ""
runParms := make(map[string]interface{}, 0)
if modelConvert.SrcEngine == PYTORCH_ENGINE {
if modelConvert.DestFormat == CONVERT_FORMAT_ONNX {
command = getGpuModelConvertCommand(modelConvert.ID, modelConvert.ModelPath, modelConvert, setting.ModelConvert.PytorchOnnxBootFile)
bootfile = setting.ModelConvert.PytorchOnnxBootFile
runParms = getGpuModelConvertRunParams(modelConvert.ID, modelConvert.ModelPath, modelConvert, setting.ModelConvert.PytorchOnnxBootFile)
//command = getGpuModelConvertCommand(modelConvert.ID, modelConvert.ModelPath, modelConvert, setting.ModelConvert.PytorchOnnxBootFile)
} else if modelConvert.DestFormat == CONVERT_FORMAT_TRT {
command = getGpuModelConvertCommand(modelConvert.ID, modelConvert.ModelPath, modelConvert, setting.ModelConvert.PytorchTrTBootFile)
bootfile = setting.ModelConvert.PytorchTrTBootFile
runParms = getGpuModelConvertRunParams(modelConvert.ID, modelConvert.ModelPath, modelConvert, setting.ModelConvert.PytorchTrTBootFile)
//command = getGpuModelConvertCommand(modelConvert.ID, modelConvert.ModelPath, modelConvert, setting.ModelConvert.PytorchTrTBootFile)
} else {
return errors.New("Not support the format.")
}
} else if modelConvert.SrcEngine == TENSORFLOW_ENGINE {
IMAGE_URL = setting.ModelConvert.GPU_TENSORFLOW_IMAGE
if modelConvert.DestFormat == CONVERT_FORMAT_ONNX {
command = getGpuModelConvertCommand(modelConvert.ID, modelConvert.ModelPath, modelConvert, setting.ModelConvert.TensorFlowGpuBootFile)
bootfile = setting.ModelConvert.TensorFlowGpuBootFile
runParms = getGpuModelConvertRunParams(modelConvert.ID, modelConvert.ModelPath, modelConvert, setting.ModelConvert.TensorFlowGpuBootFile)
//command = getGpuModelConvertCommand(modelConvert.ID, modelConvert.ModelPath, modelConvert, setting.ModelConvert.TensorFlowGpuBootFile)
} else {
return errors.New("Not support the format.")
}
} else if modelConvert.SrcEngine == PADDLE_ENGINE {
IMAGE_URL = setting.ModelConvert.GPU_PADDLE_IMAGE
if modelConvert.DestFormat == CONVERT_FORMAT_ONNX {
command = getGpuModelConvertCommand(modelConvert.ID, modelConvert.ModelPath, modelConvert, setting.ModelConvert.PaddleOnnxBootFile)
bootfile = setting.ModelConvert.PaddleOnnxBootFile
runParms = getGpuModelConvertRunParams(modelConvert.ID, modelConvert.ModelPath, modelConvert, setting.ModelConvert.PaddleOnnxBootFile)
//command = getGpuModelConvertCommand(modelConvert.ID, modelConvert.ModelPath, modelConvert, setting.ModelConvert.PaddleOnnxBootFile)
} else {
return errors.New("Not support the format.")
}
} else if modelConvert.SrcEngine == MXNET_ENGINE {
IMAGE_URL = setting.ModelConvert.GPU_MXNET_IMAGE
if modelConvert.DestFormat == CONVERT_FORMAT_ONNX {
command = getGpuModelConvertCommand(modelConvert.ID, modelConvert.ModelPath, modelConvert, setting.ModelConvert.MXnetOnnxBootFile)
bootfile = setting.ModelConvert.MXnetOnnxBootFile
runParms = getGpuModelConvertRunParams(modelConvert.ID, modelConvert.ModelPath, modelConvert, setting.ModelConvert.MXnetOnnxBootFile)
//command = getGpuModelConvertCommand(modelConvert.ID, modelConvert.ModelPath, modelConvert, setting.ModelConvert.MXnetOnnxBootFile)
} else {
return errors.New("Not support the format.")
}
@@ -505,7 +516,7 @@ func createGpuTrainJob(modelConvert *models.AiModelConvert, ctx *context.Context
},
},
PreTrainModel: nil,
BootFile: "",
BootFile: bootfile,
OutPut: []entity.ContainerData{{
ContainerPath: "/tmp/output",
ReadOnly: false,
@@ -534,7 +545,7 @@ func createGpuTrainJob(modelConvert *models.AiModelConvert, ctx *context.Context
reqJson, _ := json.Marshal(req)
log.Info("reqJson=" + string(reqJson))

jobResult, err := createGrampusTrainJob(req, command)
jobResult, err := createGrampusTrainJob(req, command, runParms)

if err != nil {
log.Error("CreateJob failed:", err.Error(), ctx.Data["MsgID"])
@@ -594,8 +605,8 @@ func getGrampusTrainTaskConfig() *entity.AITaskBaseConfig {
return config
}

func createGrampusTrainJob(req entity.CreateTrainTaskRequest, exeCommand string) (*models.CreateGrampusJobResponse, error) {
jobResult, err := grampus.CreateJob(convertTrainReq2Grampus(req, exeCommand))
func createGrampusTrainJob(req entity.CreateTrainTaskRequest, exeCommand string, runParam map[string]interface{}) (*models.CreateGrampusJobResponse, error) {
jobResult, err := grampus.CreateJob(convertTrainReq2Grampus(req, exeCommand, runParam))
if err != nil {
log.Error("CreateNoteBook failed: %v", err.Error())
return nil, err
@@ -603,19 +614,19 @@ func createGrampusTrainJob(req entity.CreateTrainTaskRequest, exeCommand string)
return jobResult, nil
}

func convertTrainReq2Grampus(req entity.CreateTrainTaskRequest, exeCommand string) models.CreateGrampusJobRequest {
command := generateGrampusTrainCommand(req, exeCommand)
func convertTrainReq2Grampus(req entity.CreateTrainTaskRequest, exeCommand string, runParam map[string]interface{}) models.CreateGrampusJobRequest {
//command := generateGrampusTrainCommand(req, exeCommand)
command := ""
tasks := make([]models.GrampusTasks, len(req.Tasks))
for i := 0; i < len(req.Tasks); i++ {
t := req.Tasks[i]
tasks[i] = convertTrainTask2Grampus(t, command)
tasks[i] = convertTrainTask2Grampus(t, command, runParam)
}

return models.CreateGrampusJobRequest{Name: req.Name, Tasks: tasks}
}

func convertTrainTask2Grampus(t entity.TrainTask, command string) models.GrampusTasks {
func convertTrainTask2Grampus(t entity.TrainTask, command string, runParam map[string]interface{}) models.GrampusTasks {
return models.GrampusTasks{
Name: t.Name,
ResourceSpecId: t.ResourceSpecId,
@@ -630,6 +641,7 @@ func convertTrainTask2Grampus(t entity.TrainTask, command string) models.Grampus
BootFile: t.BootFile,
OutPut: convertContainerArray2Grampus(t.OutPut),
WorkServerNumber: t.WorkServerNumber,
RunParams: runParam,
}
}

@@ -713,8 +725,7 @@ func buildUnzipCodeCommand(codeConfigPath, codeFilePath, computeSource string) *
Next(entity.NewCommand("cd", codeConfigPath)).
Next(entity.NewCommand("unzip", "-q", codeFilePath)).
Next(entity.NewCommand("echo", "'unzip code finished'")).
Next(entity.NewCommand("ls", "-l")).
Next(entity.NewCommand("ls", "-l", "mnist_pytorchexample_gpu"))
Next(entity.NewCommand("ls", "-l"))
return builder
}
func buildUnzipDatasetCommand(datasets []entity.ContainerData, datasetPath, computeSource string) *entity.CommandBuilder {
@@ -806,6 +817,33 @@ func getGpuModelConvertCommand(name string, modelFile string, modelConvert *mode
return command
}

func getGpuModelConvertRunParams(name string, modelFile string, modelConvert *models.AiModelConvert, bootfile string) map[string]interface{} {
re := make(map[string]interface{}, 0)
inputshape := strings.Split(modelConvert.InputShape, ",")
n := "256"
c := "1"
h := "28"
w := "28"
if len(inputshape) == 4 {
n = inputshape[0]
c = inputshape[1]
h = inputshape[2]
w = inputshape[3]
}
re["model"] = modelFile
re["n"] = n
re["c"] = c
re["h"] = h
re["w"] = w
if modelConvert.DestFormat == CONVERT_FORMAT_TRT {
if modelConvert.NetOutputFormat == NetOutputFormat_FP16 {
re["fp16"] = "True"

}
}
return re
}

func DeleteModelConvert(ctx *context.Context) {
log.Info("delete model convert start.")
id := ctx.Params(":id")


+ 3
- 0
routers/repo/ai_model_manage.go View File

@@ -1170,6 +1170,9 @@ func isQueryRight(ctx *context.Context) bool {
}

func isCanDownload(ctx *context.Context, task *models.AiModelManage) bool {
if !task.IsPrivate {
return true
}
if ctx.User == nil {
return false
}


+ 1
- 1
routers/repo/attachment.go View File

@@ -284,7 +284,7 @@ func GetAttachment(ctx *context.Context) {
}

if dataSet != nil {
if !ctx.IsSigned {
if !ctx.IsSigned && dataSet.IsPrivate() {
ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL)
ctx.Redirect(setting.AppSubURL + "/user/login")
return


+ 13
- 1
routers/repo/cloudbrain.go View File

@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"io"
"math"
"net/http"
"os"
"regexp"
@@ -15,6 +16,8 @@ import (
"time"
"unicode/utf8"

"code.gitea.io/gitea/services/role"

"code.gitea.io/gitea/services/lock"

cloudbrainService "code.gitea.io/gitea/services/cloudbrain"
@@ -2137,12 +2140,21 @@ func SyncCloudbrainStatus() {
if task.JobType == string(models.JobTypeModelSafety) {
continue
}
maxDuration := setting.MaxDuration
if role.UserHasRole(task.UserID, models.Subscriber) && task.TimeLimit != 0 {
if task.TimeLimit > 0 {
maxDuration = int64(task.TimeLimit * 60 * 60)
} else {
maxDuration = math.MaxInt64
}
}

task, _ = ai_task.UpdateCloudbrain(task)
if task.Duration >= setting.MaxDuration && task.JobType == string(models.JobTypeDebug) {
if task.Duration >= maxDuration && task.JobType == string(models.JobTypeDebug) || task.Duration >= setting.Grampus.MMLSparkMaxTime && task.JobType == string(models.JobTypeSuperCompute) {
ai_task.StopCloudbrain(task)
}
go ai_task.TryToNotifyLongRunningTask(task)

}

return


+ 30
- 20
routers/repo/dataset.go View File

@@ -376,7 +376,6 @@ func DatasetMultiple(ctx *context.Context, opts *models.SearchDatasetOptions) {
Page: page,
PageSize: pageSize,
}
opts.JustNeedZipFile = true
opts.User = ctx.User

category := ctx.Query("category")
@@ -385,8 +384,14 @@ func DatasetMultiple(ctx *context.Context, opts *models.SearchDatasetOptions) {
opts.Category = category
opts.Task = task
opts.License = license

datasets, count, err := models.SearchDataset(opts)
var datasets models.DatasetList
var count int64
var err error
if opts.ExploreMine {
datasets, count, err = models.SearchMyDataset(opts)
} else {
datasets, count, err = models.SearchDataset(opts)
}

if err != nil {
ctx.ServerError("datasets", err)
@@ -415,11 +420,12 @@ func CurrentRepoDatasetMultiple(ctx *context.Context) {
datasetIds := models.GetDatasetIdsByRepoID(ctx.Repo.Repository.ID)
searchOrderBy := getSearchOrderByInValues(datasetIds)
opts := &models.SearchDatasetOptions{
RepoID: ctx.Repo.Repository.ID,
NeedAttachment: true,
CloudBrainType: ctx.QueryInt("type"),
DatasetIDs: datasetIds,
SearchOrderBy: searchOrderBy,
RepoID: ctx.Repo.Repository.ID,
NeedAttachment: true,
CloudBrainType: ctx.QueryInt("type"),
DatasetIDs: datasetIds,
SearchOrderBy: searchOrderBy,
JustNeedZipFile: true,
}

DatasetMultiple(ctx, opts)
@@ -444,6 +450,7 @@ func MyDatasetsMultiple(ctx *context.Context) {
UploadAttachmentByMe: true,
NeedAttachment: true,
CloudBrainType: ctx.QueryInt("type"),
JustNeedZipFile: true,
}
DatasetMultiple(ctx, opts)

@@ -452,10 +459,11 @@ func MyDatasetsMultiple(ctx *context.Context) {
func ReferenceDatasetAvailable(ctx *context.Context) {

opts := &models.SearchDatasetOptions{
PublicOnly: true,
NeedAttachment: false,
CloudBrainType: models.TypeCloudBrainAll,
SearchOrderBy: models.SearchOrderByDefault,
PublicOnly: true,
NeedAttachment: false,
CloudBrainType: models.TypeCloudBrainAll,
SearchOrderBy: models.SearchOrderByDefault,
JustNeedZipFile: false,
}
dataset, _ := models.GetDatasetByRepo(&models.Repository{ID: ctx.Repo.Repository.ID})
if dataset != nil {
@@ -468,10 +476,11 @@ func ReferenceDatasetAvailable(ctx *context.Context) {
func PublicDatasetMultiple(ctx *context.Context) {

opts := &models.SearchDatasetOptions{
PublicOnly: true,
NeedAttachment: true,
CloudBrainType: ctx.QueryInt("type"),
SearchOrderBy: models.SearchOrderByDefault,
PublicOnly: true,
NeedAttachment: true,
CloudBrainType: ctx.QueryInt("type"),
SearchOrderBy: models.SearchOrderByDefault,
JustNeedZipFile: true,
}
DatasetMultiple(ctx, opts)

@@ -480,10 +489,11 @@ func PublicDatasetMultiple(ctx *context.Context) {
func MyFavoriteDatasetMultiple(ctx *context.Context) {

opts := &models.SearchDatasetOptions{
StarByMe: true,
DatasetIDs: models.GetDatasetIdsStarByUser(ctx.User.ID),
NeedAttachment: true,
CloudBrainType: ctx.QueryInt("type"),
StarByMe: true,
DatasetIDs: models.GetDatasetIdsStarByUser(ctx.User.ID),
NeedAttachment: true,
CloudBrainType: ctx.QueryInt("type"),
JustNeedZipFile: true,
}
DatasetMultiple(ctx, opts)
}


+ 5
- 0
routers/repo/user_data_analysis.go View File

@@ -100,6 +100,7 @@ func getExcelHeader(ctx *context.Context) map[string]string {
excelHeader = append(excelHeader, ctx.Tr("user.static.focusrepocount"))
excelHeader = append(excelHeader, ctx.Tr("user.static.starrepocount"))
excelHeader = append(excelHeader, ctx.Tr("user.static.logincount"))
excelHeader = append(excelHeader, ctx.Tr("user.static.loginactioncount"))
excelHeader = append(excelHeader, ctx.Tr("user.static.watchedcount"))
excelHeader = append(excelHeader, ctx.Tr("user.static.commitcodesize"))
excelHeader = append(excelHeader, ctx.Tr("user.static.solveissuecount"))
@@ -165,6 +166,8 @@ func writeExcel(row int, xlsx *excelize.File, sheetName string, userRecord *mode
tmp = tmp + 1
xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.LoginCount)
tmp = tmp + 1
xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.LoginActionCount)
tmp = tmp + 1
xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.WatchedCount)
tmp = tmp + 1
xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.CommitCodeSize)
@@ -245,6 +248,8 @@ func writeExcelPage(row int, xlsx *excelize.File, sheetName string, userRecord *
tmp = tmp + 1
xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.LoginCount)
tmp = tmp + 1
xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.LoginActionCount)
tmp = tmp + 1
xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.WatchedCount)
tmp = tmp + 1
xlsx.SetCellValue(sheetName, getColumn(tmp)+rows, userRecord.CommitCodeSize)


+ 8
- 13
services/ai_task_service/cluster/c2net.go View File

@@ -145,19 +145,13 @@ func convertNoteBookReq2Grampus(req entity.CreateNoteBookTaskRequest) (models.Cr
}
}

var commandGpuDebug = "mkdir -p /tmp/dataset;jupyter lab --ServerApp.shutdown_no_activity_timeout=%s --TerminalManager.cull_inactive_timeout=%s --TerminalManager.cull_interval=%s --MappingKernelManager.cull_idle_timeout=%s --MappingKernelManager.cull_interval=%s --MappingKernelManager.cull_connected=True --MappingKernelManager.cull_busy=True --no-browser --ip=0.0.0.0 --allow-root --notebook-dir='%s' --port=$OCTOPUS_NOTEBOOK_PORT --LabApp.token='' --LabApp.allow_origin='*' --LabApp.base_url=$OCTOPUS_NOTEBOOK_BASE_URL;"
command := fmt.Sprintf(commandGpuDebug, setting.CullIdleTimeout, setting.CullIdleTimeout, setting.CullInterval, setting.CullIdleTimeout, setting.CullInterval, codePath)
if models.DCU == req.Tasks[0].Spec.ComputeResource {
command = ""
}
if models.NPU == req.Tasks[0].Spec.ComputeResource {
command = ""
}
log.Info("debug cmd=" + command)
tasks := make([]models.GrampusNotebookTask, len(req.Tasks))
for i := 0; i < len(req.Tasks); i++ {
t := req.Tasks[i]
task, err := convertNoteBookTask2Grampus(t, command)
task, err := convertNoteBookTask2Grampus(t, "")
if !req.IsSubscriber {
task.NoActAutoShutDownTimeout = 1800000
}
if err != nil {
return models.CreateGrampusNotebookRequest{}, err
}
@@ -479,8 +473,8 @@ func convertGrampus2GeneralTaskRes(res *models.GrampusNotebookResponse) *entity.
}
}

func (c C2NetClusterAdapter) RestartNoteBook(jobId string) (*entity.RestartNoteBookTaskResponse, error) {
res, err := grampus.RestartNotebookJob(jobId)
func (c C2NetClusterAdapter) RestartNoteBook(jobId string, autoStopDuration int64) (*entity.RestartNoteBookTaskResponse, error) {
res, err := grampus.RestartNotebookJob(jobId, autoStopDuration)
if err != nil {
log.Error("RestartNotebookJob err jobId=%s .%v", jobId, err)
return nil, err
@@ -598,7 +592,8 @@ func (c C2NetClusterAdapter) GetNoteBookOperationProfile(jobId string) (*entity.
}

r := parseC2NetEventsToOperationProfile(jobResult.NotebookEvents)
getJobResult, err := grampus.GetJob(jobId)

getJobResult, err := grampus.GetNotebookJob(jobId)
if err == nil && getJobResult != nil && getJobResult.ExitDiagnostics != "" {
r.Events = append(r.Events, entity.ProfileEvent{
Message: getJobResult.ExitDiagnostics,


+ 1
- 1
services/ai_task_service/cluster/cloudbrain_one.go View File

@@ -108,7 +108,7 @@ func convertCloudbrainOne2NoteBookRes(res *models.CreateJobResult) *entity.Creat
}
}

func (c CloudbrainOneClusterAdapter) RestartNoteBook(string) (*entity.RestartNoteBookTaskResponse, error) {
func (c CloudbrainOneClusterAdapter) RestartNoteBook(string, int64) (*entity.RestartNoteBookTaskResponse, error) {

return nil, nil
}


+ 5
- 5
services/ai_task_service/cluster/cloudbrain_two.go View File

@@ -170,7 +170,7 @@ func convertCloudbrainTwo2NoteBookRes(res *models.CreateNotebookResult) *entity.
}
}

func (c CloudbrainTwoClusterAdapter) RestartNoteBook(jobId string) (*entity.RestartNoteBookTaskResponse, error) {
func (c CloudbrainTwoClusterAdapter) RestartNoteBook(jobId string, autoStopDuration int64) (*entity.RestartNoteBookTaskResponse, error) {
param := models.NotebookAction{
Action: models.ActionStart,
}
@@ -181,9 +181,9 @@ func (c CloudbrainTwoClusterAdapter) RestartNoteBook(jobId string) (*entity.Rest

var res *models.NotebookActionResult
if task.Type == models.TypeCloudBrainTwo {
res, err = cloudbrain_two.ManageNotebook2(task.JobID, param)
res, err = cloudbrain_two.ManageNotebook2(task.JobID, param, int(autoStopDuration))
} else if task.Type == models.TypeCDCenter {
res, err = cloudbrain_two_cd.ManageNotebook(task.JobID, param)
res, err = cloudbrain_two_cd.ManageNotebook(task.JobID, param, int(autoStopDuration))
}
if err != nil {
log.Error("ManageNotebook err.jobID=%s err=%v", jobId, err)
@@ -227,9 +227,9 @@ func (c CloudbrainTwoClusterAdapter) StopNoteBook(opts entity.JobIdAndVersionId)
Action: models.ActionStop,
}
if task.Type == models.TypeCloudBrainTwo {
_, err = cloudbrain_two.ManageNotebook2(task.JobID, param)
_, err = cloudbrain_two.ManageNotebook2(task.JobID, param, 0)
} else if task.Type == models.TypeCDCenter {
_, err = cloudbrain_two_cd.ManageNotebook(task.JobID, param)
_, err = cloudbrain_two_cd.ManageNotebook(task.JobID, param, 0)
}
if err != nil {
log.Error("StopNoteBook err.jobID=%s err=%v", opts, err)


+ 1
- 1
services/ai_task_service/cluster/cluster_base.go View File

@@ -24,7 +24,7 @@ func GetCluster(t entity.ClusterType) (ClusterAdapter, error) {

type ClusterAdapter interface {
CreateNoteBook(req entity.CreateNoteBookTaskRequest) (*entity.CreateNoteBookTaskResponse, error)
RestartNoteBook(jobId string) (*entity.RestartNoteBookTaskResponse, error)
RestartNoteBook(jobId string, autoStopDuration int64) (*entity.RestartNoteBookTaskResponse, error)
DeleteNoteBook(opts entity.JobIdAndVersionId) error
StopNoteBook(opts entity.JobIdAndVersionId) error
QueryNoteBook(opts entity.JobIdAndVersionId) (*entity.QueryTaskResponse, error)


+ 3
- 2
services/ai_task_service/task/cloudbrain_one_notebook_task.go View File

@@ -1,6 +1,8 @@
package task

import (
"strings"

"code.gitea.io/gitea/entity"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/cloudbrain"
@@ -9,7 +11,6 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/routers/response"
"code.gitea.io/gitea/services/ai_task_service/context"
"strings"
)

type CloudbrainOneNotebookTaskTemplate struct {
@@ -153,7 +154,7 @@ func (g CloudbrainOneNotebookTaskTemplate) CallCreationAPI(ctx *context.Creation
Code: ctx.GetContainerDataArray(entity.ContainerCode),
PreTrainModel: ctx.GetContainerDataArray(entity.ContainerPreTrainModel),
OutPut: ctx.GetContainerDataArray(entity.ContainerOutPutPath),
AutoStopDuration: autoStopDurationMs,
AutoStopDuration: getAutoStopDurationMs(ctx.NewCloudbrain.TimeLimit, ctx.User.ID),
Capacity: setting.Capacity,
Queues: ctx.Queues,
Spec: ctx.Spec,


+ 3
- 2
services/ai_task_service/task/cloudbrain_two_notebook_task.go View File

@@ -130,7 +130,7 @@ func (g CloudbrainTwoNotebookTaskTemplate) CallCreationAPI(ctx *context.Creation
ResourceSpecId: ctx.Spec.SourceSpecId,
ImageId: form.ImageID,
ImageUrl: strings.TrimSpace(form.ImageUrl),
AutoStopDuration: autoStopDurationMs,
AutoStopDuration: getAutoStopDurationMs(ctx.NewCloudbrain.TimeLimit, ctx.User.ID),
Spec: ctx.Spec,
Datasets: ctx.GetContainerDataArray(entity.ContainerDataset),
Code: ctx.GetContainerDataArray(entity.ContainerCode),
@@ -161,7 +161,8 @@ func (g CloudbrainTwoNotebookTaskTemplate) CallRestartAPI(ctx *context.CreationC
return response.SYSTEM_ERROR
}
createTime := timeutil.TimeStampNow()
res, err := c.RestartNoteBook(ctx.SourceCloudbrain.JobID)
autoStopDuration := getAutoStopDurationMs(ctx.SourceCloudbrain.TimeLimit, ctx.User.ID)
res, err := c.RestartNoteBook(ctx.SourceCloudbrain.JobID, autoStopDuration)
if err != nil {
log.Error("CloudbrainTwoNotebookTaskTemplate RestartNoteBook err.Cloudbrain.JobID=%s err=%v", ctx.SourceCloudbrain.JobID, err)
return response.NewBizError(err)


+ 17
- 2
services/ai_task_service/task/grampus_notebook_task.go View File

@@ -3,6 +3,8 @@ package task
import (
"strings"

"code.gitea.io/gitea/services/role"

"code.gitea.io/gitea/entity"

"code.gitea.io/gitea/models"
@@ -238,6 +240,17 @@ func (t GrampusNoteBookTaskTemplate) Restart(ctx *context.CreationContext) (*ent

var autoStopDurationMs = int64(4 * 60 * 60 * 1000)

func getAutoStopDurationMs(taskTimeLimit int, uid int64) int64 {
if taskTimeLimit == 0 || !role.UserHasRole(uid, models.Subscriber) {
return autoStopDurationMs
} else {
if taskTimeLimit > 0 {
return int64(taskTimeLimit * 60 * 60 * 1000)
}
return -1
}
}

func (g GrampusNoteBookTaskTemplate) CallCreationAPI(ctx *context.CreationContext) *response.BizError {
c := g.GetMyCluster()
if c == nil {
@@ -261,12 +274,13 @@ func (g GrampusNoteBookTaskTemplate) CallCreationAPI(ctx *context.CreationContex
PreTrainModel: ctx.GetContainerDataArray(entity.ContainerPreTrainModel),
Code: ctx.GetContainerDataArray(entity.ContainerCode),
EnvVariables: models.GrampusEnvVarReq{},
AutoStopDuration: autoStopDurationMs,
AutoStopDuration: getAutoStopDurationMs(ctx.NewCloudbrain.TimeLimit, ctx.User.ID),
Capacity: setting.Capacity,
Queues: ctx.Queues,
Spec: ctx.Spec,
},
},
IsSubscriber: role.UserHasRole(ctx.User.ID, models.Subscriber),
}
createTime := timeutil.TimeStampNow()
res, err := c.CreateNoteBook(req)
@@ -293,7 +307,8 @@ func (g GrampusNoteBookTaskTemplate) CallRestartAPI(ctx *context.CreationContext
return response.SYSTEM_ERROR
}
createTime := timeutil.TimeStampNow()
res, err := c.RestartNoteBook(ctx.SourceCloudbrain.JobID)
autoStopDuration := getAutoStopDurationMs(ctx.SourceCloudbrain.TimeLimit, ctx.User.ID)
res, err := c.RestartNoteBook(ctx.SourceCloudbrain.JobID, autoStopDuration)
if err != nil {
log.Error("GrampusNoteBookTask RestartNoteBook err.Cloudbrain.JobID=%s err=%v", ctx.SourceCloudbrain.JobID, err)
return response.NewBizError(err)


+ 5
- 5
services/ai_task_service/task/grampus_online_infer_task.go View File

@@ -85,7 +85,7 @@ func (t GrampusOnlineInferTaskTemplate) Create(ctx *context.CreationContext) (*e
AsyncNextWithErrFun(t.BuildContainerData, t.GetAvailableQueues, t.CallCreationAPI, t.AfterCallCreationAPI4Async, t.NotifyCreation, t.HandleErr4Async).
Operate(ctx)
if err != nil {
log.Error("create GrampusNoteBookTask err.%v", err)
log.Error("create CreateOnlineInfer err.%v", err)
return nil, err
}
return &entity.CreateTaskRes{ID: ctx.NewCloudbrain.ID}, nil
@@ -134,11 +134,11 @@ func (g GrampusOnlineInferTaskTemplate) CallCreationAPI(ctx *context.CreationCon
createTime := timeutil.TimeStampNow()
res, err := c.CreateOnlineInfer(req)
if err != nil {
log.Error("GrampusNoteBookTask CreateNoteBook err.req=%+v err=%v", req, err)
log.Error("GrampusNoteBookTask CreateOnlineInfer err.req=%+v err=%v", req, err)
return response.NewBizError(err)
}
if res.JobID == "" {
log.Error("GrampusNoteBookTask CreateNoteBook failed.Cloudbrain.JobID=%s", ctx.SourceCloudbrain.JobID)
log.Error("GrampusNoteBookTask CreateOnlineInfer failed.Cloudbrain.JobID=%s", ctx.SourceCloudbrain.JobID)
return response.CREATE_FAILED
}
ctx.Response = &entity.CreationResponse{
@@ -152,7 +152,7 @@ func (g GrampusOnlineInferTaskTemplate) CallCreationAPI(ctx *context.CreationCon
func (g GrampusOnlineInferTaskTemplate) LoadSpec(ctx *context.CreationContext) *response.BizError {
//check specification
spec, err := resource.GetAndCheckSpec(ctx.User.ID, ctx.Request.SpecId, models.FindSpecsOptions{
JobType: models.JobTypeDebug,
JobType: models.JobTypeOnlineInference,
ComputeResource: ctx.Request.ComputeSource.Name,
Cluster: ctx.Request.Cluster.GetParentCluster(),
HasInternet: ctx.Request.HasInternet,
@@ -167,7 +167,7 @@ func (g GrampusOnlineInferTaskTemplate) LoadSpec(ctx *context.CreationContext) *
func (GrampusOnlineInferTaskTemplate) GetAvailableQueues(ctx *context.CreationContext) *response.BizError {
ctx.Queues = ctx.Spec.GetAvailableQueues(models.GetAvailableCenterIdOpts{
UserId: ctx.User.ID,
JobType: models.JobTypeDebug,
JobType: models.JobTypeOnlineInference,
HasInternet: ctx.Request.HasInternet,
})
return nil


+ 12
- 2
services/ai_task_service/task/opt_handler.go View File

@@ -6,6 +6,8 @@ import (
"path"
"strings"

"code.gitea.io/gitea/services/role"

"code.gitea.io/gitea/entity"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
@@ -106,6 +108,7 @@ func (DefaultCreationHandler) BuildRequest4Restart(ctx *context.CreationContext)
IsRestartRequest: true,
DatasetNames: task.DatasetName,
HasInternet: models.SpecInternetQuery(task.HasInternet),
TimeLimit: task.TimeLimit,
}
log.Info("BuildRequest4Restart success.displayJobName=%s jobType=%s cluster=%s", ctx.Request.DisplayJobName, ctx.Request.JobType, ctx.Request.Cluster)
return nil
@@ -349,9 +352,10 @@ func (DefaultCreationHandler) CheckMultiRequest(ctx *context.CreationContext) *r
log.Error("GetGrampusCountByUserID failed:%v", err)
return response.SYSTEM_ERROR
}
if count >= 1 {
limitNum := GetUserMultiLimitNum(ctx.User.ID, ctx.Request.JobType)
if count >= limitNum {
log.Error("the user already has running or waiting task.")
return response.MULTI_TASK
return response.MULTI_TASK.WithParams(count)
}
log.Info("CheckMulti success.displayJobName=%s jobType=%s cluster=%s", ctx.Request.DisplayJobName, ctx.Request.JobType, ctx.Request.Cluster)

@@ -418,6 +422,10 @@ func (DefaultCreationHandler) InsertCloudbrainRecord4Async(ctx *context.Creation
if req.IsFileNoteBookRequest {
branchName = req.FileBranchName
}
//if a normal user set TimeLimit, it does not work
if !role.UserHasRole(ctx.User.ID, models.Subscriber) {
req.TimeLimit = 0
}
c := &models.Cloudbrain{
Status: models.LocalStatusPreparing,
UserID: ctx.User.ID,
@@ -450,6 +458,7 @@ func (DefaultCreationHandler) InsertCloudbrainRecord4Async(ctx *context.Creation
GpuQueue: ctx.Spec.QueueCode,
AppName: req.AppName,
HasInternet: int(req.HasInternet),
TimeLimit: req.TimeLimit,
}

err := models.CreateCloudbrain(c)
@@ -618,6 +627,7 @@ func (DefaultCreationHandler) CreateCloudbrainRecord4Restart(ctx *context.Creati
ModelId: req.PretrainModelId,
GpuQueue: ctx.Spec.QueueCode,
HasInternet: int(req.HasInternet),
TimeLimit: ctx.SourceCloudbrain.TimeLimit,
}
err := models.RestartCloudbrain(ctx.SourceCloudbrain, c)



+ 8
- 4
services/ai_task_service/task/task_creation_info.go View File

@@ -10,6 +10,7 @@ import (
"code.gitea.io/gitea/routers/response"
"code.gitea.io/gitea/services/cloudbrain/cloudbrainTask"
"code.gitea.io/gitea/services/reward/point/account"
"code.gitea.io/gitea/services/role"
)

func GetAITaskCreationInfo(req entity.GetAITaskCreationInfoReq) (*entity.CreationRequiredInfo, *response.BizError) {
@@ -19,13 +20,15 @@ func GetAITaskCreationInfo(req entity.GetAITaskCreationInfoReq) (*entity.Creatio
waitCount := cloudbrain.GetWaitingCloudbrainCount(req.ClusterType.GetCloudbrainType(), req.ComputeSource.GetCloudbrainFormat(), req.JobType)
result.WaitCount = waitCount
//查询是否有正在运行的任务
notStopTaskCount := 0
if req.IsOnlineType {
notStopTaskCount, _ := cloudbrainTask.GetNotFinalStatusTaskCount(req.User.ID, string(models.JobTypeOnlineInference))
result.NotStopTaskCount = notStopTaskCount
notStopTaskCount, _ = cloudbrainTask.GetNotFinalStatusTaskCount(req.User.ID, string(models.JobTypeOnlineInference))
} else {
notStopTaskCount, _ := cloudbrainTask.GetNotFinalStatusTaskCount(req.User.ID, string(req.JobType))
result.NotStopTaskCount = notStopTaskCount
notStopTaskCount, _ = cloudbrainTask.GetNotFinalStatusTaskCount(req.User.ID, string(req.JobType))
}
result.NotStopTaskCount = notStopTaskCount
limitNum := GetUserMultiLimitNum(req.User.ID, req.JobType)
result.CanCreateMore = notStopTaskCount < limitNum

//获取代码分支
if req.GitRepo != nil {
@@ -87,6 +90,7 @@ func GetAITaskCreationInfo(req entity.GetAITaskCreationInfoReq) (*entity.Creatio
} else {
result.AllowedWorkerNum = []int{1}
}
result.IsSubscriber = role.UserHasRole(req.User.ID, models.Subscriber)
return result, nil
}



+ 20
- 0
services/ai_task_service/task/task_service.go View File

@@ -21,6 +21,7 @@ import (
"encoding/json"
"errors"
"fmt"
"math"
"net/http"
"net/url"
"path"
@@ -168,6 +169,7 @@ func buildAITaskInfo(task *models.Cloudbrain, creator *models.User, config *enti
UserId: task.UserID,
AppName: task.AppName,
HasInternet: task.HasInternet,
TimeLimit: task.TimeLimit,
}, nil
}

@@ -922,3 +924,21 @@ func hasLongRunningNotificationSendBefore(cloudbrain *models.Cloudbrain, duratio
}
return !success
}

func GetUserMultiLimitNum(userId int64, jobType models.JobType) int {
defaultLimit := 1
roleList, err := models.GetUserRoleList(userId)
if err != nil || len(roleList) == 0 {
return defaultLimit
}
roleLimitMap := setting.ROLE_MULTI_LIMIT_MAP[string(jobType)]
if roleLimitMap == nil || len(roleLimitMap) == 0 {
return defaultLimit

}
realNum := defaultLimit
for _, r := range roleList {
realNum = int(math.Max(float64(roleLimitMap[string(r.RoleType)]), float64(realNum)))
}
return realNum
}

+ 2
- 1
services/repository/contributor.go View File

@@ -8,6 +8,7 @@ import (
"code.gitea.io/gitea/modules/redis/redis_key"
"encoding/json"
"github.com/patrickmn/go-cache"
"math/rand"
"time"
)

@@ -31,7 +32,7 @@ func GetRepoTopNContributors(repo *models.Repository, N int) ([]*models.Contribu
log.Debug("Get RepoTopNContributors from disk,repo.ID = %d ", repo.ID)
jsonVal, err := json.Marshal(&ContributorCacheVal{Contributors: contributorInfos, Total: total})
if err == nil {
redis_client.Setex(redis_key.RepoTopNContributors(repo.ID, N), string(jsonVal), 2*time.Minute)
redis_client.Setex(redis_key.RepoTopNContributors(repo.ID, N), string(jsonVal), 30*24*time.Hour+time.Duration(rand.Intn(10))*time.Hour)
}
return contributorInfos, total
}


+ 5
- 0
services/role/role.go View File

@@ -18,6 +18,11 @@ var roleMap = map[models.RoleType]*models.Role{
Name: "监测管理员",
Description: "拥有监测的管理员权限",
},
models.Subscriber: {
Type: models.Subscriber,
Name: "Subscriber",
Description: "Subscriber",
},
}

func GetRole(roleType models.RoleType) *models.Role {


+ 6
- 0
templates/admin/user/edit.tmpl View File

@@ -109,6 +109,12 @@
</div>
</div>
{{end}}
<div class="inline field">
<div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.users.subscriber"}}</strong></label>
<input name="subscriber" type="checkbox" {{if .User.IsSubscriber}}checked{{end}}>
</div>
</div>

<div class="ui divider"></div>



+ 66
- 0
templates/admin/user/list.tmpl View File

@@ -12,6 +12,54 @@
<div class="ui attached segment">
{{template "admin/base/search" .}}
</div>
<div class="ui attached segment">
<div class="ui selection dropdown selected subscriberTypeSel" style="margin-right:10px">
<input type="hidden" name="subscriberType">
<i class="dropdown icon"></i>
<div class="text">
{{if not (or (eq .SubscriberType 1) (eq .SubscriberType 2))}}{{.i18n.Tr "admin.users.all_subscriber_or_not"}}{{end}}
{{if eq .SubscriberType 1}}{{.i18n.Tr "admin.users.subscriber"}}{{end}}
{{if eq .SubscriberType 2}}{{.i18n.Tr "admin.users.free_users"}}{{end}}
</div>
<div class="menu">
<a class="item {{if not (or (eq .SubscriberType 1) (eq .SubscriberType 2))}}active selected{{end}}" data-value="0"
href="{{$.Link}}?adminType={{.AdminType}}&sort={{.SortType}}&q={{$.Keyword}}">
{{.i18n.Tr "admin.users.all_subscriber_or_not"}}
</a>
<a class="item {{if eq .SubscriberType 1}}active selected{{end}}" data-value="1"
href="{{$.Link}}?sort={{.SortType}}&q={{$.Keyword}}&subscriberType=1&adminType={{.AdminType}}">
{{.i18n.Tr "admin.users.subscriber"}}
</a>
<a class="item {{if eq .SubscriberType 2}}active selected{{end}}" data-value="2"
href="{{$.Link}}?sort={{.SortType}}&q={{$.Keyword}}&subscriberType=2&adminType={{.AdminType}}">
{{.i18n.Tr "admin.users.free_users"}}
</a>
</div>
</div>
<div class="ui selection dropdown selected adminTypeSel">
<input type="hidden" name="adminType">
<i class="dropdown icon"></i>
<div class="text">
{{if not (or (eq .AdminType 1) (eq .AdminType 2))}}{{.i18n.Tr "admin.users.all_admin_or_not"}}{{end}}
{{if eq .AdminType 1}}{{.i18n.Tr "admin.users.admin"}}{{end}}
{{if eq .AdminType 2}}{{.i18n.Tr "admin.users.common"}}{{end}}
</div>
<div class="menu">
<a class="item {{if not (or (eq .AdminType 1) (eq .AdminType 2))}}active selected{{end}}" data-value="0"
href="{{$.Link}}?sort={{.SortType}}&q={{$.Keyword}}&subscriberType={{.SubscriberType}}">
{{.i18n.Tr "admin.users.all_admin_or_not"}}
</a>
<a class="item {{if eq .AdminType 1}}active selected{{end}}" data-value="1"
href="{{$.Link}}?sort={{.SortType}}&q={{$.Keyword}}&adminType=1&subscriberType={{.SubscriberType}}">
{{.i18n.Tr "admin.users.admin"}}
</a>
<a class="item {{if eq .AdminType 2}}active selected{{end}}" data-value="2"
href="{{$.Link}}?sort={{.SortType}}&q={{$.Keyword}}&adminType=2&subscriberType={{.SubscriberType}}">
{{.i18n.Tr "admin.users.common"}}
</a>
</div>
</div>
</div>
<div class="ui attached table segment">
<table class="ui very basic striped table">
<thead>
@@ -21,6 +69,7 @@
<th>{{.i18n.Tr "email"}}</th>
<th>{{.i18n.Tr "admin.users.activated"}}</th>
<th>{{.i18n.Tr "admin.users.bind_phone"}}</th>
<th>{{.i18n.Tr "admin.users.subscriber"}}</th>
<th>{{.i18n.Tr "admin.users.admin"}}</th>
<th>{{.i18n.Tr "admin.users.restricted"}}</th>
<th>{{.i18n.Tr "admin.users.repos"}}</th>
@@ -37,6 +86,7 @@
<td><span class="text truncate email">{{.Email}}</span></td>
<td><i class="fa fa{{if .IsActive}}-check{{end}}-square-o"></i></td>
<td><i class="fa fa{{if .PhoneNumber}}-check{{end}}-square-o"></i></td>
<td><i class="fa fa{{if .IsSubscriber}}-check{{end}}-square-o"></i></td>
<td><i class="fa fa{{if .IsAdmin}}-check{{end}}-square-o"></i></td>
<td><i class="fa fa{{if .IsRestricted}}-check{{end}}-square-o"></i></td>
<td>{{.NumRepos}}</td>
@@ -57,3 +107,19 @@
</div>
</div>
{{template "base/footer" .}}
<script>
;(function(){
var params = new URLSearchParams(window.location.search);
var subscriberType = params.get('subscriberType') || '';
var adminType = params.get('adminType') || '';
var sort = params.get('sort') || '';
$('.segment .ui.form .action').append('<input name="subscriberType" value="' + subscriberType +'" style="display:none">');
$('.segment .ui.form .action').append('<input name="adminType" value="' + adminType +'" style="display:none">');
$('.segment .ui.form .action').append('<input name="sort" value="' + sort +'" style="display:none">');
$('.segment .filter.menu .menu .item').each(function(index, item) {
var href = $(item).attr('href');
href += '&subscriberType=' + subscriberType + '&adminType=' + adminType;
$(item).attr('href', href);
});
})();
</script>

+ 23
- 4
web_src/js/components/Model.vue View File

@@ -143,9 +143,8 @@
<a class="op-btn" style="color: #fa8c16;" v-show="repoIsPrivate == false && scope.row.isPrivate==false && scope.row.isCanOper" @click="
modifyModelStatus(scope.row.id, scope.row.cName, scope.row.rowKey,true)
">{{ i18n.modelaccess_setprivate }}</a>
<a class="op-btn"
:href="loadhref + scope.row.id"
@click="downloadAllModel(scope.row.size,scope.row.id)"
:class="{ disabled: !scope.row.isCanDownload }"
>{{ i18n.model_download }}</a>
<a class="op-btn"
@@ -173,15 +172,20 @@
</el-pagination>
</div>
</div>
<CommonTipsDialog ref="childModelTips" type="modelFile"
:promotePath="codeUsePromotePath" :title="i18n.codeDownDlgTitle"
:closeText="i18n.close">
</CommonTipsDialog>
</div>
</template>

<script>
import { modifyModelStatus } from '~/apis/modules/modelmanage';
import CommonTipsDialog from '~/components/CommonTipsDialog.vue';
const { _AppSubUrl, _StaticUrlPrefix, csrf } = window.config;
const REPOISPRIVATE = window.REPO_IS_PRIVATE;
export default {
components: {},
components: {CommonTipsDialog},
data() {
return {
curHref: window.location.href,
@@ -201,7 +205,8 @@ export default {
data: "",
timer: null,
timerFlag: false,
repoIsPrivate: REPOISPRIVATE,
repoIsPrivate: REPOISPRIVATE,
codeUsePromotePath: `tips/modelDown/sdkcode${ document.documentElement.attributes["lang"].nodeValue == "zh-CN" ? '' : '_en'}.md`,
};
},
methods: {
@@ -541,6 +546,20 @@ export default {
console.log(e);
}
},
downloadAllModel(size,id) {
if (size / (1024 * 1024) <= 500) {
let downloadElement = document.createElement('a')
let href = `${this.loadhref}${id}`
downloadElement.href = href
document.body.appendChild(downloadElement)
downloadElement.click() //点击下载
document.body.removeChild(downloadElement) //下载完成移除元素
} else {
this.$nextTick(() => {
this.$refs.childModelTips.handlerOpen()
})
}
}
},
computed: {
loadhref() {


+ 10
- 1
web_src/js/components/dataset/referenceDataset.vue View File

@@ -219,11 +219,14 @@
</template>

<el-dialog
:title="i18n.linked_datasets"
:visible.sync="dialogVisible"
:width="dialogWidth"
@closed="refreshData"
>
<template #title>
<span>{{ i18n.linked_datasets }}</span>
<span class="refer-data-tips"><span>*</span>{{ i18n.linked_datasets_tips }}</span>
</template>
<div class="ui icon input dataset-search-vue">
<i
class="search icon"
@@ -744,4 +747,10 @@ export default {
.disabled1 {
opacity: 0.45 !important;
}
.refer-data-tips {
margin-left: 2rem;
display: inline-block;
color: rgb(242, 113, 28);
font-size: 12px;
}
</style>

+ 4
- 2
web_src/js/features/i18nVue.js View File

@@ -40,6 +40,7 @@ export const i18nVue = {
speech_synthesis: "语音合成",
current_dataset: "当前数据集",
linked_datasets: "关联数据集",
linked_datasets_tips: "只能关联非本项目的公开的数据集",
unfavorite: "取消收藏",
favorite: "收藏",
disassociate: "取消关联",
@@ -149,7 +150,7 @@ export const i18nVue = {
warmPrompt: '温馨提示',
submittedSuccessfully: '提交成功!',
submittedFailed: '提交失败!',
codeDownDlgTitle: '如何在OpenI协作平台下载模型',
cloudeBrainMirror: {
cloud_brain_mirror: '云脑镜像',
public_mirror: '公开镜像',
@@ -284,6 +285,7 @@ export const i18nVue = {
speech_synthesis: "speech synthesis",
current_dataset: "Current Dataset",
linked_datasets: "Linked Datasets",
linked_datasets_tips: "Only can associate with publicly available datasets that are not part of this repo",
unfavorite: "UnLike",
favorite: "Like",
disassociate: "Unlink",
@@ -396,7 +398,7 @@ export const i18nVue = {
warmPrompt: 'Tips',
submittedSuccessfully: 'Submitted Successfully!',
submittedFailed: 'Submitted Failed!',
codeDownDlgTitle: 'How to download models on the OpenI',
cloudeBrainMirror: {
cloud_brain_mirror: 'Cloud Brain Mirror',
public_mirror: 'Public Mirror',


+ 31
- 27
web_src/js/index.js View File

@@ -5388,33 +5388,37 @@ function initChartsNpu() {

initChartsNpu();

Fancybox.bind('.gallery img', {
// Do not create a gallery
groupAttr: null,

// Do not hide page scrollbars
hideScrollbar: false,

// Disable drag to close guesture
dragToClose: false,

// Hide close button
closeButton: false,

// Disable toolbar
Toolbar: false,

// Disable zoom animation; close on click and wheel events
Image: {
zoom: false,
click: "close",
wheel: "close",
},

// Custom animations
showClass: "fancybox-zoomIn",
hideClass: "fancybox-zoomOut",
});
; (function () {
setTimeout(() => {
$('.gallery img').each(function (_, ele) {
const elementA = $(ele).closest('a');
if (elementA.length && elementA.attr('href') != $(ele).attr('src')) {
$(ele).attr('no-gallery', '');
}
});
Fancybox.bind('.gallery img:not([no-gallery])', {
// Do not create a gallery
groupAttr: null,
// Do not hide page scrollbars
hideScrollbar: false,
// Disable drag to close guesture
dragToClose: false,
// Hide close button
closeButton: false,
// Disable toolbar
Toolbar: false,
// Disable zoom animation; close on click and wheel events
Image: {
zoom: false,
click: "close",
wheel: "close",
},
// Custom animations
showClass: "fancybox-zoomIn",
hideClass: "fancybox-zoomOut",
});
}, 1 * 1000);
})();

function initTopToHome() {
const topToHomeEl = $('.__go-top');


+ 1
- 1
web_src/less/vendor/fancyapp.less View File

@@ -752,7 +752,7 @@ body:not(.is-using-mouse) .fancybox__container :focus {
display: block;
}

.gallery img {
.gallery img:not([no-gallery]) {
cursor: zoom-in;
}



+ 16
- 11
web_src/vuepages/components/CommonTipsDialog.vue View File

@@ -5,16 +5,17 @@
<slot></slot>
</div>
<BaseDialog :visible="visible" :title="title" :show-close="showClose" @closed="closeDialog"
:appendToBody="appendToBody">
:appendToBody="appendToBody" >
<el-skeleton style=" padding:0 20px;" :rows="10" :loading="loading" animated/>
<div class="markdown" v-html="content"></div>
<div slot="footer" class="dialog-footer">
<el-button class="close-btn" size="default" type="success" @click="closeDialog">{{
$t('cloudbrainObj.dialogTips.tips8') }}</el-button>
<el-button class="close-btn" size="default" type="success" @click="closeDialog">
{{ closeText }}
</el-button>
</div>
</BaseDialog>
</div>
</template>

<script>
import BaseDialog from '~/components/BaseDialog.vue';
import { getPromoteData, getMarkdownHtml, getSDKCode } from '~/apis/modules/common';
@@ -29,7 +30,8 @@ export default {
promotePath: { type: String, default: '' },
appendToBody: { type: Boolean, default: false },
type: { type: String, default: '' },
data: { type: Object, default: () => ({}) }
data: { type: Object, default: () => ({}) },
closeText: { type: String, default: '' },
},
data() {
return {
@@ -55,23 +57,27 @@ export default {
codeEl.innerHTML = hljs.highlight('python', code).value;
const copyBtn = document.createElement('div');
copyBtn.classList = ['copy-btn'];
copyBtn.innerHTML = `<a href="javascript:;" class="ui poping inline up clipboard" id="clipboard-${this.sparkMD5Hash(txt)}"
data-position="top center" data-variation="inverted tiny" data-success="${this.$t('copySuccess')}"
data-content="${this.$t('copy')}" data-original="${this.$t('copy')}"
data-clipboard-text=""><i style="font-size:14px;" class="copy outline icon"></i></a>`;
copyBtn.querySelector('a').setAttribute('data-clipboard-text', txt);
if (this.type !== 'modelFile') {
copyBtn.innerHTML = `<a href="javascript:;" class="ui poping inline up clipboard" id="clipboard-${this.sparkMD5Hash(txt)}"
data-position="top center" data-variation="inverted tiny" data-success="${this.$t('copySuccess')}"
data-content="${this.$t('copy')}" data-original="${this.$t('copy')}"
data-clipboard-text=""><i style="font-size:14px;" class="copy outline icon"></i></a>`;
copyBtn.querySelector('a').setAttribute('data-clipboard-text', txt);
}
codeBlockI.outerHTML = `<div class="code-content">${codeBlockI.outerHTML}${copyBtn.outerHTML}</div>`;
}
return html.innerHTML;
},
getMarkdown(str) {
getMarkdownHtml(str).then(res => {
this.loading = false;
const html = res.data;
this.content = this.hljsAndInsertCopyButton(html);
this.$nextTick(() => {
initClipboard('.base-dlg .clipboard');
});
}).catch(err => {
this.loading = false;
console.log(err);
});
},
@@ -79,7 +85,6 @@ export default {
if (!this.promotePath) return;
this.loading = true;
getPromoteData(this.promotePath).then(res => {
this.loading = false;
let contentStr = res.data;
if (contentStr) {
if (this.type == 'model' && this.data.name) {


+ 155
- 0
web_src/vuepages/components/cloudbrain/RunTimeLimit.vue View File

@@ -0,0 +1,155 @@
<template>
<div class="form-row">
<div class="left-area">
<div class="title">
<span :class="required ? 'required' : ''">{{ $t('cloudbrainObj.taskIsAutomaticStop') }}</span>
</div>
<div class="content">
<el-select class="spec-sel field-input" v-model="type" @change="changeType">
<el-option :value="1" :label="$t('cloudbrainObj.automaticStop')" key="1"></el-option>
<el-option :value="2" :label="$t('cloudbrainObj.manualStop')" key="2"></el-option>
</el-select>
<div v-if="type == 1" class="tips">{{ $t('cloudbrainObj.automaticStopTips') }}</div>
<div v-if="type == 2" class="tips">{{ $t('cloudbrainObj.manualStopTips') }}</div>
<el-radio-group class="time-group-sel" v-if="type == 1" v-model="limitTime" @change="changeTime">
<el-radio :label="1">{{ $t('cloudbrainObj.numOfHours', { num: 1 }) }}</el-radio>
<el-radio :label="2">{{ $t('cloudbrainObj.numOfHours', { num: 2 }) }}</el-radio>
<el-radio :label="4">{{ $t('cloudbrainObj.numOfHours', { num: 4 }) }}</el-radio>
<el-radio :label="6">{{ $t('cloudbrainObj.numOfHours', { num: 6 }) }}</el-radio>
<br>
<el-radio class="custom" :label="-1">{{ $t('cloudbrainObj.customize') }}</el-radio>
<div class="limit-time-inp-c content" :class="errStatus ? 'error' : ''" v-if="type == 1 && limitTime == -1">
<div class="limit-time-inp-wrap">
<el-input class="limit-time-inp field-input" v-model="limitTimeInputValue"
@input="handleInput"></el-input>
<div class="unit">
{{ $t('cloudbrainObj.hours') }} ({{ $t('cloudbrainObj.customizeTimeLimitPlaceholder') }})
</div>
</div>
</div>
</el-radio-group>
</div>
</div>
<div class="right-area"></div>
</div>
</template>

<script>

export default {
name: 'RunTimeLimit',
props: {
value: { type: String, required: true },
required: { type: Boolean, default: true },
},
data() {
return {
type: 1,
limitTime: 4,
limitTimeInputValue: '',
currentValue: '',
errStatus: false,
};
},
watch: {
value: {
immediate: true,
handler(newVal) {
newVal = newVal === undefined ? '' : newVal;
this.currentValue = newVal.toString();
}
}
},
methods: {
check() {
if (this.type == 2) {
this.currentValue = '-1';
this.errStatus = false;
} else {
if (this.limitTime == -1) {
const value = parseInt(this.limitTimeInputValue);
if (value >= 1 && value <= 24) {
this.limitTimeInputValue = value;
this.currentValue = this.limitTimeInputValue.toString();
this.errStatus = false;
} else {
this.currentValue = '';
this.limitTimeInputValue = '';
this.errStatus = true;
}
} else {
this.currentValue = this.limitTime.toString();
this.errStatus = false;
}
}
return !this.errStatus;
},
changeType() {
this.check();
this.errStatus = false;
this.$emit('input', this.currentValue);
},
changeTime() {
this.check();
this.errStatus = false;
this.$emit('input', this.currentValue);
},
handleInput() {
const value = parseInt(this.limitTimeInputValue);
if (value >= 1 && value <= 24) {
this.limitTimeInputValue = value;
} else {
this.limitTimeInputValue = this.limitTimeInputValue.slice(0, -1);
}
this.check();
this.$emit('input', this.currentValue);
},
},
beforeMount() { }
};
</script>

<style scoped lang="less">
@import 'cloudbrain.less';

.time-group-sel {
margin-top: 12px;
width: 100%;

/deep/ .el-radio {
margin-bottom: 16px;
}

.custom {
margin-right: 10px;
}

.limit-time-inp-c {
display: inline-block;
position: relative;
width: auto !important;

.limit-time-inp-wrap {
display: flex;
align-items: center;

/deep/.el-input__inner {
height: 32px !important;
line-height: 32px !important;
font-size: 13px !important;
text-align: center;
}
}

.limit-time-inp {
width: 60px !important;
}

.unit {
margin-left: 5px;
font-size: 14px;
color: #606266;
}
}
}
</style>

+ 15
- 3
web_src/vuepages/components/cloudbrain/details/ConfigInfo.vue View File

@@ -4,12 +4,12 @@
<div :key="item + '-' + index"
v-if="item != 'dataset' && item != 'modelList' && item != 'failedReason' && item != 'generalTaskCodeTips'"
class="item-block">
<div class="title"> {{ renderTitle(item) }} </div>
<div class="title" :title="renderTitle(item)">{{ renderTitle(item) }}</div>
<div class="content" v-html="renderContent(item)"></div>
</div>
<div :key="item + '-' + index" v-if="item == 'failedReason' && renderContent(item)"
class="item-block item-failed-reason">
<div class="title"> {{ renderTitle(item) }} </div>
<div class="title" :title="renderTitle(item)">{{ renderTitle(item) }}</div>
<div class="content" v-html="renderContent(item)"></div>
</div>
<div :key="item + '-' + index" v-if="item == 'dataset' && data.task.dataset_list.length > 0"
@@ -148,6 +148,7 @@ export default {
return hljs.highlight('python', str).value;
},
renderTitle(key) {
const task = this.data.task;
let result = key;
switch (key) {
case 'taskName':
@@ -240,6 +241,9 @@ export default {
case 'failedReason':
result = i18n.t('cloudbrainObj.failedReason');
break;
case 'timeLimit':
result = task.time_limit == 0 ? '' : i18n.t('cloudbrainObj.taskIsAutomaticStop');
break;
default:
break;
}
@@ -369,6 +373,14 @@ export default {
case 'failedReason':
result = task.failed_reason;
break;
case 'timeLimit':
if (task.time_limit == 0) {
result = '';
} else if (task.time_limit == -1) {
result = i18n.t('cloudbrainObj.manualStop');
} else {
result = i18n.t('cloudbrainObj.automaticStop') + `(${i18n.t('cloudbrainObj.numOfHours', { num: task.time_limit })})`;
}
default:
break;
}
@@ -396,7 +408,7 @@ export default {
min-width: 200px;

.title {
width: 105px;
width: 118px;
padding-right: 20px;
color: #8a8e99;
font-size: 12px;


+ 46
- 16
web_src/vuepages/components/cloudbrain/details/ResourceUseage.vue View File

@@ -40,7 +40,7 @@ const chartOptions = {
color: "#fff",
},
axisPointer: {
type: "line",
type: "cross",
},
appendToBody: true,
},
@@ -53,14 +53,23 @@ const chartOptions = {
},
name: "",
},
yAxis: {
yAxis: [{
show: true,
name: "(%)",
position: 'left',
axisLine: {
show: true,
},
axisTick: { show: true },
},
}, {
show: false,
name: "Value",
position: 'right',
axisLine: {
show: true,
},
axisTick: { show: true },
}],
series: [],
};

@@ -81,7 +90,10 @@ export default {
};
},
methods: {
getChartData() {
checkIsValueType(name) {
return name.indexOf('Bytes') >= 0 || name.indexOf('Rate') >= 0 || name.indexOf('numProcesses') >= 0;
},
getChartData(useRefreshBtn) {
const task = this.data.task;
this.loading = true;
getAiTaskResourceUseage({
@@ -97,21 +109,20 @@ export default {
const data = res.data || {};
const metricsInfo = data.metrics_info || [];
let filterData = metricsInfo.filter((item) => {
return ![
"recvBytesRate",
"diskWriteRate",
"sendBytesRate",
"diskReadRate",
].includes(item.name);
// return !["recvBytesRate", "diskWriteRate", "sendBytesRate", "diskReadRate",].includes(item.name);
return (item.value && item.value.length);
});
filterData = sortBy(filterData, "name");
const legenData = filterData.map((item) => {
return item.name;
});
let valueTypeCount = 0;
const seriesData = filterData.map((item) => {
const value = item.value.map((item) => {
return item > 0 ? item : "0";
const value = (item.value || []).map((item) => {
return item > 0 ? Number(Number(item).toFixed(3)) : "0";
});
const valueType = this.checkIsValueType(item.name);
valueTypeCount += (valueType ? 1 : 0);
const seriesOption = {
name: item.name,
type: "line",
@@ -119,6 +130,7 @@ export default {
symbolSize: 10,
smooth: true,
showSymbol: false,
yAxisIndex: valueType ? 1 : 0,
lineStyle: {
width: 2,
shadowColor: "rgba(0,0,0,0.3)",
@@ -136,6 +148,24 @@ export default {
(_, index) => index * xInterval
);
chartOptions.legend.data = legenData;
const legendSelected = {};
legenData.forEach(element => {
const valueType = this.checkIsValueType(element);
legendSelected[element] = !valueType;
});
if (valueTypeCount > 0) {
chartOptions.yAxis[1].show = true;
} else if (chartOptions.yAxis[1]) {
chartOptions.yAxis.pop();
}
if (useRefreshBtn && chartHandler && Object.keys(legendSelected).join('') == Object.keys(chartOptions.legend.selected || {}).join('')) {
try {
const selected = chartHandler.getOption().legend[0].selected;
chartOptions.legend.selected = selected;
} catch { }
} else {
chartOptions.legend.selected = legendSelected;
}
chartOptions.series = seriesData;
chartOptions.grid.top = '60';
if (legenData.length == 0) {
@@ -156,7 +186,7 @@ export default {
console.log(err);
});
},
refresh() {
refresh(useRefreshBtn) {
const task = this.data.task;
if (this.configs.multiNodes) {
this.loading = true;
@@ -169,13 +199,13 @@ export default {
if (res && res.code == 0) {
this.multiNodesData = res.data?.nodes || [];
}
this.getChartData();
this.getChartData(useRefreshBtn);
}).catch(err => {
this.loading = false;
console.log(err);
});
} else {
this.getChartData();
this.getChartData(useRefreshBtn);
}
},
changeNode() {
@@ -187,7 +217,7 @@ export default {
},
beforeMount() {
chartOptions.xAxis.name = this.$t('cloudbrainObj.chartTime');
chartOptions.yAxis.name = this.$t('cloudbrainObj.chartResourceUsage');
chartOptions.yAxis[0].name = this.$t('cloudbrainObj.chartResourceUsage');
},
mounted() {
window.addEventListener('resize', this.resize);


+ 22
- 2
web_src/vuepages/langs/config/en-US.js View File

@@ -312,11 +312,12 @@ const en = {
modelCreateFailed: 'Model create failed',
modelModifyFailed: 'Model modify failed',
fileUpload: 'File upload',
folderUpload: 'Folder upload',
upload: 'Upload',
uploadPath: 'Upload Path',
uploadStatus: 'Upload status',
modelFileUploadDefaultTips: 'Click to add files or drag files here directly',
modelFileUploadErrTips: 'Up to 10 files can be uploaded at a time, and the total file size of the model does not exceed {size}GB',
modelFileUploadErrTips: 'Up to 10 files can be uploaded at a time, and the total file size of the model does not exceed {size}',
modelFileNameTips: 'The file name should not exceed 128 characters',
fileIstoBig: 'File is to big',
removeFile: 'Rmove file',
@@ -327,6 +328,14 @@ const en = {
fileHasAlreadyInTheModel: 'This file has already in the model: ',
fileEqualInTheModel: 'This file content is the same as other file: ',
fileExistInTheModel: 'This file content is the same as other file: ',
uploadFile: 'Upload files',
uploadFolder: 'Upload folders',
addUploadFolder: 'Add upload folder',
clearAll: 'Clear all',
fileCountAndSize: 'Selected files: {count}, total size: {size}',
folderUploadPlaceholder: 'Click to add a folder or drag folders/files here directly',
folderUploadTips: 'The number of files uploaded this time does not exceed {maxCount}, and the total size does not exceed {maxSize}, please use <a target="blank" href="https://openi.pcl.ac.cn/docs/index.html#/api/list">SDK</a> to upload too many large files.',
folderUploadErrorTips: 'The number of files uploaded this time does not exceed {maxCount}, and the total size does not exceed {maxSize}',
basicInfo: 'Basic information',
modelSize: 'Model size',
descr: 'Description',
@@ -460,6 +469,7 @@ const en = {
model_sdk_use_way: 'Model SDK use way',
codeUseDlgTriggerTxt: 'How to use models in OpenI',
codeUseDlgTitle: 'How to use models on the OpenI',
codeDownDlgTitle: 'How to download models on the OpenI',
model_not_equal_file: "Cannot select models with the same name.",
},
datasetObj: {
@@ -612,7 +622,8 @@ const en = {
createTask: 'Create Task',
cluster: 'Resource cluster',
computeResource: 'Computing resources',
sameTaskTips1: 'You have created an <span>equivalent task</span> that is waiting or running, please wait for the task to finish before creating it.',
sameTaskTips0: 'You have created an <span>equivalent task</span> that is waiting or running, please wait for the task to finish before creating it.',
sameTaskTips1: 'You have created {count} <span>equivalent tasks</span> that are waiting or running, please wait for these task to finish before creating it.',
sameTaskTips2: 'You can view all your Cloud Brain tasks in <a href="/cloudbrains" target="_blank"> Home &gt; Cloudbrain Task </a>.',
pathTips1: 'The code is storaged in <strong style="color:#010101">{code}</strong>, the dataset is storaged in <strong style="color:#010101">{dataset}</strong>, the pre-trained model is storaged in the run parameter <strong style="color:#010101">{model}</strong>, and please put your model into <strong style="color:#010101">{output}</strong> then you can download it online.',
pathTips11: 'The code is storaged in <strong style="color:#010101">{code}</strong>, the dataset is storaged in <strong style="color:#010101">{dataset}</strong>, the pre-trained model is storaged in the <strong style="color:#010101">{model}</strong>, and please put your model into <strong style="color:#010101">{output}</strong> then you can download it online.',
@@ -796,6 +807,15 @@ const en = {
cloudbrainTaskType: 'Task Type',
repo: 'Repository',
cloudbrainTaskName: 'Cloudbrain Name',
taskIsAutomaticStop: 'Task is automatic stop',
automaticStop: 'Automatic stop',
manualStop: 'Manual stop',
automaticStopTips: 'This task will automatically stop when the running time exceeds the time you have selected, or when the point balance is insufficient.',
manualStopTips: 'This task will be manually stopped by you or automatically stopped when your point balance is insufficient.',
customize: 'Customize',
numOfHours: '{num} hours',
customizeTimeLimitPlaceholder: 'Please enter an integer between 1 and 24',
},
superComputeObj: {
mmlSparkDescr: `The full name of MMLSpark is Microsoft Machine Learning for Apache Spark, which enables users to run customized container images and grants them root accesses within the container. Users can directly use Microsoft's MMLSpark provided by the platform.\nNote: MMLSpark is a Spark version provided by Microsoft for machine learning environments( <a target="_blank" href="https://github.com/Azure/mmlspark">https://github.com/Azure/mmlspark</a> )Regarding mmlspark, please refer to the following paper: <a target="_blank" href="https://arxiv.org/pdf/1810.08744.pdf">https://arxiv.org/pdf/1810.08744.pdf</a>`,


+ 22
- 2
web_src/vuepages/langs/config/zh-CN.js View File

@@ -328,11 +328,12 @@ const zh = {
modelCreateFailed: '模型创建失败',
modelModifyFailed: '模型修改失败',
fileUpload: '文件上传',
folderUpload: '文件夹上传',
upload: '上传',
uploadPath: '上传路径',
uploadStatus: '上传状态',
modelFileUploadDefaultTips: '点击添加文件或直接拖拽文件到此处',
modelFileUploadErrTips: '单次最多上传10个文件,模型总文件大小不超过{size}G',
modelFileUploadErrTips: '单次最多上传 10 个文件,模型总文件大小不超过 {size}',
modelFileNameTips: '文件名长度不超过128个字符',
fileIstoBig: '文件太大',
removeFile: '移除文件',
@@ -343,6 +344,14 @@ const zh = {
fileHasAlreadyInTheModel: '该文件已上传在模型:',
fileEqualInTheModel: '该文件与其它文件内容相同:',
fileExistInTheModel: '已存在相同内容文件:',
uploadFile: '上传文件',
uploadFolder: '上传文件夹',
addUploadFolder: '添加上传文件夹',
clearAll: '清空所有',
fileCountAndSize: '已选择文件数: {count},总大小: {size}',
folderUploadPlaceholder: '点击添加文件夹或直接拖拽文件夹/文件到此处',
folderUploadTips: '本次上传的文件数量不超过 {maxCount} 个,总大小不超过 {maxSize},超多超大的文件请通过 <a target="blank" href="https://openi.pcl.ac.cn/docs/index.html#/api/list">SDK</a> 方式上传。',
folderUploadErrorTips: '本次上传的文件数量不超过 {maxCount} 个,总大小不超过 {maxSize}',
basicInfo: '基本信息',
modelSize: '模型大小',
descr: '描述',
@@ -476,6 +485,7 @@ const zh = {
model_sdk_use_way: '模型SDK使用方式',
codeUseDlgTriggerTxt: '在OpenI如何使用模型',
codeUseDlgTitle: '如何在OpenI协作平台使用模型',
codeDownDlgTitle: '如何在OpenI协作平台下载模型',
model_not_equal_file: "不能选择相同名称的模型",
},
datasetObj: {
@@ -627,7 +637,8 @@ const zh = {
createTask: '新建任务',
cluster: '算力集群',
computeResource: '计算资源',
sameTaskTips1: '您已经有 <span>同类任务</span> 正在等待或运行中,请等待任务结束再创建;',
sameTaskTips0: '您已经有 <span>同类任务</span> 正在等待或运行中,请等待任务结束再创建;',
sameTaskTips1: '您已经有 {count}个 <span>同类任务</span> 正在等待或运行中,请等待任务结束再创建;',
sameTaskTips2: '可以在 “<a href="/cloudbrains" target="_blank">个人中心 &gt; 云脑任务</a>” 查看您所有的云脑任务。',
pathTips1: '训练脚本存储在 <strong style="color:#010101">{code}</strong> 中,数据集存储在 <strong style="color:#010101">{dataset}</strong> 中,预训练模型存放在运行参数 <strong style="color:#010101">{model}</strong> 中,训练输出请存储在 <strong style="color:#010101">{output}</strong> 中以供后续下载。',
pathTips11: '训练脚本存储在 <strong style="color:#010101">{code}</strong> 中,数据集存储在 <strong style="color:#010101">{dataset}</strong> 中,预训练模型存储在 <strong style="color:#010101">{model}</strong> 中,训练输出请存储在 <strong style="color:#010101">{output}</strong> 中以供后续下载。',
@@ -811,6 +822,15 @@ const zh = {
cloudbrainTaskType: '云脑任务类型',
repo: '项目',
cloudbrainTaskName: '云脑侧任务',

taskIsAutomaticStop: '任务是否自动停止',
automaticStop: '自动停止',
manualStop: '手动停止',
automaticStopTips: '该任务将在运行时长超过您所选择的时长后,或积分余额不足时,自动停止。',
manualStopTips: '该任务将由您手动停止或积分余额不足时自动停止。',
customize: '自定义',
numOfHours: '{num} 小时',
customizeTimeLimitPlaceholder: '请输入1到24之间的整数',
},
superComputeObj: {
mmlSparkDescr: `MMLSpark全称为Microsoft Machine Learning for Apache Spark,支持用户运行自制容器镜像,且赋予了用户容器内root权限。用户可直接使用平台提供的微软的MMLSpark。\n注:MMLSpark是微软提供针对机器学习环境的Spark版本(<a target="_blank" href="https://github.com/Azure/mmlspark">https://github.com/Azure/mmlspark</a>),关于mmlspark可参考论文:<a target="_blank" href="https://arxiv.org/pdf/1810.08744.pdf">https://arxiv.org/pdf/1810.08744.pdf</a>`,


+ 16
- 8
web_src/vuepages/pages/cloudbrain/configs.js View File

@@ -57,6 +57,7 @@ export const CreatePageConfigs = {
dataset: { required: false, type: 0, useExceedSize: true },
networkType: { required: true },
spec: { required: true },
runTimeLimit: { required: true },
/* just test */
// imagev2: { required: true },
// aiEngine: { required: true },
@@ -78,6 +79,7 @@ export const CreatePageConfigs = {
dataset: { required: false, type: 1, useExceedSize: true },
networkType: { required: true },
spec: { required: true },
runTimeLimit: { required: true },
},
}],
}],
@@ -103,6 +105,7 @@ export const CreatePageConfigs = {
dataset: { required: false, type: 0, useExceedSize: true },
networkType: { required: true },
spec: {},
runTimeLimit: { required: true },
},
}],
'NPU': [{
@@ -118,6 +121,7 @@ export const CreatePageConfigs = {
dataset: { required: false, type: 1, useExceedSize: true },
networkType: { required: true },
spec: { required: true },
runTimeLimit: { required: true },
},
}],
'GCU': [{
@@ -138,6 +142,7 @@ export const CreatePageConfigs = {
dataset: { required: false, useExceedSize: true },
networkType: { required: true },
spec: { required: true },
runTimeLimit: { required: true },
},
}],
'MLU': [{
@@ -158,6 +163,7 @@ export const CreatePageConfigs = {
dataset: { required: false, useExceedSize: true },
networkType: { required: true },
spec: { required: true },
runTimeLimit: { required: true },
},
}],
'DCU': [{
@@ -198,6 +204,7 @@ export const CreatePageConfigs = {
dataset: { required: false, useExceedSize: true },
networkType: { required: true },
spec: { required: true },
runTimeLimit: { required: true },
},
}],
'METAX-GPGPU': [{
@@ -218,6 +225,7 @@ export const CreatePageConfigs = {
dataset: { required: false, useExceedSize: true },
networkType: { required: true },
spec: { required: true },
runTimeLimit: { required: true },
},
}],
}]
@@ -719,7 +727,7 @@ export const DetailPageConfigs = {
'computerRes', 'datasetPath',
'createTime', 'modelPath',
'startTime', 'outputPath',
'endTime', '',
'endTime', 'timeLimit',
'duration', '',
'descr', '',
'failedReason',
@@ -741,7 +749,7 @@ export const DetailPageConfigs = {
'status', 'spec',
'creator', 'hasInternet',
'branch', 'computerRes',
'createTime', '',
'createTime', 'timeLimit',
'startTime', '',
'endTime', '',
'duration', '',
@@ -770,7 +778,7 @@ export const DetailPageConfigs = {
'computerRes', 'codePath',
'createTime', 'datasetPath',
'startTime', 'modelPath',
'endTime', '',
'endTime', 'timeLimit',
'duration', '',
'descr', '',
'failedReason',
@@ -792,7 +800,7 @@ export const DetailPageConfigs = {
'status', 'spec',
'creator', 'aiCenter',
'branch', 'hasInternet',
'computerRes', '',
'computerRes', 'timeLimit',
'createTime', '',
'startTime', '',
'endTime', '',
@@ -819,7 +827,7 @@ export const DetailPageConfigs = {
'status', 'spec',
'creator', 'aiCenter',
'branch', 'hasInternet',
'computerRes', '',
'computerRes', 'timeLimit',
'createTime', '',
'startTime', '',
'endTime', '',
@@ -844,7 +852,7 @@ export const DetailPageConfigs = {
'status', 'spec',
'creator', 'aiCenter',
'branch', 'hasInternet',
'computerRes', '',
'computerRes', 'timeLimit',
'createTime', '',
'startTime', '',
'endTime', '',
@@ -894,7 +902,7 @@ export const DetailPageConfigs = {
'status', 'spec',
'creator', 'aiCenter',
'branch', 'hasInternet',
'computerRes', '',
'computerRes', 'timeLimit',
'createTime', '',
'startTime', '',
'endTime', '',
@@ -919,7 +927,7 @@ export const DetailPageConfigs = {
'status', 'spec',
'creator', 'aiCenter',
'branch', 'hasInternet',
'computerRes', '',
'computerRes', 'timeLimit',
'createTime', '',
'startTime', '',
'endTime', '',


+ 18
- 5
web_src/vuepages/pages/cloudbrain/create/index.vue View File

@@ -17,7 +17,9 @@
<div class="msg-content">
<i class="ri-information-line"></i>
<div class="msg-content-tip">
<div class="line-1" v-html="$t('cloudbrainObj.sameTaskTips1')"></div>
<div class="line-1" v-if="notStopTaskCount <= 1" v-html="$t('cloudbrainObj.sameTaskTips0')"></div>
<div class="line-1" v-else v-html="$t('cloudbrainObj.sameTaskTips1', { count: notStopTaskCount })">
</div>
<div class="line-2" v-html="$t('cloudbrainObj.sameTaskTips2')"></div>
</div>
</div>
@@ -73,8 +75,9 @@
<BootFile ref="bootFileRef" v-if="formCfg.bootFile" v-model="state.bootFile"
:required="formCfg.bootFile.required" :sampleUrl="formCfg.bootFile.sampleUrl"></BootFile>
<RunParameters ref="runParametersRef" v-if="formCfg.runParameters" v-model="state.runParameters"
:required="formCfg.runParameters.required">
</RunParameters>
:required="formCfg.runParameters.required"></RunParameters>
<RunTimeLimit ref="runTimeLimitRef" v-if="formCfg.runTimeLimit && subscriberUser" v-model="state.time_limit"
:required="formCfg.runTimeLimit.required"></RunTimeLimit>
<div class="form-row" v-if="this.isModifyTask && (pageCfg.modify && pageCfg.modify.showIsContinue)">
<div class="left-area">
<div class="title"></div>
@@ -140,6 +143,7 @@ import RunParameters from '~/components/cloudbrain/RunParameters.vue';
import NetworkType from '~/components/cloudbrain/NetworkType.vue';
import SpecSelect from '~/components/cloudbrain/SpecSelect.vue';
import WorkServerNum from '~/components/cloudbrain/WorkServerNum.vue';
import RunTimeLimit from '~/components/cloudbrain/RunTimeLimit.vue';
import AlgBechmarkType from '~/components/cloudbrain/AlgBechmarkType.vue';
import SDKCode from '~/components/cloudbrain/SDKCode.vue';
import DialogTips from '~/components/cloudbrain/DialogTips.vue';
@@ -172,6 +176,7 @@ export default {
networkType: 'no_internet',
spec: '',
workServerNum: 1,
time_limit: '4',
algBechmarkType: ['1', ''],
isContinue: false,
},
@@ -193,6 +198,7 @@ export default {
errorMsgBoxShow: false,
errorMsg: '',
alreadyMsgBoxShow: false,
notStopTaskCount: 0,
maskLoading: false,
maskLoadingContent: '',
datasetSize: 0,
@@ -205,11 +211,12 @@ export default {
noSpecFlag: false,
visible: false,
showFormRight: true,
subscriberUser: false,
};
},
components: {
FormTop, TaskName, TaskDescr, BranchName, BootFile, AIEngineSelect, ImageSelectV1, ImageSelectV2,
ModelSelect, DatasetSelect, RunParameters, NetworkType, SpecSelect, WorkServerNum,
ModelSelect, DatasetSelect, RunParameters, NetworkType, SpecSelect, WorkServerNum, RunTimeLimit,
AlgBechmarkType, SDKCode,
LoadingMask, DialogTips
},
@@ -288,6 +295,9 @@ export default {
case 'workServerNum':
subObj['work_server_number'] = this.state.workServerNum;
break;
case 'runTimeLimit':
subObj['time_limit'] = Number(this.state.time_limit);
break;
default:
break;
}
@@ -535,9 +545,12 @@ export default {
res = res.data;
if (res.code == 0) {
const data = res.data;
this.subscriberUser = data.is_subscriber;
if (!this.subscriberUser) delete this.formCfg['runTimeLimit'];
this.branchList = data.branches || [];
this.imageList = data.images || [];
this.alreadyMsgBoxShow = data.not_stop_task_count > 0;
this.alreadyMsgBoxShow = !data.can_create_more;
this.notStopTaskCount = data.not_stop_task_count || 0;
this.specConfigs.showPoint = data.pay_switch;
this.specConfigs.blance = data.point_account ? data.point_account.balance : 0;
this.specConfigs.specs = data.specs || {


+ 3
- 3
web_src/vuepages/pages/cloudbrain/detail/index.vue View File

@@ -37,7 +37,7 @@
</span>
<span class="task-refresh">
<el-tooltip placement="top" :content="$t('cloudbrainObj.refresh')">
<i class="el-icon-refresh-right" @click.stop.prevent="refresh(item)"></i>
<i class="el-icon-refresh-right" @click.stop.prevent="refresh(item, true)"></i>
</el-tooltip>
</span>
</div>
@@ -142,7 +142,7 @@ export default {
this.refresh(item);
});
},
refresh(item) {
refresh(item, useRefreshBtn) {
const activeTab = item.activeName;
const tabContent = this.$refs[activeTab + '-Ref'];
if (activeTab.indexOf('configInfo-') > -1) {
@@ -168,7 +168,7 @@ export default {
console.log(err);
});
} else {
tabContent && tabContent[0] && tabContent[0].refresh && tabContent[0].refresh();
tabContent && tabContent[0] && tabContent[0].refresh && tabContent[0].refresh(useRefreshBtn);
}
}
},


+ 1
- 1
web_src/vuepages/pages/cloudbrain/list/index.vue View File

@@ -11,7 +11,7 @@
<div class="right-c">
<div class="use-code-c" v-if="tableData.length > 0">
<CommonTipsDialog type="cloudbrain" :promotePath="codeUsePromotePath"
:title="$t('cloudbrainObj.codeUseDlgTitle')">
:title="$t('cloudbrainObj.codeUseDlgTitle')" :closeText="$t('cloudbrainObj.dialogTips.tips8')">
<a class="use-dlg-btn">{{ $t('cloudbrainObj.codeUseDlgTriggerTxt') }}</a>
</CommonTipsDialog>
</div>


+ 1
- 1
web_src/vuepages/pages/dataset/codedialog/index.vue View File

@@ -1,6 +1,6 @@
<template>
<CommonTipsDialog type="dataset" :promotePath="promotePath" :appendToBody="true"
:title="$t('datasetObj.codeUseDlgTitle')">
:title="$t('datasetObj.codeUseDlgTitle')" :closeText="$t('cloudbrainObj.dialogTips.tips8')">
<a class="use-dlg-btn">{{ $t('datasetObj.codeUseDlgTriggerTxt') }}</a>
</CommonTipsDialog>
</template>


+ 250
- 0
web_src/vuepages/pages/modelmanage/components/FolderUploadSelect.vue View File

@@ -0,0 +1,250 @@
<template>
<div class="folder-upload-select-c">
<div class="folder-upload-select">
<div class="op-c">
<el-button size="mini" icon="el-icon-circle-plus" :disabled="uploading" @click="addFolder">{{
$t('modelManage.addUploadFolder') }}</el-button>
<div class="op-right">
<div class="summary">{{ $t('modelManage.fileCountAndSize', { count: fileList.length, size: allFilesSize }) }}
</div>
<el-button size="mini" type="text" style="color:red" :disabled="uploading" @click="remove()">{{
$t('modelManage.clearAll') }}</el-button>
</div>
</div>
<div class="err-tips-c">
<div class="err-tips ui red message" v-show="errorState"> {{ errorInfo }}</div>
</div>
<div class="file-list-c" @dragover="handleDragover" @drop="handleDrop">
<div class="file-item" v-for="(file, index) in fileList" :key="index" @click.stop.prevent="">
<div class="file-name">{{ file.fullname }}</div>
<div class="file-size">{{ file.file_size }}</div>
<div class="file-status">
<el-tooltip v-if="file.status == 'error'" effect="dark" :content="file.errTips" placement="top">
<i class="el-icon-warning-outline" style="color:red"></i>
</el-tooltip>
<i class="el-icon-circle-check" v-if="file.status == 'success'" style="color:#21ba45"></i>
</div>
<div class="file-op"><i class="el-icon-circle-close" :disabled="uploading"
@click.stop.prevent="remove(file)"></i></div>
</div>
<div class="empty-place-holder" v-if="!fileList.length" @click="addFolder"
v-html="$t('modelManage.folderUploadPlaceholder')">
</div>
</div>
<input type="file" ref="filepicker" @change="folderSelectChange" webkitdirectory directory multiple
style="display:none" />
</div>
<div class="tips"
v-html="$t('modelManage.folderUploadTips', { maxCount: maxFilesCount, maxSize: maxFilesSizeShow })"></div>
</div>
</template>

<script>
import { transFileSize, uuidv4 } from '~/utils';
export default {
name: "FolderUploadSelect",
props: {
uploading: { type: Boolean, default: false },
maxFilesCount: { type: Number, default: 100 },
maxFilesSize: { type: Number, default: 200 * 1024 * 1024 * 1024 },
},
data() {
return {
fileList: [],
errorState: false,
errorInfo: '',
};
},
computed: {
allFilesSize() {
const allFilesSize = this.fileList.reduce((acc, item, index) => acc + item.size, 0);
return transFileSize(allFilesSize);
},
maxFilesSizeShow() {
return transFileSize(this.maxFilesSize);
}
},
methods: {
addFolder() {
this.$refs['filepicker'].value = '';
this.$refs['filepicker'].click();
},
folderSelectChange(evt) {
const files = evt.target.files || [];
for (let i = 0, iLen = files.length; i < iLen; i++) {
const file = files[i];
file._webkitRelativePath = file.webkitRelativePath;
}
this.handleFiles(files);
},
handleFiles(files) {
for (let i = 0, iLen = files.length; i < iLen; i++) {
const file = files[i];
if (this.fileList.findIndex(item => item._webkitRelativePath == file._webkitRelativePath) >= 0) continue;
file.fullname = file._webkitRelativePath ? file._webkitRelativePath : file.name;
file.file_size = transFileSize(file.size);
file.upload = { uuid: uuidv4() };
this.fileList.push(file);
}
this.checkFiles();
},
handleDragover(evt) {
evt.preventDefault();
},
handleDrop(evt) {
evt.preventDefault();
const filesList = [];
const dataTransferItemList = evt.dataTransfer.items;
for (const dataTransferItem of dataTransferItemList) {
const fileEntry = dataTransferItem.webkitGetAsEntry();
this.handleDropedFileEntry(fileEntry, filesList);
}
},
handleDropedFileEntry(fileEntry) {
const self = this;
if (fileEntry.isFile) {
fileEntry.file(function (file) {
file._webkitRelativePath = fileEntry.fullPath.slice(1);
self.handleFiles([file]);
return;
})
} else {
const dirReader = fileEntry.createReader()
dirReader.readEntries(function (entries) {
for (let i = 0; i < entries.length; i++) {
self.handleDropedFileEntry(entries[i]);
}
})
}
},
remove(file) {
if (file) {
const index = this.fileList.findIndex(item => item.webkitRelativePath === file.webkitRelativePath);
this.fileList.splice(index, 1);
} else {
this.fileList.splice(0);
}
this.checkFiles();
},
showErrInfo(state, info) {
this.errorState = state;
this.errorInfo = info;
},
checkFiles() {
const allFilesSize = this.fileList.reduce((acc, item, index) => acc + item.size, 0);
if (this.fileList.length > this.maxFilesCount || allFilesSize > this.maxFilesSize) {
this.showErrInfo(true, this.$t('modelManage.folderUploadErrorTips', {
maxCount: this.maxFilesCount,
maxSize: this.maxFilesSizeShow
}));
return false;
}
this.showErrInfo(false, '');
return true;
},
getAcceptedFiles() {
return this.fileList;
},
},
beforeMount() { },
};
</script>

<style scoped lang="less">
.folder-upload-select-c {
position: relative;

.folder-upload-select {
border: 2px dashed #0087f5;
border-radius: 4px;
padding: 4px;

.op-c {
margin: 4px;
display: flex;
align-items: center;
justify-content: space-between;

.op-right {
display: flex;
align-items: center;

.summary {
margin-right: 12px;
font-size: 12px;
color: #606266;
}
}
}

.err-tips-c {
margin: 8px 4px;
}

.file-list-c {
min-height: 130px;
max-height: 300px;
overflow: auto;
border: 1px solid burlywood;
margin: 4px;

.file-item {
color: #606266;
font-size: 14px;
display: flex;
align-items: center;
margin: 0 4px;
padding: 4px 0;
border-bottom: 1px dashed burlywood;

.file-name {
flex: 1;
width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-left: 5px;
padding-right: 10px;
}

.file-size {
width: 100px;
margin: 0 10px;
}

.file-status {
width: 30px;
display: flex;
align-items: center;
justify-content: center;
}

.file-op {
width: 30px;
display: flex;
align-items: center;
justify-content: center;

i {
cursor: pointer;
color: #606266;
}
}
}

.empty-place-holder {
color: rgba(0, 0, 0, .6);
padding: 54px 0 54px 0;
text-align: center;
cursor: pointer;
}
}
}

.tips {
margin-top: 10px;
font-size: 13px;
color: rgba(0, 0, 0, 0.6);
}
}
</style>

+ 3
- 1
web_src/vuepages/pages/modelmanage/components/ModelHeader.vue View File

@@ -14,7 +14,9 @@
<img src="/img/jian.svg" v-if="model.recommend == 1">
</div>
<div class="title-r">
<CommonTipsDialog type="model" :promotePath="promotePath" :title="$t('modelObj.codeUseDlgTitle')" :data="model">
<CommonTipsDialog type="model" :promotePath="promotePath"
:title="$t('modelObj.codeUseDlgTitle')" :data="model"
:closeText="$t('cloudbrainObj.dialogTips.tips8')">
<a class="use-dlg-btn">{{ $t('modelObj.codeUseDlgTriggerTxt') }}</a>
</CommonTipsDialog>
<div style="background: transparent;" v-if="!isCanDebug && !model.isPrivate">


+ 21
- 4
web_src/vuepages/pages/modelmanage/files/index.vue View File

@@ -32,8 +32,7 @@
</div>
</div>
<div class="right-btn-c">
<a class="download-btn" v-if="modelData.isCanDownload" download
:href="`${repoUrl}/modelmanage/downloadall?id=${modelData.id}`">
<a v-if="modelData.isCanDownload" @click="downloadAllModel(modelData.size,modelData.id)" class="download-btn">
{{ $t('modelManage.modelDownloadAll') }}
</a>
<el-button v-if="modelType == 1 && canOperate" type="primary" icon="el-icon-upload" @click="goUploadPage">
@@ -85,17 +84,19 @@
</div>
</div>
</div>
<CommonTipsDialog ref="childModelTips" type="modelFile" :promotePath="codeUsePromotePath" :title="$t('modelObj.codeDownDlgTitle')" :closeText="$t('cloudbrainObj.dialogTips.tips8')"></CommonTipsDialog>
</div>
</template>

<script>
import ModelHeader from '../components/ModelHeader.vue';
import NotFound from '~/components/NotFound.vue';
import CommonTipsDialog from '~/components/CommonTipsDialog.vue';
import { getModelInfoByName, getModelFiles, deleteModelFile } from '~/apis/modules/modelmanage';
import { getUrlSearchParams, getListValueWithKey, transFileSize, renderSpecStr } from '~/utils';
import { MODEL_ENGINES } from '~/const';
import { formatDate } from 'element-ui/lib/utils/date-util';
import { lang } from '~/langs';
export default {
data() {
return {
@@ -129,9 +130,11 @@ export default {
modelList: [],
filesList: [],
filePath: [],

codeUsePromotePath: `tips/modelDown/sdkcode${lang == 'zh-CN' ? '' : '_en'}.md`,
};
},
components: { ModelHeader, NotFound },
components: { ModelHeader, NotFound,CommonTipsDialog },
methods: {
getDirFiles(dir, usePath, tempFilePath) {
dir = dir.length ? dir.slice(1) : '';
@@ -289,6 +292,20 @@ export default {
console.log(err);
});
},
downloadAllModel(size,id) {
if (size / (1024 * 1024) <= 500) {
let downloadElement = document.createElement('a')
let href = `${this.repoUrl}/modelmanage/downloadall?id=${id}`
downloadElement.href = href
document.body.appendChild(downloadElement)
downloadElement.click() //点击下载
document.body.removeChild(downloadElement) //下载完成移除元素
} else {
this.$nextTick(() => {
this.$refs.childModelTips.handlerOpen()
})
}
}
},
beforeMount() {
const urlParams = getUrlSearchParams();


+ 87
- 41
web_src/vuepages/pages/modelmanage/fileupload/index.vue View File

@@ -39,24 +39,41 @@
</el-input>
</div>
</div>
<div class="row">
<div class="r-title"></div>
<div class="r-content">
<el-radio-group class="upload-type-sel" v-model="uploadType" :disabled="uploading"
@change="changeUploadType">
<el-radio :label="'file'">{{ $t('modelManage.uploadFile') }}</el-radio>
<el-radio :label="'folder'">{{ $t('modelManage.uploadFolder') }}</el-radio>
</el-radio-group>
</div>
</div>
<div class="row" style="align-items:flex-start;">
<div class="r-title"><label class="required">{{ $t('modelManage.fileUpload') }}</label></div>
<div class="r-title">
<label class="required" v-if="uploadType == 'file'">{{ $t('modelManage.fileUpload') }}</label>
<label class="required" v-if="uploadType == 'folder'">{{ $t('modelManage.folderUpload') }}</label>
</div>
<div class="r-content">
<div style="position:relative">
<div style="position:relative" v-show="uploadType == 'file'">
<form class="dropzone" ref="dropzoneRef">
<div class="dropzon-err-tips ui red message" v-show="showUploadErr"
style="display:none;margin:2.5rem">
{{ uploadErrTxt }}</div>
</form>
<div class="tips">{{ getDefaultErrTxt() }}</div>
<div class="not-allowed-placeholder" v-show="uploading"></div>
</div>
<FolderUploadSelect ref="folderUploadSelect" v-if="uploadType == 'folder'"
:maxFilesSize="maxModelFilesSize" :uploading="uploading">
</FolderUploadSelect>
</div>
</div>
<div class="row" style="margin-top:10px">
<div class="r-title"><label></label></div>
<div class="r-content">
<el-button size="medium" class="green" @click="submit" :disabled="uploading">{{ $t('modelManage.upload')
}}
}}
</el-button>
<el-button size="medium" @click="cancel">{{ $t('modelManage.cancel') }}</el-button>
</div>
@@ -65,7 +82,7 @@
<div class="r-title"><label>{{ $t('modelManage.uploadStatus') }}:</label></div>
<div class="r-content">
<div v-for="(item, index) in uploadFiles" :key="item.upload.uuid" class="datast-upload-progress">
<span class="dataset-name nowrap" :title="item.name">{{ item.name }}</span>
<span class="dataset-name nowrap" :title="item.fullname">{{ item.fullname }}</span>
<div class="dataset-progress">
<el-progress :text-inside="true" :stroke-width="14" :percentage="uploadStatusList[index].progress">
</el-progress>
@@ -95,19 +112,20 @@

<script>
import ModelHeader from '../components/ModelHeader.vue';
import FolderUploadSelect from '../components/FolderUploadSelect.vue';
import NotFound from '~/components/NotFound.vue';
import 'dropzone/dist/dropzone.css';
import Dropzone from 'dropzone';
import SparkMD5 from "spark-md5";
import { getModelInfoByName, getChunks, getNewMultipart, getMultipartUrl, setCompleteMultipart } from '~/apis/modules/modelmanage';
import { getUrlSearchParams } from '~/utils';
import { getUrlSearchParams, transFileSize } from '~/utils';

Dropzone.autoDiscover = false;

const uploadChunkSize = 1024 * 1024 * 64;
const md5ChunkSize = 1024 * 1024 * 64;
const maxFileSize = 10;
const maxModelFilesSize = window.MAX_MODEL_SIZE || 250 * 1024 * 1024 * 1024; // 200 GB
const maxModelFilesSize = window.MAX_MODEL_SIZE || 200 * 1024 * 1024 * 1024; // 200 GB

export default {
data() {
@@ -119,6 +137,7 @@ export default {
repoName: location.pathname.split('/')[2],
modelData: {},
uploadDir: '',
uploadType: 'file', // file|folder
dropzoneHandler: null,
type: '1', // 1-修改,其它-新增
state: {
@@ -139,11 +158,11 @@ export default {
uploadLength: 0,
uploadSuccessLength: 0,
uploadStatusList: [],
maxModelFilesSize: maxModelFilesSize,
uploading: false,
};
},
components: { ModelHeader, NotFound },
components: { ModelHeader, FolderUploadSelect, NotFound },
methods: {
initModelData() {
const urlParams = getUrlSearchParams();
@@ -203,6 +222,7 @@ export default {
dictMaxFilesExceeded: this.getDefaultErrTxt(),
});
this.dropzoneHandler.on("addedfile", file => {
file.fullname = file.name;
this.checkFiles(file);
});
this.dropzoneHandler.on("removedfile", file => {
@@ -210,7 +230,7 @@ export default {
});
},
getDefaultErrTxt() {
return this.$t('modelManage.modelFileUploadErrTips', { size: (maxModelFilesSize / (1024 * 1024 * 1024)).toFixed(2) });
return this.$t('modelManage.modelFileUploadErrTips', { size: transFileSize(maxModelFilesSize) });
},
showUploadErrInfo(state, info) {
if (state) {
@@ -221,8 +241,13 @@ export default {
this.showUploadErr = false;
}
},
changeUploadType() {
this.dropzoneHandler.removeAllFiles();
this.resetFileStatus();
},
checkFiles(file, countStay) {
const fileList = this.dropzoneHandler.getAcceptedFiles();
fileList.forEach(item => item.fullname = item.name);
const filesSize = fileList.reduce((acc, item, index) => acc + item.size, 0) + (file && !countStay ? file.size : 0);
const filesCount = fileList.length + (file && !countStay ? 1 : 0);
const allfilesSize = filesSize + this.state.size;
@@ -235,7 +260,7 @@ export default {
this.showUploadErrInfo(true, this.getDefaultErrTxt());
return false;
}
if (file && file.name.length > 128) {
if (file && file.fullname.length > 128) {
this.showUploadErrInfo(true, this.$t('modelManage.modelFileNameTips'));
this.uploadError(file, this.$t('modelManage.modelFileNameTips'));
return false;
@@ -272,7 +297,7 @@ export default {
let currentChunk = 0;
this.uploadStatusList.push({
uploadUuid: file.upload.uuid,
name: file.name,
name: file.fullname,
status: this.$t('modelManage.calcFileMd5'),
progress: 0,
infoCode: 3,
@@ -291,7 +316,7 @@ export default {
}
};
fileReader.onerror = function (e) {
console.warn(file.name + ': calcFileMd5 went wrong.');
console.warn(file.fullname + ': calcFileMd5 went wrong.');
reject(e);
};

@@ -308,7 +333,7 @@ export default {
md5: file.uniqueIdentifier,
type: this.state.type,
modeluuid: this.state.id,
file_name: this.uploadDir + file.name,
file_name: this.uploadDir + file.fullname,
scene: 'model',
}).then(res => {
const data = res.data;
@@ -333,7 +358,7 @@ export default {
size: file.size,
fileType: file.type,
type: this.state.type,
file_name: this.uploadDir + file.name,
file_name: this.uploadDir + file.fullname,
scene: 'model',
modeluuid: file.modelUuid,
}).then(res => {
@@ -385,7 +410,7 @@ export default {
size: partSize,
chunkNumber: currentChunk + 1,
type: _this.state.type,
file_name: _this.uploadDir + file.name,
file_name: _this.uploadDir + file.fullname,
scene: 'model',
});
urls[currentChunk] = res.data.url;
@@ -433,7 +458,7 @@ export default {
return await setCompleteMultipart({
uuid: file.uuid,
uploadID: file.uploadID,
file_name: _this.uploadDir + file.name,
file_name: _this.uploadDir + file.fullname,
size: file.size,
type: _this.state.type,
scene: 'model',
@@ -459,7 +484,7 @@ export default {
} else {
try {
await completeUpload();
console.log(`文件上传完成:${file.name} \n分片:${chunks} 大小:${file.size} 用时:${(new Date().getTime() - time) / 1000} s`);
console.log(`文件上传完成:${file.fullname} \n分片:${chunks} 大小:${file.size} 用时:${(new Date().getTime() - time) / 1000} s`);
this.uploadLength++;
this.uploadSuccessLength++;
this.updateFileStatus(file, this.$t('modelManage.uploadSuccess'), 100, 0);
@@ -485,25 +510,33 @@ export default {
loadNext();
},
uploadError(file, info) {
file.previewTemplate.querySelector('.dz-success-mark').style.opacity = 0;
file.previewTemplate.querySelector('.dz-error-mark').style.opacity = 1;
file.previewTemplate.querySelector('.dz-error-message span').innerHTML = info;
file.previewTemplate.querySelector(".dz-error-message").style.display = 'block';
file.previewTemplate.querySelector(".dz-details").onmouseover = () => {
file.previewTemplate.querySelector('.dz-error-message').style.opacity = 1;
};
file.previewTemplate.querySelector(".dz-details").onmouseout = () => {
file.previewTemplate.querySelector('.dz-error-message').style.opacity = 0;
};
file.status = 'error';
file.errTips = info;
if (file.previewTemplate) {
file.previewTemplate.querySelector('.dz-success-mark').style.opacity = 0;
file.previewTemplate.querySelector('.dz-error-mark').style.opacity = 1;
file.previewTemplate.querySelector('.dz-error-message span').innerHTML = info;
file.previewTemplate.querySelector(".dz-error-message").style.display = 'block';
file.previewTemplate.querySelector(".dz-details").onmouseover = () => {
file.previewTemplate.querySelector('.dz-error-message').style.opacity = 1;
};
file.previewTemplate.querySelector(".dz-details").onmouseout = () => {
file.previewTemplate.querySelector('.dz-error-message').style.opacity = 0;
};
}
this.uploadFinishCheck(file);
},
uploadSuccess(file) {
file.previewTemplate.querySelector('.dz-error-mark').style.opacity = 0;
file.previewTemplate.querySelector('.dz-error-message span').innerHTML = '';
file.previewTemplate.querySelector('.dz-success-mark').style.opacity = 1;
file.previewTemplate.querySelector(".dz-error-message").style.display = 'none';
file.previewTemplate.querySelector(".dz-details").onmouseover = null;
file.previewTemplate.querySelector(".dz-details").onmouseout = null;
file.status = 'success';
file.errTips = '';
if (file.previewTemplate) {
file.previewTemplate.querySelector('.dz-error-mark').style.opacity = 0;
file.previewTemplate.querySelector('.dz-error-message span').innerHTML = '';
file.previewTemplate.querySelector('.dz-success-mark').style.opacity = 1;
file.previewTemplate.querySelector(".dz-error-message").style.display = 'none';
file.previewTemplate.querySelector(".dz-details").onmouseover = null;
file.previewTemplate.querySelector(".dz-details").onmouseout = null;
}
this.uploadFinishCheck(file);
},
uploadFinishCheck(file) {
@@ -523,10 +556,17 @@ export default {
}
},
submit() {
const fileList = this.dropzoneHandler.getAcceptedFiles();
if (!fileList.length) return;
for (let i = 0, iLen = fileList.length; i < iLen; i++) {
if (!this.checkFiles(fileList[i], true)) return;
let fileList = [];
if (this.uploadType == 'file') {
fileList = this.dropzoneHandler.getAcceptedFiles();
if (!fileList.length) return;
for (let i = 0, iLen = fileList.length; i < iLen; i++) {
if (!this.checkFiles(fileList[i], true)) return;
}
} else if (this.uploadType == 'folder') {
fileList = this.$refs['folderUploadSelect'].getAcceptedFiles();
if (!fileList.length) return;
if (!this.$refs['folderUploadSelect'].checkFiles()) return;
}
this.resetFileStatus();
this.uploadFiles = fileList;
@@ -544,7 +584,7 @@ export default {
this.uploadError(file, this.$t('modelManage.uploadFailed'));
this.updateFileStatus(file, this.$t('modelManage.uploadFailed'), 0, 1, info);
} else {
md5map[file.uniqueIdentifier] = file.name;
md5map[file.uniqueIdentifier] = file.fullname;
this.getChunksInfo(file).then(res => { // 获取Chunk信息
if (file.uploadID == '' || file.uuid == '') { // 未上传过
this.newUpload(file);
@@ -564,7 +604,7 @@ export default {
this.updateFileStatus(file, this.$t('modelManage.uploadSuccess'), 100, 0);
this.uploadSuccess(file);
}
console.log(file.name, '文件处理完成');
console.log(file.fullname, '文件处理完成');
} else { // 断点续传
this.breakpointUpload(file);
}
@@ -752,6 +792,12 @@ export default {
top: 0;
}

.tips {
margin-top: 10px;
font-size: 13px;
color: rgba(0, 0, 0, 0.6);
}

.dropzone {
min-height: 186px !important;

@@ -794,7 +840,7 @@ export default {

.datast-upload-progress .dataset-name {
text-align: right;
width: 200px;
width: 240px;
margin-right: 1rem;
}

@@ -820,7 +866,7 @@ export default {
color: #85b7d9;
}

/deep/ .el-button {
/deep/ .el-button.el-button--medium {
background-color: #e0e1e2;
color: rgba(0, 0, 0, .6);
border-color: transparent;


+ 6
- 2
web_src/vuepages/pages/supercompute/create/index.vue View File

@@ -20,7 +20,9 @@
<div class="msg-content">
<i class="ri-information-line"></i>
<div class="msg-content-tip">
<div class="line-1" v-html="$t('cloudbrainObj.sameTaskTips1')"></div>
<div class="line-1" v-if="notStopTaskCount <= 1" v-html="$t('cloudbrainObj.sameTaskTips0')"></div>
<div class="line-1" v-else v-html="$t('cloudbrainObj.sameTaskTips1', { count: notStopTaskCount })">
</div>
<div class="line-2" v-html="$t('cloudbrainObj.sameTaskTips2')"></div>
</div>
</div>
@@ -124,6 +126,7 @@ export default {
errorMsgBoxShow: false,
errorMsg: '',
alreadyMsgBoxShow: false,
notStopTaskCount: 0,
maskLoading: false,
maskLoadingContent: '',
datasetSize: 0,
@@ -251,7 +254,8 @@ export default {
const data = res.data;
this.branchList = data.branches || [];
this.imageList = data.images || [];
this.alreadyMsgBoxShow = data.not_stop_task_count > 0;
this.alreadyMsgBoxShow = !data.can_create_more;
this.notStopTaskCount = data.not_stop_task_count || 0;
this.specConfigs.showPoint = data.pay_switch;
this.specConfigs.blance = data.point_account ? data.point_account.balance : 0;
this.specConfigs.specs = data.specs || {


+ 7
- 1
web_src/vuepages/pages/supercompute/detail/index.vue View File

@@ -20,8 +20,14 @@
<div class="title-l">
<span class="task-create-time">{{ item.task.createTimeStr }}</span>
<span class="task-status">
<span>{{ $t('status') }}:</span><i :class="item.task.status"></i><span>{{ item.task.status
<span>{{ $t('status') }}:</span><i :class="item.task.status"></i><span style="margin-right:4px">{{ item.task.status
}}</span>
<i v-if="item.task.detailed_status === 'dataMigrating' && item.task.status === 'WAITING'"
:class="item.task.detailed_status" :title="$t('cloudbrainObj.migratingData')"></i>
<i v-if="item.task.detailed_status === 'centerPending' && item.task.status === 'WAITING'"
:class="item.task.detailed_status" :title="$t('cloudbrainObj.centerPending')"></i>
<i v-if="item.task.detailed_status === 'ImagePulling' && item.task.status === 'WAITING'"
:class="item.task.detailed_status" :title="$t('cloudbrainObj.imagePulling')"></i>
</span>
<span class="task-duration">
<span>{{ $t('cloudbrainObj.runDuration') }}:</span><span>{{ item.task.formatted_duration }}</span>


+ 7
- 1
web_src/vuepages/pages/supercompute/list/index.vue View File

@@ -26,7 +26,13 @@
<template slot-scope="scope">
<div class="status-wrap">
<i :class="scope.row.task.status"></i>
<span>{{ scope.row.task.status }}</span>
<span style="margin-right:4px">{{ scope.row.task.status }}</span>
<i v-if="scope.row.task.detailed_status === 'dataMigrating' && scope.row.task.status === 'WAITING'"
:class="scope.row.task.detailed_status" :title="$t('cloudbrainObj.migratingData')"></i>
<i v-if="scope.row.task.detailed_status === 'centerPending' && scope.row.task.status === 'WAITING'"
:class="scope.row.task.detailed_status" :title="$t('cloudbrainObj.centerPending')"></i>
<i v-if="scope.row.task.detailed_status === 'ImagePulling' && scope.row.task.status === 'WAITING'"
:class="scope.row.task.detailed_status" :title="$t('cloudbrainObj.imagePulling')"></i>
</div>
</template>
</el-table-column>


+ 8
- 0
web_src/vuepages/utils/index.js View File

@@ -20,6 +20,14 @@ export const getUrlSearchParams = () => {
return obj;
};

export const uuidv4 = () => {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = Math.random() * 16 | 0,
v = c === 'x' ? r : r & 0x3 | 0x8;
return v.toString(16);
});
};

export const transFileSize = (srcSize) => {
if (null == srcSize || srcSize == '') {
return '0 Bytes';


Loading…
Cancel
Save