diff --git a/custom/public/img/ranking_list.jpg b/custom/public/img/ranking_list.jpg new file mode 100644 index 000000000..94610215b Binary files /dev/null and b/custom/public/img/ranking_list.jpg differ diff --git a/integrations/pull_update_test.go b/integrations/pull_update_test.go index 484390001..251051582 100644 --- a/integrations/pull_update_test.go +++ b/integrations/pull_update_test.go @@ -58,7 +58,7 @@ func createOutdatedPR(t *testing.T, actor, forkOrg *models.User) *models.PullReq assert.NoError(t, err) assert.NotEmpty(t, baseRepo) - headRepo, err := repo_module.ForkRepository(actor, forkOrg, baseRepo, "repo-pr-update", "desc") + headRepo, err := repo_module.ForkRepository(actor, forkOrg, baseRepo, "repo-pr-update", "desc", "") assert.NoError(t, err) assert.NotEmpty(t, headRepo) diff --git a/models/action.go b/models/action.go index e2958821c..4821910db 100755 --- a/models/action.go +++ b/models/action.go @@ -164,12 +164,24 @@ func (a *Action) GetRepoName() string { return a.Repo.Name } +// GetRepoName returns the name of the action repository. +func (a *Action) GetRepoDisplayName() string { + a.loadRepo() + return a.Repo.DisplayName() +} + // ShortRepoName returns the name of the action repository // trimmed to max 33 chars. func (a *Action) ShortRepoName() string { return base.EllipsisString(a.GetRepoName(), 33) } +// ShortRepoName returns the name of the action repository +// trimmed to max 33 chars. +func (a *Action) ShortRepoDisplayName() string { + return base.EllipsisString(a.GetRepoDisplayName(), 33) +} + // GetRepoPath returns the virtual path to the action repository. func (a *Action) GetRepoPath() string { return path.Join(a.GetRepoUserName(), a.GetRepoName()) @@ -181,6 +193,12 @@ func (a *Action) ShortRepoPath() string { return path.Join(a.ShortRepoUserName(), a.ShortRepoName()) } +// ShortRepoPath returns the virtual path to the action repository +// trimmed to max 20 + 1 + 33 chars. +func (a *Action) ShortRepoFullDisplayName() string { + return path.Join(a.ShortRepoUserName(), a.ShortRepoDisplayName()) +} + // GetRepoLink returns relative link to action repository. func (a *Action) GetRepoLink() string { if len(setting.AppSubURL) > 0 { @@ -346,11 +364,12 @@ func GetFeeds(opts GetFeedsOptions) ([]*Action, error) { return actions, nil } -func GetLast20PublicFeeds() ([]*Action, error) { +func GetLast20PublicFeeds(opTypes []int) ([]*Action, error) { cond := builder.NewCond() cond = cond.And(builder.Eq{"is_private": false}) cond = cond.And(builder.Eq{"is_deleted": false}) - + cond = cond.And(builder.Expr("user_id=act_user_id")) + cond = cond.And(builder.In("op_type", opTypes)) actions := make([]*Action, 0, 20) diff --git a/models/ai_model_manage.go b/models/ai_model_manage.go index 581b19a9c..ed696fcf0 100644 --- a/models/ai_model_manage.go +++ b/models/ai_model_manage.go @@ -36,6 +36,7 @@ type AiModelManage struct { CreatedUnix timeutil.TimeStamp `xorm:"created"` UpdatedUnix timeutil.TimeStamp `xorm:"updated"` IsCanOper bool + IsCanDelete bool } type AiModelQueryOptions struct { diff --git a/models/cloudbrain.go b/models/cloudbrain.go index efaa9ffeb..0a14ea7b4 100755 --- a/models/cloudbrain.go +++ b/models/cloudbrain.go @@ -22,6 +22,16 @@ const ( NPUResource = "NPU" GPUResource = "CPU/GPU" + //notebook storage category + EVSCategory = "EVS" + EFSCategory = "EFS" + + ManagedOwnership = "MANAGED" + DetectedOwnership = "DEDICATED" + + NotebookFeature = "NOTEBOOK" + DefaultFeature = "DEFAULT" + JobWaiting CloudbrainStatus = "WAITING" JobStopped CloudbrainStatus = "STOPPED" JobSucceeded CloudbrainStatus = "SUCCEEDED" @@ -33,6 +43,7 @@ const ( JobTypeSnn4imagenet JobType = "SNN4IMAGENET" JobTypeBrainScore JobType = "BRAINSCORE" JobTypeTrain JobType = "TRAIN" + JobTypeInference JobType = "INFERENCE" //notebook ModelArtsCreateQueue ModelArtsJobStatus = "CREATE_QUEUING" //免费资源创建排队中 @@ -77,28 +88,30 @@ const ( ) type Cloudbrain struct { - ID int64 `xorm:"pk autoincr"` - JobID string `xorm:"INDEX NOT NULL"` - JobType string `xorm:"INDEX NOT NULL DEFAULT 'DEBUG'"` - JobName string - Status string - UserID int64 - RepoID int64 - SubTaskName string - ContainerID string - ContainerIp string - CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` - UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` - Duration int64 - TrainJobDuration string - Image string //GPU镜像名称 - GpuQueue string //GPU类型即GPU队列 - ResourceSpecId int //GPU规格id - DeletedAt time.Time `xorm:"deleted"` - CanDebug bool `xorm:"-"` - CanDel bool `xorm:"-"` - CanModify bool `xorm:"-"` - Type int + ID int64 `xorm:"pk autoincr"` + JobID string `xorm:"INDEX NOT NULL"` + JobType string `xorm:"INDEX NOT NULL DEFAULT 'DEBUG'"` + JobName string + Status string + UserID int64 + RepoID int64 + SubTaskName string + ContainerID string + ContainerIp string + CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` + UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` + Duration int64 + TrainJobDuration string + Image string //GPU镜像名称 + GpuQueue string //GPU类型即GPU队列 + ResourceSpecId int //GPU规格id + DeletedAt time.Time `xorm:"deleted"` + CanDebug bool `xorm:"-"` + CanDel bool `xorm:"-"` + CanModify bool `xorm:"-"` + Type int + BenchmarkTypeID int + BenchmarkChildTypeID int VersionID int64 //版本id VersionName string `xorm:"INDEX"` //当前版本 @@ -111,7 +124,7 @@ type Cloudbrain struct { ComputeResource string //计算资源,例如npu EngineID int64 //引擎id - TrainUrl string //输出的obs路径 + TrainUrl string //输出模型的obs路径 BranchName string //分支名称 Parameters string //传给modelarts的param参数 BootFile string //启动文件 @@ -125,6 +138,12 @@ type Cloudbrain struct { EngineName string //引擎名称 TotalVersionCount int //任务的所有版本数量,包括删除的 + LabelName string //标签名称 + ModelName string //模型名称 + ModelVersion string //模型版本 + CkptName string //权重文件名称 + ResultUrl string //推理结果的obs路径 + User *User `xorm:"-"` Repo *Repository `xorm:"-"` } @@ -207,7 +226,7 @@ type CloudbrainsOptions struct { CloudbrainIDs []int64 // JobStatus CloudbrainStatus Type int - JobType string + JobTypes []string VersionName string IsLatestVersion string JobTypeNot bool @@ -380,6 +399,24 @@ type Category struct { Value string `json:"value"` } +type BenchmarkTypes struct { + BenchmarkType []*BenchmarkType `json:"type"` +} + +type BenchmarkType struct { + Id int `json:"id"` + First string `json:"first"` //一级算法类型名称 + Second []*BenchmarkDataset `json:"second"` +} + +type BenchmarkDataset struct { + Id int `json:"id"` + Value string `json:"value"` //二级算法类型名称 + Attachment string `json:"attachment"` //数据集的uuid + Owner string `json:"owner"` //评估脚本所在仓库的拥有者 + RepoName string `json:"repo_name"` //评估脚本所在仓库的名称 +} + type GpuInfos struct { GpuInfo []*GpuInfo `json:"gpu_type"` } @@ -435,11 +472,83 @@ type CommitImageResult struct { Payload map[string]interface{} `json:"payload"` } +type GetJobLogParams struct { + Size string `json:"size"` + Sort string `json:"sort"` + QueryInfo QueryInfo `json:"query"` +} + +type QueryInfo struct { + MatchInfo MatchInfo `json:"match"` +} + +type MatchInfo struct { + PodName string `json:"kubernetes.pod.name"` +} + +type GetJobLogResult struct { + ScrollID string `json:"_scroll_id"` + Took int `json:"took"` + TimedOut bool `json:"timed_out"` + Shards struct { + Total int `json:"total"` + Successful int `json:"successful"` + Skipped int `json:"skipped"` + Failed int `json:"failed"` + } `json:"_shards"` + Hits struct { + Hits []Hits `json:"hits"` + } `json:"hits"` +} + +type Hits struct { + Index string `json:"_index"` + Type string `json:"_type"` + ID string `json:"_id"` + Source struct { + Message string `json:"message"` + } `json:"_source"` + Sort []int `json:"sort"` +} + +type GetAllJobLogParams struct { + Scroll string `json:"scroll"` + ScrollID string `json:"scroll_id"` +} + +type DeleteJobLogTokenParams struct { + ScrollID string `json:"scroll_id"` +} + +type DeleteJobLogTokenResult struct { + Succeeded bool `json:"succeeded"` + NumFreed int `json:"num_freed"` +} + type CloudBrainResult struct { Code string `json:"code"` Msg string `json:"msg"` } +type CreateNotebook2Params struct { + JobName string `json:"name"` + Description string `json:"description"` + Duration int64 `json:"duration"` //ms + Feature string `json:"feature"` + PoolID string `json:"pool_id"` + Flavor string `json:"flavor"` + ImageID string `json:"image_id"` + WorkspaceID string `json:"workspace_id"` + Volume VolumeReq `json:"volume"` +} + +type VolumeReq struct { + Capacity int `json:"capacity"` + Category string `json:"category"` + Ownership string `json:"ownership"` + Uri string `json:"uri"` +} + type CreateNotebookParams struct { JobName string `json:"name"` Description string `json:"description"` @@ -557,6 +666,42 @@ type GetNotebookResult struct { } `json:"spec"` } +type GetNotebook2Result struct { + ErrorCode string `json:"error_code"` + ErrorMsg string `json:"error_msg"` + FailReason string `json:"fail_reason"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Status string `json:"status"` + Url string `json:"url"` //实例访问的URL + Token string `json:"token"` //notebook鉴权使用的token信息 + Flavor string `json:"flavor"` + CreateTime string + LatestUpdateTime string + CreateAt int64 `json:"create_at"` //实例创建的时间,UTC毫秒 + UpdateAt int64 `json:"update_at"` //实例最后更新(不包括保活心跳)的时间,UTC毫秒 + Image struct { + Name string `json:"name"` + Status string `json:"status"` + QueuingNum int `json:"queuing_num"` + QueueLeftTime int `json:"queue_left_time"` //s + Duration int `json:"duration"` //auto_stop_time s + } `json:"image"` + Lease struct { + CreateTime int64 `json:"create_at"` //实例创建的时间,UTC毫秒 + Duration int64 `json:"duration"` //实例运行时长,以创建时间为起点计算,即“创建时间+duration > 当前时刻”时,系统会自动停止实例 + UpdateTime int64 `json:"update_at"` //实例最后更新(不包括保活心跳)的时间,UTC毫秒 + } `json:"lease"` //实例自动停止的倒计时信息 + VolumeRes struct { + Capacity int `json:"capacity"` + Category string `json:"category"` + MountPath string `json:"mount_path"` + Ownership string `json:"ownership"` + Status string `json:"status"` + } `json:"volume"` +} + type GetTokenParams struct { Auth Auth `json:"auth"` } @@ -610,6 +755,7 @@ type NotebookActionResult struct { ErrorMsg string `json:"error_msg"` CurrentStatus string `json:"current_status"` PreviousState string `json:"previous_state"` + Status string `json:"status"` } type NotebookGetJobTokenResult struct { @@ -644,6 +790,25 @@ type Config struct { Flavor Flavor `json:"flavor"` PoolID string `json:"pool_id"` } +type CreateInferenceJobParams struct { + JobName string `json:"job_name"` + Description string `json:"job_desc"` + InfConfig InfConfig `json:"config"` + WorkspaceID string `json:"workspace_id"` +} + +type InfConfig struct { + WorkServerNum int `json:"worker_server_num"` + AppUrl string `json:"app_url"` //训练作业的代码目录 + BootFileUrl string `json:"boot_file_url"` //训练作业的代码启动文件,需要在代码目录下 + Parameter []Parameter `json:"parameter"` + DataUrl string `json:"data_url"` //训练作业需要的数据集OBS路径URL + EngineID int64 `json:"engine_id"` + LogUrl string `json:"log_url"` + CreateVersion bool `json:"create_version"` + Flavor Flavor `json:"flavor"` + PoolID string `json:"pool_id"` +} type CreateTrainJobVersionParams struct { Description string `json:"job_desc"` @@ -894,14 +1059,14 @@ func Cloudbrains(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int64, error) { ) } - if (opts.JobType) != "" { + if len(opts.JobTypes) > 0 { if opts.JobTypeNot { cond = cond.And( - builder.Neq{"cloudbrain.job_type": opts.JobType}, + builder.NotIn("cloudbrain.job_type", opts.JobTypes), ) } else { cond = cond.And( - builder.Eq{"cloudbrain.job_type": opts.JobType}, + builder.In("cloudbrain.job_type", opts.JobTypes), ) } } @@ -978,6 +1143,7 @@ func QueryModelTrainJobList(repoId int64) ([]*CloudbrainInfo, int, error) { cond = cond.And( builder.Eq{"job_type": "TRAIN"}, ) + cloudbrains := make([]*CloudbrainInfo, 0) if err := sess.Select("job_id,job_name").Table(&Cloudbrain{}).Where(cond).OrderBy("created_unix DESC"). Find(&cloudbrains); err != nil { @@ -1025,9 +1191,9 @@ func CloudbrainsVersionList(opts *CloudbrainsOptions) ([]*CloudbrainInfo, int, e ) } - if (opts.JobType) != "" { + if len(opts.JobTypes) > 0 { cond = cond.And( - builder.Eq{"cloudbrain.job_type": opts.JobType}, + builder.In("cloudbrain.job_type", opts.JobTypes), ) } @@ -1194,8 +1360,8 @@ func GetCloudBrainUnStoppedJob() ([]*Cloudbrain, error) { Find(&cloudbrains) } -func GetCloudbrainCountByUserID(userID int64) (int, error) { - count, err := x.In("status", JobWaiting, JobRunning).And("job_type = ? and user_id = ? and type = ?", JobTypeDebug, userID, TypeCloudBrainOne).Count(new(Cloudbrain)) +func GetCloudbrainCountByUserID(userID int64, jobType string) (int, error) { + count, err := x.In("status", JobWaiting, JobRunning).And("job_type = ? and user_id = ? and type = ?", jobType, userID, TypeCloudBrainOne).Count(new(Cloudbrain)) return int(count), err } @@ -1211,6 +1377,22 @@ func GetCloudbrainTrainJobCountByUserID(userID int64) (int, error) { return int(count), err } +func GetCloudbrainInferenceJobCountByUserID(userID int64) (int, error) { + count, err := x.In("status", ModelArtsTrainJobInit, ModelArtsTrainJobImageCreating, ModelArtsTrainJobSubmitTrying, ModelArtsTrainJobWaiting, ModelArtsTrainJobRunning, ModelArtsTrainJobScaling, ModelArtsTrainJobCheckInit, ModelArtsTrainJobCheckRunning, ModelArtsTrainJobCheckRunningCompleted). + And("job_type = ? and user_id = ? and type = ?", JobTypeInference, userID, TypeCloudBrainTwo).Count(new(Cloudbrain)) + return int(count), err +} + +func UpdateInferenceJob(job *Cloudbrain) error { + return updateInferenceJob(x, job) +} + +func updateInferenceJob(e Engine, job *Cloudbrain) error { + var sess *xorm.Session + sess = e.Where("job_id = ?", job.JobID) + _, err := sess.Cols("status", "train_job_duration").Update(job) + return err +} func RestartCloudbrain(old *Cloudbrain, new *Cloudbrain) (err error) { sess := x.NewSession() defer sess.Close() diff --git a/models/repo.go b/models/repo.go index f393b51b2..6b3df9fe0 100755 --- a/models/repo.go +++ b/models/repo.go @@ -12,6 +12,7 @@ import ( "errors" "fmt" "html/template" + "math/rand" "xorm.io/xorm" "code.gitea.io/gitea/modules/blockchain" @@ -139,6 +140,7 @@ func NewRepoContext() { // RepositoryStatus defines the status of repository type RepositoryStatus int type RepoBlockChainStatus int +type RepoType int // all kinds of RepositoryStatus const ( @@ -152,6 +154,11 @@ const ( RepoBlockChainFailed ) +const ( + RepoNormal RepoType = iota + RepoCourse +) + // Repository represents a git repository. type Repository struct { ID int64 `xorm:"pk autoincr"` @@ -165,7 +172,8 @@ type Repository struct { OriginalServiceType api.GitServiceType `xorm:"index"` OriginalURL string `xorm:"VARCHAR(2048)"` DefaultBranch string - + CreatorID int64 `xorm:"INDEX NOT NULL DEFAULT 0"` + Creator *User `xorm:"-"` NumWatches int NumStars int NumForks int @@ -174,11 +182,12 @@ type Repository struct { NumOpenIssues int `xorm:"-"` NumPulls int NumClosedPulls int - NumOpenPulls int `xorm:"-"` - NumMilestones int `xorm:"NOT NULL DEFAULT 0"` - NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"` - NumOpenMilestones int `xorm:"-"` - NumCommit int64 `xorm:"NOT NULL DEFAULT 0"` + NumOpenPulls int `xorm:"-"` + NumMilestones int `xorm:"NOT NULL DEFAULT 0"` + NumClosedMilestones int `xorm:"NOT NULL DEFAULT 0"` + NumOpenMilestones int `xorm:"-"` + NumCommit int64 `xorm:"NOT NULL DEFAULT 0"` + RepoType RepoType `xorm:"NOT NULL DEFAULT 0"` IsPrivate bool `xorm:"INDEX"` IsEmpty bool `xorm:"INDEX"` @@ -221,8 +230,10 @@ type Repository struct { CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"` UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"` - Hot int64 `xorm:"-"` - Active int64 `xorm:"-"` + Hot int64 `xorm:"-"` + Active int64 `xorm:"-"` + Alias string `xorm:"INDEX"` + LowerAlias string `xorm:"INDEX"` } // SanitizedOriginalURL returns a sanitized OriginalURL @@ -233,6 +244,14 @@ func (repo *Repository) SanitizedOriginalURL() string { return util.SanitizeURLCredentials(repo.OriginalURL, false) } +// GetAlias returns a sanitized OriginalURL +func (repo *Repository) DisplayName() string { + if repo.Alias == "" { + return repo.Name + } + return repo.Alias +} + // ColorFormat returns a colored string to represent this repo func (repo *Repository) ColorFormat(s fmt.State) { var ownerName interface{} @@ -286,6 +305,11 @@ func (repo *Repository) FullName() string { return repo.OwnerName + "/" + repo.Name } +// FullDisplayName returns the repository full display name +func (repo *Repository) FullDisplayName() string { + return repo.OwnerName + "/" + repo.DisplayName() +} + // HTMLURL returns the repository HTML URL func (repo *Repository) HTMLURL() string { return setting.AppURL + repo.FullName() @@ -385,7 +409,9 @@ func (repo *Repository) innerAPIFormat(e Engine, mode AccessMode, isParent bool) ID: repo.ID, Owner: repo.Owner.APIFormat(), Name: repo.Name, + Alias: repo.Alias, FullName: repo.FullName(), + FullDisplayName: repo.FullDisplayName(), Description: repo.Description, Private: repo.IsPrivate, Template: repo.IsTemplate, @@ -548,6 +574,19 @@ func (repo *Repository) GetOwner() error { return repo.getOwner(x) } +func (repo *Repository) getCreator(e Engine) (err error) { + if repo.CreatorID == 0 { + return nil + } + + repo.Creator, err = getUserByID(e, repo.CreatorID) + return err +} + +func (repo *Repository) GetCreator() error { + return repo.getCreator(x) +} + func (repo *Repository) mustOwner(e Engine) *User { if err := repo.getOwner(e); err != nil { return &User{ @@ -921,17 +960,50 @@ func (repo *Repository) DescriptionHTML() template.HTML { return template.HTML(markup.Sanitize(string(desc))) } -func isRepositoryExist(e Engine, u *User, repoName string) (bool, error) { - has, err := e.Get(&Repository{ - OwnerID: u.ID, - LowerName: strings.ToLower(repoName), - }) - return has && com.IsDir(RepoPath(u.Name, repoName)), err +func isRepositoryExist(e Engine, u *User, repoName string, alias string) (bool, error) { + var cond = builder.NewCond() + cond = cond.And(builder.Eq{"owner_id": u.ID}) + if alias != "" { + subCon := builder.NewCond() + subCon = subCon.Or(builder.Eq{"lower_alias": strings.ToLower(alias)}, builder.Eq{"lower_name": strings.ToLower(repoName)}) + cond = cond.And(subCon) + } else { + cond = cond.And(builder.Eq{"lower_name": strings.ToLower(repoName)}) + } + count, err := e.Where(cond).Count(&Repository{}) + return count > 0 || com.IsDir(RepoPath(u.Name, repoName)), err } // IsRepositoryExist returns true if the repository with given name under user has already existed. -func IsRepositoryExist(u *User, repoName string) (bool, error) { - return isRepositoryExist(x, u, repoName) +func IsRepositoryExist(u *User, repoName string, alias string) (bool, error) { + return isRepositoryExist(x, u, repoName, alias) +} + +// IsRepositoryAliasExist returns true if the repository with given alias under user has already existed. +func IsRepositoryAliasExist(u *User, alias string) (bool, error) { + return isRepositoryAliasExist(x, u, alias) +} + +func isRepositoryAliasExist(e Engine, u *User, alias string) (bool, error) { + var cond = builder.NewCond() + cond = cond.And(builder.Eq{"owner_id": u.ID}) + cond = cond.And(builder.Eq{"lower_alias": strings.ToLower(alias)}) + count, err := e.Where(cond).Count(&Repository{}) + return count > 0, err +} + +func IsRepositoryAliasAvailable(doer *User, alias string) error { + if err := IsUsableRepoAlias(alias); err != nil { + return err + } + + has, err := IsRepositoryAliasExist(doer, alias) + if err != nil { + return fmt.Errorf("IsRepositoryExist: %v", err) + } else if has { + return ErrRepoAlreadyExist{doer.Name, alias} + } + return nil } // CloneLink represents different types of clone URLs of repository. @@ -975,20 +1047,24 @@ func (repo *Repository) CloneLink() (cl *CloneLink) { } // CheckCreateRepository check if could created a repository -func CheckCreateRepository(doer, u *User, name string) error { +func CheckCreateRepository(doer, u *User, repoName, alias string) error { if !doer.CanCreateRepo() { return ErrReachLimitOfRepo{u.MaxRepoCreation} } - if err := IsUsableRepoName(name); err != nil { + if err := IsUsableRepoName(repoName); err != nil { + return err + } + + if err := IsUsableRepoAlias(alias); err != nil { return err } - has, err := isRepositoryExist(x, u, name) + has, err := isRepositoryExist(x, u, repoName, alias) if err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { - return ErrRepoAlreadyExist{u.Name, name} + return ErrRepoAlreadyExist{u.Name, repoName} } return nil } @@ -996,6 +1072,7 @@ func CheckCreateRepository(doer, u *User, name string) error { // CreateRepoOptions contains the create repository options type CreateRepoOptions struct { Name string + Alias string Description string OriginalURL string GitServiceType api.GitServiceType @@ -1008,6 +1085,8 @@ type CreateRepoOptions struct { IsMirror bool AutoInit bool Status RepositoryStatus + IsCourse bool + Topics []string } // GetRepoInitFile returns repository init files @@ -1036,8 +1115,10 @@ func GetRepoInitFile(tp, name string) ([]byte, error) { } var ( - reservedRepoNames = []string{".", ".."} - reservedRepoPatterns = []string{"*.git", "*.wiki"} + reservedRepoNames = []string{".", ".."} + reservedRepoPatterns = []string{"*.git", "*.wiki"} + reservedRepoAliasNames = []string{} + reservedRepoAliasPatterns = []string{} ) // IsUsableRepoName returns true when repository is usable @@ -1045,19 +1126,34 @@ func IsUsableRepoName(name string) error { return isUsableName(reservedRepoNames, reservedRepoPatterns, name) } +// IsUsableRepoAlias returns true when repository alias is usable +func IsUsableRepoAlias(name string) error { + return isUsableName(reservedRepoAliasNames, reservedRepoAliasPatterns, name) +} + // CreateRepository creates a repository for the user/organization. -func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error) { +func CreateRepository(ctx DBContext, doer, u *User, repo *Repository, opts ...CreateRepoOptions) (err error) { + if repo.Alias == "" { + repo.Alias = repo.Name + } + repo.LowerAlias = strings.ToLower(repo.Alias) if err = IsUsableRepoName(repo.Name); err != nil { return err } - has, err := isRepositoryExist(ctx.e, u, repo.Name) + if err := IsUsableRepoAlias(repo.Alias); err != nil { + return err + } + has, err := isRepositoryExist(ctx.e, u, repo.Name, repo.Alias) if err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { return ErrRepoAlreadyExist{u.Name, repo.Name} } - + isCourse := isCourse(opts) + if isCourse { + repo.CreatorID = doer.ID + } if _, err = ctx.e.Insert(repo); err != nil { return err } @@ -1091,17 +1187,23 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error Config: &PullRequestsConfig{AllowMerge: true, AllowRebase: true, AllowRebaseMerge: true, AllowSquash: true}, }) } else if tp == UnitTypeDatasets { - units = append(units, RepoUnit{ - RepoID: repo.ID, - Type: tp, - Config: &DatasetConfig{EnableDataset: true}, - }) + if !isCourse { + units = append(units, RepoUnit{ + RepoID: repo.ID, + Type: tp, + Config: &DatasetConfig{EnableDataset: true}, + }) + } + } else if tp == UnitTypeCloudBrain { - units = append(units, RepoUnit{ - RepoID: repo.ID, - Type: tp, - Config: &CloudBrainConfig{EnableCloudBrain: true}, - }) + if !isCourse { + units = append(units, RepoUnit{ + RepoID: repo.ID, + Type: tp, + Config: &CloudBrainConfig{EnableCloudBrain: true}, + }) + } + } else if tp == UnitTypeBlockChain { units = append(units, RepoUnit{ RepoID: repo.ID, @@ -1109,11 +1211,13 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error Config: &BlockChainConfig{EnableBlockChain: true}, }) } else if tp == UnitTypeModelManage { - units = append(units, RepoUnit{ - RepoID: repo.ID, - Type: tp, - Config: &ModelManageConfig{EnableModelManage: true}, - }) + if !isCourse { + units = append(units, RepoUnit{ + RepoID: repo.ID, + Type: tp, + Config: &ModelManageConfig{EnableModelManage: true}, + }) + } } else { units = append(units, RepoUnit{ RepoID: repo.ID, @@ -1183,6 +1287,14 @@ func CreateRepository(ctx DBContext, doer, u *User, repo *Repository) (err error return nil } +func isCourse(opts []CreateRepoOptions) bool { + var isCourse = false + if len(opts) > 0 { + isCourse = opts[0].IsCourse + } + return isCourse +} + func countRepositories(userID int64, private bool) int64 { sess := x.Where("id > 0") @@ -1233,7 +1345,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error } // Check if new owner has repository with same name. - has, err := IsRepositoryExist(newOwner, repo.Name) + has, err := IsRepositoryExist(newOwner, repo.Name, repo.Alias) if err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { @@ -1366,7 +1478,7 @@ func ChangeRepositoryName(doer *User, repo *Repository, newRepoName string) (err return err } - has, err := IsRepositoryExist(repo.Owner, newRepoName) + has, err := IsRepositoryExist(repo.Owner, newRepoName, "") if err != nil { return fmt.Errorf("IsRepositoryExist: %v", err) } else if has { @@ -1848,6 +1960,26 @@ func getRepositoryByOwnerAndName(e Engine, ownerName, repoName string) (*Reposit return &repo, nil } +// GetRepositoryByOwnerAndAlias returns the repository by given ownername and reponame. +func GetRepositoryByOwnerAndAlias(ownerName, alias string) (*Repository, error) { + return getRepositoryByOwnerAndAlias(x, ownerName, alias) +} + +func getRepositoryByOwnerAndAlias(e Engine, ownerName, alias string) (*Repository, error) { + var repo Repository + has, err := e.Table("repository").Select("repository.*"). + Join("INNER", "`user`", "`user`.id = repository.owner_id"). + Where("repository.lower_alias = ?", strings.ToLower(alias)). + And("`user`.lower_name = ?", strings.ToLower(ownerName)). + Get(&repo) + if err != nil { + return nil, err + } else if !has { + return nil, ErrRepoNotExist{0, 0, ownerName, alias} + } + return &repo, nil +} + // GetRepositoryByName returns the repository by given name under user if exists. func GetRepositoryByName(ownerID int64, name string) (*Repository, error) { repo := &Repository{ @@ -2521,6 +2653,14 @@ func UpdateRepositoryCommitNum(repo *Repository) error { return nil } +func GenerateDefaultRepoName(ownerName string) string { + if len(ownerName) > 5 { + ownerName = ownerName[:5] + } + now := time.Now().Format("20060102150405") + return ownerName + now + fmt.Sprint(rand.Intn(10)) +} + type RepoFile struct { CommitId string Content []byte diff --git a/models/repo_generate.go b/models/repo_generate.go index 480683cd4..08bb1463d 100644 --- a/models/repo_generate.go +++ b/models/repo_generate.go @@ -19,6 +19,7 @@ import ( // GenerateRepoOptions contains the template units to generate type GenerateRepoOptions struct { Name string + Alias string Description string Private bool GitContent bool diff --git a/models/repo_list.go b/models/repo_list.go index c4d8ee823..6fb9380de 100755 --- a/models/repo_list.go +++ b/models/repo_list.go @@ -48,9 +48,12 @@ func (repos RepositoryList) loadAttributes(e Engine) error { set := make(map[int64]struct{}) repoIDs := make([]int64, len(repos)) + setCreator := make(map[int64]struct{}) for i := range repos { set[repos[i].OwnerID] = struct{}{} repoIDs[i] = repos[i].ID + setCreator[repos[i].CreatorID] = struct{}{} + } // Load owners. @@ -61,8 +64,18 @@ func (repos RepositoryList) loadAttributes(e Engine) error { Find(&users); err != nil { return fmt.Errorf("find users: %v", err) } + //Load creator + creators := make(map[int64]*User, len(set)) + if err := e. + Where("id > 0"). + In("id", keysInt64(setCreator)). + Find(&creators); err != nil { + return fmt.Errorf("find create repo users: %v", err) + } + for i := range repos { repos[i].Owner = users[repos[i].OwnerID] + repos[i].Creator = creators[repos[i].CreatorID] } // Load primary language. @@ -174,6 +187,10 @@ type SearchRepoOptions struct { // True -> include just has milestones // False -> include just has no milestone HasMilestones util.OptionalBool + // None -> include all repos + // True -> include just courses + // False -> include just no courses + Course util.OptionalBool } //SearchOrderBy is used to sort the result @@ -200,8 +217,8 @@ const ( SearchOrderByForks SearchOrderBy = "num_forks ASC" SearchOrderByForksReverse SearchOrderBy = "num_forks DESC" SearchOrderByDownloadTimes SearchOrderBy = "download_times DESC" - SearchOrderByHot SearchOrderBy = "(num_watches + num_stars + num_forks + clone_cnt) DESC" - SearchOrderByActive SearchOrderBy = "(num_issues + num_pulls + num_commit) DESC" + SearchOrderByHot SearchOrderBy = "(num_watches + num_stars + num_forks + clone_cnt) DESC" + SearchOrderByActive SearchOrderBy = "(num_issues + num_pulls + num_commit) DESC" ) // SearchRepositoryCondition creates a query condition according search repository options @@ -321,6 +338,7 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { var likes = builder.NewCond() for _, v := range strings.Split(opts.Keyword, ",") { likes = likes.Or(builder.Like{"lower_name", strings.ToLower(v)}) + likes = likes.Or(builder.Like{"alias", v}) if opts.IncludeDescription { likes = likes.Or(builder.Like{"LOWER(description)", strings.ToLower(v)}) } @@ -350,6 +368,10 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond { cond = cond.And(builder.Eq{"is_mirror": opts.Mirror == util.OptionalBoolTrue}) } + if opts.Course == util.OptionalBoolTrue { + cond = cond.And(builder.Eq{"repo_type": RepoCourse}) + } + if opts.Actor != nil && opts.Actor.IsRestricted { cond = cond.And(accessibleRepositoryCondition(opts.Actor)) } diff --git a/models/repo_tag.go b/models/repo_tag.go index 76740bd76..730eb3f2a 100644 --- a/models/repo_tag.go +++ b/models/repo_tag.go @@ -28,6 +28,7 @@ type OfficialTagRepos struct { type TagReposBrief struct { RepoID int64 RepoName string + Alias string TagID int64 } @@ -41,7 +42,7 @@ type TagsDetail struct { TagId int64 TagName string TagLimit int - RepoList []Repository + RepoList []*Repository } func GetTagByID(id int64) (*OfficialTag, error) { @@ -97,7 +98,7 @@ func UpdateTagReposByID(tagID, orgID int64, repoIdList []int64) error { func GetTagRepos(tagID, orgID int64) ([]TagReposSelected, error) { t := make([]TagReposBrief, 0) - const SQLCmd = "select t1.id as repo_id,t1.name as repo_name,t2.id as tag_id from repository t1 left join official_tag_repos t2 on (t1.id = t2.repo_id and t2.tag_id = ?) where t1.owner_id = ? and t1.is_private = false order by t1.updated_unix desc" + const SQLCmd = "select t1.id as repo_id,t1.name as repo_name,t1.alias,t2.id as tag_id from repository t1 left join official_tag_repos t2 on (t1.id = t2.repo_id and t2.tag_id = ?) where t1.owner_id = ? and t1.is_private = false order by t1.updated_unix desc" if err := x.SQL(SQLCmd, tagID, orgID).Find(&t); err != nil { return nil, err @@ -108,9 +109,13 @@ func GetTagRepos(tagID, orgID int64) ([]TagReposSelected, error) { if v.TagID > 0 { selected = true } + repoName := v.Alias + if v.Alias == "" { + repoName = v.RepoName + } r = append(r, TagReposSelected{ RepoID: v.RepoID, - RepoName: v.RepoName, + RepoName: repoName, Selected: selected, }) } @@ -141,8 +146,8 @@ func GetAllOfficialTagRepos(orgID int64, isOwner bool) ([]TagsDetail, error) { return result, nil } -func GetOfficialTagDetail(orgID, tagId int64) ([]Repository, error) { - t := make([]Repository, 0) +func GetOfficialTagDetail(orgID, tagId int64) ([]*Repository, error) { + t := make([]*Repository, 0) const SQLCmd = "select t2.* from official_tag_repos t1 inner join repository t2 on t1.repo_id = t2.id where t1.org_id = ? and t1.tag_id=? order by t2.updated_unix desc" if err := x.SQL(SQLCmd, orgID, tagId).Find(&t); err != nil { diff --git a/models/repo_watch.go b/models/repo_watch.go index 85a7834bb..31868fcae 100644 --- a/models/repo_watch.go +++ b/models/repo_watch.go @@ -305,7 +305,10 @@ func NotifyWatchersActions(acts []*Action) error { return err } } - return sess.Commit() + + err := sess.Commit() + producer(acts...) + return err } func watchIfAuto(e Engine, userID, repoID int64, isWrite bool) error { diff --git a/models/user_business_analysis.go b/models/user_business_analysis.go index d04e350c2..288762161 100644 --- a/models/user_business_analysis.go +++ b/models/user_business_analysis.go @@ -387,7 +387,7 @@ func refreshUserStaticTable(wikiCountMap map[string]int, CommitCodeSizeMap map[s OpenIIndexMap := queryUserRepoOpenIIndex(startTime.Unix(), end_unix) - DataDate := currentTimeNow.Format("2006-01-02") + DataDate := currentTimeNow.Format("2006-01-02") + " 00:01" cond := "type != 1 and is_active=true" count, err := sess.Where(cond).Count(new(User)) diff --git a/modules/auth/auth.go b/modules/auth/auth.go index 352e50ca0..6a156491d 100644 --- a/modules/auth/auth.go +++ b/modules/auth/auth.go @@ -186,6 +186,8 @@ func validate(errs binding.Errors, data map[string]interface{}, f Form, l macaro data["ErrorMsg"] = trName + l.Tr("form.include_error", GetInclude(field)) case validation.ErrGlobPattern: data["ErrorMsg"] = trName + l.Tr("form.glob_pattern_error", errs[0].Message) + case validation.ErrAlphaDashDotChinese: + data["ErrorMsg"] = trName + l.Tr("form.alpha_dash_dot_chinese_error") default: data["ErrorMsg"] = l.Tr("form.unknown_error") + " " + errs[0].Classification } diff --git a/modules/auth/cloudbrain.go b/modules/auth/cloudbrain.go index d598b495c..0d7ef1b02 100755 --- a/modules/auth/cloudbrain.go +++ b/modules/auth/cloudbrain.go @@ -6,14 +6,19 @@ import ( ) type CreateCloudBrainForm struct { - JobName string `form:"job_name" binding:"Required"` - Image string `form:"image" binding:"Required"` - Command string `form:"command" binding:"Required"` - Attachment string `form:"attachment" binding:"Required"` - JobType string `form:"job_type" binding:"Required"` - BenchmarkCategory string `form:"get_benchmark_category"` - GpuType string `form:"gpu_type"` - ResourceSpecId int `form:"resource_spec_id" binding:"Required"` + JobName string `form:"job_name" binding:"Required"` + Image string `form:"image" binding:"Required"` + Command string `form:"command" binding:"Required"` + Attachment string `form:"attachment" binding:"Required"` + JobType string `form:"job_type" binding:"Required"` + BenchmarkCategory string `form:"get_benchmark_category"` + GpuType string `form:"gpu_type"` + TrainUrl string `form:"train_url"` + TestUrl string `form:"test_url"` + Description string `form:"description"` + ResourceSpecId int `form:"resource_spec_id" binding:"Required"` + BenchmarkTypeID int `form:"benchmark_types_id"` + BenchmarkChildTypeID int `form:"benchmark_child_types_id"` } type CommitImageCloudBrainForm struct { diff --git a/modules/auth/modelarts.go b/modules/auth/modelarts.go index 59f72696e..821cd72f8 100755 --- a/modules/auth/modelarts.go +++ b/modules/auth/modelarts.go @@ -45,6 +45,30 @@ type CreateModelArtsTrainJobForm struct { EngineName string `form:"engine_names" binding:"Required"` } +type CreateModelArtsInferenceJobForm struct { + JobName string `form:"job_name" binding:"Required"` + Attachment string `form:"attachment" binding:"Required"` + BootFile string `form:"boot_file" binding:"Required"` + WorkServerNumber int `form:"work_server_number" binding:"Required"` + EngineID int `form:"engine_id" binding:"Required"` + PoolID string `form:"pool_id" binding:"Required"` + Flavor string `form:"flavor" binding:"Required"` + Params string `form:"run_para_list" binding:"Required"` + Description string `form:"description"` + IsSaveParam string `form:"is_save_para"` + ParameterTemplateName string `form:"parameter_template_name"` + PrameterDescription string `form:"parameter_description"` + BranchName string `form:"branch_name" binding:"Required"` + VersionName string `form:"version_name" binding:"Required"` + FlavorName string `form:"flaver_names" binding:"Required"` + EngineName string `form:"engine_names" binding:"Required"` + LabelName string `form:"label_names" binding:"Required"` + TrainUrl string `form:"train_url" binding:"Required"` + ModelName string `form:"model_name" binding:"Required"` + ModelVersion string `form:"model_version" binding:"Required"` + CkptName string `form:"ckpt_name" binding:"Required"` +} + func (f *CreateModelArtsTrainJobForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { return validate(errs, ctx.Data, f, ctx.Locale) } diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go index 8061c6469..c113aa890 100755 --- a/modules/auth/repo_form.go +++ b/modules/auth/repo_form.go @@ -29,6 +29,7 @@ import ( type CreateRepoForm struct { UID int64 `binding:"Required"` RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` + Alias string `binding:"Required;MaxSize(100);AlphaDashDotChinese"` Private bool Description string `binding:"MaxSize(1024)"` DefaultBranch string `binding:"GitRefName;MaxSize(100)"` @@ -62,6 +63,7 @@ type MigrateRepoForm struct { UID int64 `json:"uid" binding:"Required"` // required: true RepoName string `json:"repo_name" binding:"Required;AlphaDashDot;MaxSize(100)"` + Alias string `json:"alias" binding:"Required;AlphaDashDotChinese;MaxSize(100)"` Mirror bool `json:"mirror"` Private bool `json:"private"` Description string `json:"description" binding:"MaxSize(255)"` @@ -109,6 +111,7 @@ func (f MigrateRepoForm) ParseRemoteAddr(user *models.User) (string, error) { // RepoSettingForm form for changing repository settings type RepoSettingForm struct { RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` + Alias string `binding:"Required;AlphaDashDotChinese;MaxSize(100)"` Description string `binding:"MaxSize(255)"` Website string `binding:"ValidUrl;MaxSize(255)"` Interval string @@ -725,3 +728,15 @@ type DeadlineForm struct { func (f *DeadlineForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { return validate(errs, ctx.Data, f, ctx.Locale) } + +type CreateCourseForm struct { + RepoName string `binding:"Required;AlphaDashDot;MaxSize(100)"` + Alias string `binding:"Required;MaxSize(100);AlphaDashDotChinese"` + Topics string + Description string `binding:"MaxSize(1024)"` +} + +// Validate validates the fields +func (f *CreateCourseForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go old mode 100644 new mode 100755 index 228e17dce..86771b9f8 --- a/modules/auth/user_form.go +++ b/modules/auth/user_form.go @@ -105,6 +105,11 @@ func (f RegisterForm) IsEmailDomainWhitelisted() bool { } domain := strings.ToLower(f.Email[n+1:]) + + //support edu.cn + if strings.HasSuffix(domain, "edu.cn") { + return true + } for _, v := range setting.Service.EmailDomainWhitelist { if strings.ToLower(v) == domain { diff --git a/modules/cloudbrain/cloudbrain.go b/modules/cloudbrain/cloudbrain.go index 74dcbe7b0..f15443b30 100755 --- a/modules/cloudbrain/cloudbrain.go +++ b/modules/cloudbrain/cloudbrain.go @@ -14,11 +14,16 @@ import ( ) const ( - Command = `pip3 install jupyterlab==2.2.5 -i https://pypi.tuna.tsinghua.edu.cn/simple;service ssh stop;jupyter lab --no-browser --ip=0.0.0.0 --allow-root --notebook-dir="/code" --port=80 --LabApp.token="" --LabApp.allow_origin="self https://cloudbrain.pcl.ac.cn"` + Command = `pip3 install jupyterlab==2.2.5 -i https://pypi.tuna.tsinghua.edu.cn/simple; + service ssh stop; + jupyter lab --no-browser --ip=0.0.0.0 --allow-root --notebook-dir="/code" --port=80 --LabApp.token="" --LabApp.allow_origin="self https://cloudbrain.pcl.ac.cn"` + //CommandBenchmark = `echo "start benchmark";python /code/test.py;echo "end benchmark"` + CommandBenchmark = `echo "start benchmark";cd /benchmark && bash run_bk.sh;echo "end benchmark"` CodeMountPath = "/code" DataSetMountPath = "/dataset" ModelMountPath = "/model" BenchMarkMountPath = "/benchmark" + BenchMarkResourceID = 1 Snn4imagenetMountPath = "/snn4imagenet" BrainScoreMountPath = "/brainscore" TaskInfoName = "/taskInfo" @@ -102,7 +107,7 @@ func AdminOrJobCreaterRight(ctx *context.Context) { } -func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath, modelPath, benchmarkPath, snn4imagenetPath, brainScorePath, jobType, gpuQueue string, resourceSpecId int) error { +func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath, modelPath, benchmarkPath, snn4imagenetPath, brainScorePath, jobType, gpuQueue, description string, benchmarkTypeID, benchmarkChildTypeID, resourceSpecId int) error { dataActualPath := setting.Attachment.Minio.RealPath + setting.Attachment.Minio.Bucket + "/" + setting.Attachment.Minio.BasePath + @@ -201,19 +206,22 @@ func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath, var jobID = jobResult.Payload["jobId"].(string) err = models.CreateCloudbrain(&models.Cloudbrain{ - Status: string(models.JobWaiting), - UserID: ctx.User.ID, - RepoID: ctx.Repo.Repository.ID, - JobID: jobID, - JobName: jobName, - SubTaskName: SubTaskName, - JobType: jobType, - Type: models.TypeCloudBrainOne, - Uuid: uuid, - Image: image, - GpuQueue: gpuQueue, - ResourceSpecId: resourceSpecId, - ComputeResource: models.GPUResource, + Status: string(models.JobWaiting), + UserID: ctx.User.ID, + RepoID: ctx.Repo.Repository.ID, + JobID: jobID, + JobName: jobName, + SubTaskName: SubTaskName, + JobType: jobType, + Type: models.TypeCloudBrainOne, + Uuid: uuid, + Image: image, + GpuQueue: gpuQueue, + ResourceSpecId: resourceSpecId, + ComputeResource: models.GPUResource, + BenchmarkTypeID: benchmarkTypeID, + BenchmarkChildTypeID: benchmarkChildTypeID, + Description: description, }) if err != nil { @@ -270,7 +278,7 @@ func RestartTask(ctx *context.Context, task *models.Cloudbrain, newJobID *string Volumes: []models.Volume{ { HostPath: models.StHostPath{ - Path: storage.GetMinioPath(jobName, CodeMountPath + "/"), + Path: storage.GetMinioPath(jobName, CodeMountPath+"/"), MountPath: CodeMountPath, ReadOnly: false, }, @@ -284,28 +292,28 @@ func RestartTask(ctx *context.Context, task *models.Cloudbrain, newJobID *string }, { HostPath: models.StHostPath{ - Path: storage.GetMinioPath(jobName, ModelMountPath + "/"), + Path: storage.GetMinioPath(jobName, ModelMountPath+"/"), MountPath: ModelMountPath, ReadOnly: false, }, }, { HostPath: models.StHostPath{ - Path: storage.GetMinioPath(jobName, BenchMarkMountPath + "/"), + Path: storage.GetMinioPath(jobName, BenchMarkMountPath+"/"), MountPath: BenchMarkMountPath, ReadOnly: true, }, }, { HostPath: models.StHostPath{ - Path: storage.GetMinioPath(jobName, Snn4imagenetMountPath + "/"), + Path: storage.GetMinioPath(jobName, Snn4imagenetMountPath+"/"), MountPath: Snn4imagenetMountPath, ReadOnly: true, }, }, { HostPath: models.StHostPath{ - Path: storage.GetMinioPath(jobName, BrainScoreMountPath + "/"), + Path: storage.GetMinioPath(jobName, BrainScoreMountPath+"/"), MountPath: BrainScoreMountPath, ReadOnly: true, }, @@ -323,18 +331,18 @@ func RestartTask(ctx *context.Context, task *models.Cloudbrain, newJobID *string var jobID = jobResult.Payload["jobId"].(string) newTask := &models.Cloudbrain{ - Status: string(models.JobWaiting), - UserID: task.UserID, - RepoID: task.RepoID, - JobID: jobID, - JobName: task.JobName, - SubTaskName: task.SubTaskName, - JobType: task.JobType, - Type: task.Type, - Uuid: task.Uuid, - Image: task.Image, - GpuQueue: task.GpuQueue, - ResourceSpecId: task.ResourceSpecId, + Status: string(models.JobWaiting), + UserID: task.UserID, + RepoID: task.RepoID, + JobID: jobID, + JobName: task.JobName, + SubTaskName: task.SubTaskName, + JobType: task.JobType, + Type: task.Type, + Uuid: task.Uuid, + Image: task.Image, + GpuQueue: task.GpuQueue, + ResourceSpecId: task.ResourceSpecId, ComputeResource: task.ComputeResource, } diff --git a/modules/cloudbrain/resty.go b/modules/cloudbrain/resty.go index 4e30ea0e4..46b7c991b 100755 --- a/modules/cloudbrain/resty.go +++ b/modules/cloudbrain/resty.go @@ -2,7 +2,10 @@ package cloudbrain import ( "encoding/json" + "errors" "fmt" + "net/http" + "strconv" "strings" "code.gitea.io/gitea/modules/log" @@ -23,6 +26,8 @@ const ( JobHasBeenStopped = "S410" Public = "public" Custom = "custom" + LogPageSize = 500 + LogPageTokenExpired = "5m" ) func getRestyClient() *resty.Client { @@ -270,3 +275,99 @@ sendjob: return nil } + +func GetJobLog(jobID string) (*models.GetJobLogResult, error) { + checkSetting() + client := getRestyClient() + var result models.GetJobLogResult + req := models.GetJobLogParams{ + Size: strconv.Itoa(LogPageSize), + Sort: "log.offset", + QueryInfo: models.QueryInfo{ + MatchInfo: models.MatchInfo{ + PodName: jobID + "-task1-0", + }, + }, + } + + res, err := client.R(). + SetHeader("Content-Type", "application/json"). + SetAuthToken(TOKEN). + SetBody(req). + SetResult(&result). + Post(HOST + "es/_search?_source=message&scroll=" + LogPageTokenExpired) + + if err != nil { + log.Error("GetJobLog failed: %v", err) + return &result, fmt.Errorf("resty GetJobLog: %v, %s", err, res.String()) + } + + if !strings.Contains(res.Status(), strconv.Itoa(http.StatusOK)) { + log.Error("res.Status(): %s, response: %s", res.Status(), res.String()) + return &result, errors.New(res.String()) + } + + return &result, nil +} + +func GetJobAllLog(scrollID string) (*models.GetJobLogResult, error) { + checkSetting() + client := getRestyClient() + var result models.GetJobLogResult + req := models.GetAllJobLogParams{ + Scroll: LogPageTokenExpired, + ScrollID: scrollID, + } + + res, err := client.R(). + SetHeader("Content-Type", "application/json"). + SetAuthToken(TOKEN). + SetBody(req). + SetResult(&result). + Post(HOST + "es/_search/scroll") + + if err != nil { + log.Error("GetJobAllLog failed: %v", err) + return &result, fmt.Errorf("resty GetJobAllLog: %v, %s", err, res.String()) + } + + if !strings.Contains(res.Status(), strconv.Itoa(http.StatusOK)) { + log.Error("res.Status(): %s, response: %s", res.Status(), res.String()) + return &result, errors.New(res.String()) + } + + return &result, nil +} + +func DeleteJobLogToken(scrollID string) (error) { + checkSetting() + client := getRestyClient() + var result models.DeleteJobLogTokenResult + req := models.DeleteJobLogTokenParams{ + ScrollID: scrollID, + } + + res, err := client.R(). + SetHeader("Content-Type", "application/json"). + SetAuthToken(TOKEN). + SetBody(req). + SetResult(&result). + Delete(HOST + "es/_search/scroll") + + if err != nil { + log.Error("DeleteJobLogToken failed: %v", err) + return fmt.Errorf("resty DeleteJobLogToken: %v, %s", err, res.String()) + } + + if !strings.Contains(res.Status(), strconv.Itoa(http.StatusOK)) { + log.Error("res.Status(): %s, response: %s", res.Status(), res.String()) + return errors.New(res.String()) + } + + if !result.Succeeded { + log.Error("DeleteJobLogToken failed") + return errors.New("DeleteJobLogToken failed") + } + + return nil +} diff --git a/modules/context/auth.go b/modules/context/auth.go index 9877657eb..61a7b029b 100755 --- a/modules/context/auth.go +++ b/modules/context/auth.go @@ -64,6 +64,11 @@ func Toggle(options *ToggleOptions) macaron.Handler { ctx.Redirect(setting.AppSubURL + "/") return } + + if ctx.QueryBool("course") { + ctx.Redirect(setting.AppSubURL + "/" + setting.Course.OrgName) + return + } } // Redirect to dashboard if user tries to visit any non-login page. diff --git a/modules/context/context.go b/modules/context/context.go index 6cb6c267a..65f4b3f5d 100755 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -45,8 +45,8 @@ type Context struct { IsSigned bool IsBasicAuth bool - Repo *Repository - Org *Organization + Repo *Repository + Org *Organization Cloudbrain *models.Cloudbrain } @@ -328,7 +328,7 @@ func Contexter() macaron.Handler { } } - ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`) + //ctx.Resp.Header().Set(`X-Frame-Options`, `SAMEORIGIN`) ctx.Data["CsrfToken"] = html.EscapeString(x.GetToken()) ctx.Data["CsrfTokenHtml"] = template.HTML(``) @@ -347,9 +347,9 @@ func Contexter() macaron.Handler { ctx.Data["EnableSwagger"] = setting.API.EnableSwagger ctx.Data["EnableOpenIDSignIn"] = setting.Service.EnableOpenIDSignIn - notice, _ := notice.GetNewestNotice() - if notice != nil { - ctx.Data["notice"] = *notice + notices, _ := notice.GetNewestNotice() + if notices != nil { + ctx.Data["notices"] = notices } c.Map(ctx) } diff --git a/modules/context/org.go b/modules/context/org.go index 9b87fba9f..9b3f720c9 100644 --- a/modules/context/org.go +++ b/modules/context/org.go @@ -63,6 +63,8 @@ func HandleOrgAssignment(ctx *Context, args ...bool) { org := ctx.Org.Organization ctx.Data["Org"] = org + ctx.Data["IsCourse"] = ctx.Org.Organization.Name == setting.Course.OrgName + // Force redirection when username is actually a user. if !org.IsOrganization() { ctx.Redirect(setting.AppSubURL + "/" + org.Name) diff --git a/modules/context/repo.go b/modules/context/repo.go index 4da6e9a16..64f02c921 100755 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -402,6 +402,7 @@ func RepoAssignment() macaron.Handler { } ctx.Repo.Owner = owner ctx.Data["Username"] = ctx.Repo.Owner.Name + ctx.Data["IsCourse"] = owner.Name == setting.Course.OrgName // Get repository. repo, err := models.GetRepositoryByName(owner.ID, repoName) diff --git a/modules/modelarts/modelarts.go b/modules/modelarts/modelarts.go index 3f7ebfd91..301c4cb0e 100755 --- a/modules/modelarts/modelarts.go +++ b/modules/modelarts/modelarts.go @@ -17,6 +17,7 @@ const ( //notebook storageTypeOBS = "obs" autoStopDuration = 4 * 60 * 60 + autoStopDurationMs = 4 * 60 * 60 * 1000 DataSetMountPath = "/home/ma-user/work" NotebookEnv = "Python3" @@ -38,6 +39,7 @@ const ( // "]}" CodePath = "/code/" OutputPath = "/output/" + ResultPath = "/result/" LogPath = "/log/" JobPath = "/job/" OrderDesc = "desc" //向下查询 @@ -45,6 +47,8 @@ const ( Lines = 500 TrainUrl = "train_url" DataUrl = "data_url" + ResultUrl = "result_url" + CkptUrl = "ckpt_url" PerPage = 10 IsLatestVersion = "1" NotLatestVersion = "0" @@ -113,6 +117,36 @@ type GenerateTrainJobVersionReq struct { TotalVersionCount int } +type GenerateInferenceJobReq struct { + JobName string + Uuid string + Description string + CodeObsPath string + BootFile string + BootFileUrl string + DataUrl string + TrainUrl string + FlavorCode string + LogUrl string + PoolID string + WorkServerNumber int + EngineID int64 + Parameters []models.Parameter + CommitID string + Params string + BranchName string + FlavorName string + EngineName string + LabelName string + IsLatestVersion string + VersionCount int + TotalVersionCount int + ModelName string + ModelVersion string + CkptName string + ResultUrl string +} + type VersionInfo struct { Version []struct { ID int `json:"id"` @@ -229,6 +263,48 @@ func GenerateTask(ctx *context.Context, jobName, uuid, description, flavor strin return nil } +func GenerateNotebook2(ctx *context.Context, jobName, uuid, description, flavor string) error { + if poolInfos == nil { + json.Unmarshal([]byte(setting.PoolInfos), &poolInfos) + } + jobResult, err := createNotebook2(models.CreateNotebook2Params{ + JobName: jobName, + Description: description, + Flavor: flavor, + Duration: autoStopDurationMs, + ImageID: "59a6e9f5-93c0-44dd-85b0-82f390c5d53a", + PoolID: poolInfos.PoolInfo[0].PoolId, + Feature: models.NotebookFeature, + Volume: models.VolumeReq{ + Capacity: 100, + Category: models.EVSCategory, + Ownership: models.ManagedOwnership, + }, + WorkspaceID: "0", + }) + if err != nil { + log.Error("createNotebook2 failed: %v", err.Error()) + return err + } + err = models.CreateCloudbrain(&models.Cloudbrain{ + Status: string(models.JobWaiting), + UserID: ctx.User.ID, + RepoID: ctx.Repo.Repository.ID, + JobID: jobResult.ID, + JobName: jobName, + JobType: string(models.JobTypeDebug), + Type: models.TypeCloudBrainTwo, + Uuid: uuid, + ComputeResource: models.NPUResource, + }) + + if err != nil { + return err + } + + return nil +} + func GenerateTrainJob(ctx *context.Context, req *GenerateTrainJobReq) (err error) { jobResult, err := createTrainJob(models.CreateTrainJobParams{ JobName: req.JobName, @@ -329,12 +405,14 @@ func GenerateTrainJobVersion(ctx *context.Context, req *GenerateTrainJobReq, job return err } + var jobTypes []string + jobTypes = append(jobTypes, string(models.JobTypeTrain)) repo := ctx.Repo.Repository VersionTaskList, VersionListCount, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{ - RepoID: repo.ID, - Type: models.TypeCloudBrainTwo, - JobType: string(models.JobTypeTrain), - JobID: strconv.FormatInt(jobResult.JobID, 10), + RepoID: repo.ID, + Type: models.TypeCloudBrainTwo, + JobTypes: jobTypes, + JobID: strconv.FormatInt(jobResult.JobID, 10), }) if err != nil { ctx.ServerError("Cloudbrain", err) @@ -441,8 +519,82 @@ func TransTrainJobStatus(status int) string { } } -func GetVersionOutputPathByTotalVersionCount(TotalVersionCount int) (VersionOutputPath string) { +func GetOutputPathByCount(TotalVersionCount int) (VersionOutputPath string) { talVersionCountToString := fmt.Sprintf("%04d", TotalVersionCount) VersionOutputPath = "V" + talVersionCountToString return VersionOutputPath } + +func GenerateInferenceJob(ctx *context.Context, req *GenerateInferenceJobReq) (err error) { + jobResult, err := createInferenceJob(models.CreateInferenceJobParams{ + JobName: req.JobName, + Description: req.Description, + InfConfig: models.InfConfig{ + WorkServerNum: req.WorkServerNumber, + AppUrl: req.CodeObsPath, + BootFileUrl: req.BootFileUrl, + DataUrl: req.DataUrl, + EngineID: req.EngineID, + // TrainUrl: req.TrainUrl, + LogUrl: req.LogUrl, + PoolID: req.PoolID, + CreateVersion: true, + Flavor: models.Flavor{ + Code: req.FlavorCode, + }, + Parameter: req.Parameters, + }, + }) + if err != nil { + log.Error("CreateJob failed: %v", err.Error()) + return err + } + + attach, err := models.GetAttachmentByUUID(req.Uuid) + if err != nil { + log.Error("GetAttachmentByUUID(%s) failed:%v", strconv.FormatInt(jobResult.JobID, 10), err.Error()) + return err + } + + err = models.CreateCloudbrain(&models.Cloudbrain{ + Status: TransTrainJobStatus(jobResult.Status), + UserID: ctx.User.ID, + RepoID: ctx.Repo.Repository.ID, + JobID: strconv.FormatInt(jobResult.JobID, 10), + JobName: req.JobName, + JobType: string(models.JobTypeInference), + Type: models.TypeCloudBrainTwo, + VersionID: jobResult.VersionID, + VersionName: jobResult.VersionName, + Uuid: req.Uuid, + DatasetName: attach.Name, + CommitID: req.CommitID, + EngineID: req.EngineID, + TrainUrl: req.TrainUrl, + BranchName: req.BranchName, + Parameters: req.Params, + BootFile: req.BootFile, + DataUrl: req.DataUrl, + LogUrl: req.LogUrl, + FlavorCode: req.FlavorCode, + Description: req.Description, + WorkServerNumber: req.WorkServerNumber, + FlavorName: req.FlavorName, + EngineName: req.EngineName, + LabelName: req.LabelName, + IsLatestVersion: req.IsLatestVersion, + VersionCount: req.VersionCount, + TotalVersionCount: req.TotalVersionCount, + ModelName: req.ModelName, + ModelVersion: req.ModelVersion, + CkptName: req.CkptName, + ResultUrl: req.ResultUrl, + }) + + if err != nil { + log.Error("CreateCloudbrain(%s) failed:%v", req.JobName, err.Error()) + return err + } + + return nil +} diff --git a/modules/modelarts/resty.go b/modules/modelarts/resty.go index 07f26ceb7..afc5a14c0 100755 --- a/modules/modelarts/resty.go +++ b/modules/modelarts/resty.go @@ -28,6 +28,11 @@ const ( urlResourceSpecs = "/job/resource-specs" urlTrainJobConfig = "/training-job-configs" errorCodeExceedLimit = "ModelArts.0118" + + //notebook 2.0 + urlNotebook2 = "/notebooks" + + modelartsIllegalToken = "ModelArts.6401" ) func getRestyClient() *resty.Client { @@ -174,6 +179,50 @@ sendjob: return &result, nil } +func GetNotebook2(jobID string) (*models.GetNotebook2Result, error) { + checkSetting() + client := getRestyClient() + var result models.GetNotebook2Result + + retry := 0 + +sendjob: + res, err := client.R(). + SetHeader("Content-Type", "application/json"). + SetAuthToken(TOKEN). + SetResult(&result). + Get(HOST + "/v1/" + setting.ProjectID + urlNotebook2 + "/" + jobID) + + if err != nil { + return nil, fmt.Errorf("resty GetJob: %v", err) + } + + if res.StatusCode() == http.StatusUnauthorized && retry < 1 { + retry++ + _ = getToken() + goto sendjob + } + + var response models.NotebookResult + err = json.Unmarshal(res.Body(), &response) + if err != nil { + log.Error("json.Unmarshal failed: %s", err.Error()) + return &result, fmt.Errorf("son.Unmarshal failed: %s", err.Error()) + } + + if len(response.ErrorCode) != 0 { + log.Error("GetJob failed(%s): %s", response.ErrorCode, response.ErrorMsg) + if response.ErrorCode == modelartsIllegalToken && retry < 1 { + retry++ + _ = getToken() + goto sendjob + } + return &result, fmt.Errorf("GetJob failed(%s): %s", response.ErrorCode, response.ErrorMsg) + } + + return &result, nil +} + func ManageNotebook(jobID string, param models.NotebookAction) (*models.NotebookActionResult, error) { checkSetting() client := getRestyClient() @@ -214,6 +263,50 @@ sendjob: return &result, nil } +func ManageNotebook2(jobID string, param models.NotebookAction) (*models.NotebookActionResult, error) { + checkSetting() + client := getRestyClient() + var result models.NotebookActionResult + + retry := 0 + +sendjob: + res, err := client.R(). + SetHeader("Content-Type", "application/json"). + SetAuthToken(TOKEN). + SetResult(&result). + Post(HOST + "/v1/" + setting.ProjectID + urlNotebook2 + "/" + jobID + "/" + param.Action + "?duration=" + strconv.Itoa(autoStopDurationMs)) + + if err != nil { + return &result, fmt.Errorf("resty ManageNotebook2: %v", err) + } + + if res.StatusCode() == http.StatusUnauthorized && retry < 1 { + retry++ + _ = getToken() + goto sendjob + } + + var response models.NotebookResult + err = json.Unmarshal(res.Body(), &response) + if err != nil { + log.Error("json.Unmarshal failed: %s", err.Error()) + return &result, fmt.Errorf("son.Unmarshal failed: %s", err.Error()) + } + + if len(response.ErrorCode) != 0 { + log.Error("ManageNotebook2 failed(%s): %s", response.ErrorCode, response.ErrorMsg) + if response.ErrorCode == modelartsIllegalToken && retry < 1 { + retry++ + _ = getToken() + goto sendjob + } + return &result, fmt.Errorf("ManageNotebook2 failed(%s): %s", response.ErrorCode, response.ErrorMsg) + } + + return &result, nil +} + func DelNotebook(jobID string) (*models.NotebookDelResult, error) { checkSetting() client := getRestyClient() @@ -253,6 +346,50 @@ sendjob: return &result, nil } +func DelNotebook2(jobID string) (*models.NotebookDelResult, error) { + checkSetting() + client := getRestyClient() + var result models.NotebookDelResult + + retry := 0 + +sendjob: + res, err := client.R(). + SetHeader("Content-Type", "application/json"). + SetAuthToken(TOKEN). + SetResult(&result). + Delete(HOST + "/v1/" + setting.ProjectID + urlNotebook2 + "/" + jobID) + + if err != nil { + return &result, fmt.Errorf("resty DelJob: %v", err) + } + + if res.StatusCode() == http.StatusUnauthorized && retry < 1 { + retry++ + _ = getToken() + goto sendjob + } + + var response models.NotebookResult + err = json.Unmarshal(res.Body(), &response) + if err != nil { + log.Error("json.Unmarshal failed: %s", err.Error()) + return &result, fmt.Errorf("son.Unmarshal failed: %s", err.Error()) + } + + if len(response.ErrorCode) != 0 { + log.Error("DelNotebook2 failed(%s): %s", response.ErrorCode, response.ErrorMsg) + if response.ErrorCode == modelartsIllegalToken && retry < 1 { + retry++ + _ = getToken() + goto sendjob + } + return &result, fmt.Errorf("DelNotebook2 failed(%s): %s", response.ErrorCode, response.ErrorMsg) + } + + return &result, nil +} + func DelJob(jobID string) (*models.NotebookDelResult, error) { checkSetting() client := getRestyClient() @@ -874,3 +1011,107 @@ sendjob: return &result, nil } + +func createInferenceJob(createJobParams models.CreateInferenceJobParams) (*models.CreateTrainJobResult, error) { + checkSetting() + client := getRestyClient() + var result models.CreateTrainJobResult + + retry := 0 + +sendjob: + res, err := client.R(). + SetHeader("Content-Type", "application/json"). + SetAuthToken(TOKEN). + SetBody(createJobParams). + SetResult(&result). + Post(HOST + "/v1/" + setting.ProjectID + urlTrainJob) + + if err != nil { + return nil, fmt.Errorf("resty create inference-job: %s", err) + } + + req, _ := json.Marshal(createJobParams) + log.Info("%s", req) + + if res.StatusCode() == http.StatusUnauthorized && retry < 1 { + retry++ + _ = getToken() + goto sendjob + } + + if res.StatusCode() != http.StatusOK { + var temp models.ErrorResult + if err = json.Unmarshal([]byte(res.String()), &temp); err != nil { + log.Error("json.Unmarshal failed(%s): %v", res.String(), err.Error()) + return &result, fmt.Errorf("json.Unmarshal failed(%s): %v", res.String(), err.Error()) + } + log.Error("createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) + BootFileErrorMsg := "Invalid OBS path '" + createJobParams.InfConfig.BootFileUrl + "'." + DataSetErrorMsg := "Invalid OBS path '" + createJobParams.InfConfig.DataUrl + "'." + if temp.ErrorMsg == BootFileErrorMsg { + log.Error("启动文件错误!createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) + return &result, fmt.Errorf("启动文件错误!") + } + if temp.ErrorMsg == DataSetErrorMsg { + log.Error("数据集错误!createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) + return &result, fmt.Errorf("数据集错误!") + } + return &result, fmt.Errorf("createInferenceJob failed(%d):%s(%s)", res.StatusCode(), temp.ErrorCode, temp.ErrorMsg) + } + + if !result.IsSuccess { + log.Error("createInferenceJob failed(%s): %s", result.ErrorCode, result.ErrorMsg) + return &result, fmt.Errorf("createInferenceJob failed(%s): %s", result.ErrorCode, result.ErrorMsg) + } + + return &result, nil +} + +func createNotebook2(createJobParams models.CreateNotebook2Params) (*models.CreateNotebookResult, error) { + checkSetting() + client := getRestyClient() + var result models.CreateNotebookResult + + retry := 0 + +sendjob: + res, err := client.R(). + SetHeader("Content-Type", "application/json"). + SetAuthToken(TOKEN). + SetBody(createJobParams). + SetResult(&result). + Post(HOST + "/v1/" + setting.ProjectID + urlNotebook2) + + if err != nil { + return nil, fmt.Errorf("resty create notebook2: %s", err) + } + + if res.StatusCode() == http.StatusUnauthorized && retry < 1 { + retry++ + _ = getToken() + goto sendjob + } + + var response models.NotebookResult + err = json.Unmarshal(res.Body(), &response) + if err != nil { + log.Error("json.Unmarshal failed: %s", err.Error()) + return &result, fmt.Errorf("son.Unmarshal failed: %s", err.Error()) + } + + if len(response.ErrorCode) != 0 { + log.Error("createNotebook2 failed(%s): %s", response.ErrorCode, response.ErrorMsg) + if response.ErrorCode == errorCodeExceedLimit { + response.ErrorMsg = "所选规格使用数量已超过最大配额限制。" + } + if response.ErrorCode == modelartsIllegalToken && retry < 1 { + retry++ + _ = getToken() + goto sendjob + } + return &result, fmt.Errorf("createNotebook2 failed(%s): %s", response.ErrorCode, response.ErrorMsg) + } + + return &result, nil +} diff --git a/modules/notification/action/action.go b/modules/notification/action/action.go index 9956940f3..4bc296657 100644 --- a/modules/notification/action/action.go +++ b/modules/notification/action/action.go @@ -154,6 +154,22 @@ func (a *actionNotifier) NotifyRenameRepository(doer *models.User, repo *models. } } +func (a *actionNotifier) NotifyAliasRepository(doer *models.User, repo *models.Repository, oldAlias string) { + log.Trace("action.ChangeRepositoryAlias: %s/%s", doer.Name, repo.Alias) + + if err := models.NotifyWatchers(&models.Action{ + ActUserID: doer.ID, + ActUser: doer, + OpType: models.ActionRenameRepo, + RepoID: repo.ID, + Repo: repo, + IsPrivate: repo.IsPrivate, + Content: oldAlias, + }); err != nil { + log.Error("NotifyWatchers: %v", err) + } +} + func (a *actionNotifier) NotifyTransferRepository(doer *models.User, repo *models.Repository, oldOwnerName string) { if err := models.NotifyWatchers(&models.Action{ ActUserID: doer.ID, diff --git a/modules/notification/base/notifier.go b/modules/notification/base/notifier.go index 0b3e1173b..8325f710c 100644 --- a/modules/notification/base/notifier.go +++ b/modules/notification/base/notifier.go @@ -18,6 +18,7 @@ type Notifier interface { NotifyDeleteRepository(doer *models.User, repo *models.Repository) NotifyForkRepository(doer *models.User, oldRepo, repo *models.Repository) NotifyRenameRepository(doer *models.User, repo *models.Repository, oldRepoName string) + NotifyAliasRepository(doer *models.User, repo *models.Repository, oldAlias string) NotifyTransferRepository(doer *models.User, repo *models.Repository, oldOwnerName string) NotifyNewIssue(*models.Issue) diff --git a/modules/notification/base/null.go b/modules/notification/base/null.go index d2fd51d71..a74c47980 100644 --- a/modules/notification/base/null.go +++ b/modules/notification/base/null.go @@ -135,6 +135,10 @@ func (*NullNotifier) NotifyDeleteRef(doer *models.User, repo *models.Repository, func (*NullNotifier) NotifyRenameRepository(doer *models.User, repo *models.Repository, oldRepoName string) { } +func (a *NullNotifier) NotifyAliasRepository(doer *models.User, repo *models.Repository, oldAlias string) { + +} + // NotifyTransferRepository places a place holder function func (*NullNotifier) NotifyTransferRepository(doer *models.User, repo *models.Repository, oldOwnerName string) { } diff --git a/modules/repository/create.go b/modules/repository/create.go index d740c58b1..0844c43c3 100644 --- a/modules/repository/create.go +++ b/modules/repository/create.go @@ -22,12 +22,17 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m Limit: u.MaxRepoCreation, } } + var RepoType = models.RepoNormal + if opts.IsCourse { + RepoType = models.RepoCourse + } repo := &models.Repository{ OwnerID: u.ID, Owner: u, OwnerName: u.Name, Name: opts.Name, + Alias: opts.Alias, LowerName: strings.ToLower(opts.Name), Description: opts.Description, OriginalURL: opts.OriginalURL, @@ -37,10 +42,15 @@ func CreateRepository(doer, u *models.User, opts models.CreateRepoOptions) (_ *m CloseIssuesViaCommitInAnyBranch: setting.Repository.DefaultCloseIssuesViaCommitsInAnyBranch, Status: opts.Status, IsEmpty: !opts.AutoInit, + RepoType: RepoType, + Topics: opts.Topics, } err = models.WithTx(func(ctx models.DBContext) error { - if err = models.CreateRepository(ctx, doer, u, repo); err != nil { + if err = models.CreateRepository(ctx, doer, u, repo, opts); err != nil { + return err + } + if err = models.SaveTopics(repo.ID, opts.Topics...); err != nil { return err } diff --git a/modules/repository/fork.go b/modules/repository/fork.go index 2ed2a0eb7..da9039d00 100644 --- a/modules/repository/fork.go +++ b/modules/repository/fork.go @@ -15,7 +15,7 @@ import ( ) // ForkRepository forks a repository -func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, desc string) (_ *models.Repository, err error) { +func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, desc, alias string) (_ *models.Repository, err error) { forkedRepo, err := oldRepo.GetUserFork(owner.ID) if err != nil { return nil, err @@ -33,6 +33,7 @@ func ForkRepository(doer, owner *models.User, oldRepo *models.Repository, name, Owner: owner, OwnerName: owner.Name, Name: name, + Alias: alias, LowerName: strings.ToLower(name), Description: desc, DefaultBranch: oldRepo.DefaultBranch, diff --git a/modules/repository/fork_test.go b/modules/repository/fork_test.go index cb3526bcc..f599ead68 100644 --- a/modules/repository/fork_test.go +++ b/modules/repository/fork_test.go @@ -18,7 +18,7 @@ func TestForkRepository(t *testing.T) { user := models.AssertExistsAndLoadBean(t, &models.User{ID: 13}).(*models.User) repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: 10}).(*models.Repository) - fork, err := ForkRepository(user, user, repo, "test", "test") + fork, err := ForkRepository(user, user, repo, "test", "test", "test") assert.Nil(t, fork) assert.Error(t, err) assert.True(t, models.IsErrForkAlreadyExist(err)) diff --git a/modules/repository/generate.go b/modules/repository/generate.go index 6d80488de..86c9a5c28 100644 --- a/modules/repository/generate.go +++ b/modules/repository/generate.go @@ -236,6 +236,7 @@ func GenerateRepository(ctx models.DBContext, doer, owner *models.User, template Owner: owner, OwnerName: owner.Name, Name: opts.Name, + Alias: opts.Alias, LowerName: strings.ToLower(opts.Name), Description: opts.Description, IsPrivate: opts.Private, diff --git a/modules/repository/init.go b/modules/repository/init.go index 3d1c663c8..5dc352d65 100644 --- a/modules/repository/init.go +++ b/modules/repository/init.go @@ -51,7 +51,7 @@ func prepareRepoCommit(ctx models.DBContext, repo *models.Repository, tmpDir, re cloneLink := repo.CloneLink() match := map[string]string{ - "Name": repo.Name, + "Name": repo.DisplayName(), "Description": repo.Description, "CloneURL.SSH": cloneLink.SSH, "CloneURL.HTTPS": cloneLink.HTTPS, diff --git a/modules/setting/setting.go b/modules/setting/setting.go index e7ab0b7d2..c6828f9f7 100755 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -462,11 +462,15 @@ var ( MaxDuration int64 //benchmark config - IsBenchmarkEnabled bool - BenchmarkOwner string - BenchmarkName string - BenchmarkServerHost string - BenchmarkCategory string + IsBenchmarkEnabled bool + BenchmarkOwner string + BenchmarkName string + BenchmarkServerHost string + BenchmarkCategory string + BenchmarkTypes string + BenchmarkGpuTypes string + BenchmarkResourceSpecs string + BenchmarkMaxDuration int64 //snn4imagenet config IsSnn4imagenetEnabled bool @@ -508,6 +512,7 @@ var ( ProfileID string PoolInfos string Flavor string + DebugHost string //train-job ResourcePools string Engines string @@ -568,6 +573,11 @@ var ( }{} Warn_Notify_Mails []string + + Course = struct { + OrgName string + TeamName string + }{} ) // DateLang transforms standard language locale name to corresponding value in datetime plugin. @@ -1270,6 +1280,10 @@ func NewContext() { BenchmarkName = sec.Key("NAME").MustString("") BenchmarkServerHost = sec.Key("HOST").MustString("") BenchmarkCategory = sec.Key("CATEGORY").MustString("") + BenchmarkTypes = sec.Key("TYPES").MustString("") + BenchmarkGpuTypes = sec.Key("GPU_TYPES").MustString("") + BenchmarkResourceSpecs = sec.Key("RESOURCE_SPECS").MustString("") + BenchmarkMaxDuration = sec.Key("MAX_DURATION").MustInt64(14400) sec = Cfg.Section("snn4imagenet") IsSnn4imagenetEnabled = sec.Key("ENABLED").MustBool(false) @@ -1312,6 +1326,7 @@ func NewContext() { ProfileID = sec.Key("PROFILE_ID").MustString("") PoolInfos = sec.Key("POOL_INFOS").MustString("") Flavor = sec.Key("FLAVOR").MustString("") + DebugHost = sec.Key("DEBUG_SERVER_HOST").MustString("http://192.168.202.73") ResourcePools = sec.Key("Resource_Pools").MustString("") Engines = sec.Key("Engines").MustString("") EngineVersions = sec.Key("Engine_Versions").MustString("") @@ -1331,6 +1346,11 @@ func NewContext() { sec = Cfg.Section("warn_mail") Warn_Notify_Mails = strings.Split(sec.Key("mails").MustString(""), ",") + + sec = Cfg.Section("course") + Course.OrgName = sec.Key("org_name").MustString("") + Course.TeamName = sec.Key("team_name").MustString("") + } func SetRadarMapConfig() { diff --git a/modules/storage/obs.go b/modules/storage/obs.go index 367ffe1e8..8e6b4201b 100755 --- a/modules/storage/obs.go +++ b/modules/storage/obs.go @@ -28,6 +28,13 @@ type FileInfo struct { ParenDir string `json:"ParenDir"` UUID string `json:"UUID"` } +type FileInfoList []FileInfo + +func (ulist FileInfoList) Swap(i, j int) { ulist[i], ulist[j] = ulist[j], ulist[i] } +func (ulist FileInfoList) Len() int { return len(ulist) } +func (ulist FileInfoList) Less(i, j int) bool { + return strings.Compare(ulist[i].FileName, ulist[j].FileName) > 0 +} //check if has the object func ObsHasObject(path string) (bool, error) { @@ -50,8 +57,8 @@ func ObsHasObject(path string) (bool, error) { return hasObject, nil } -func GetObsPartInfos(uuid string, uploadID string) (string, error) { - key := strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid, uuid)), "/") +func GetObsPartInfos(uuid, uploadID, fileName string) (string, error) { + key := strings.TrimPrefix(path.Join(setting.BasePath, path.Join(uuid[0:1], uuid[1:2], uuid, fileName)), "/") output, err := ObsCli.ListParts(&obs.ListPartsInput{ Bucket: setting.Bucket, @@ -333,7 +340,8 @@ func GetAllObjectByBucketAndPrefix(bucket string, prefix string) ([]FileInfo, er input.MaxKeys = 100 input.Prefix = prefix index := 1 - fileInfos := make([]FileInfo, 0) + fileInfoList := FileInfoList{} + prefixLen := len(prefix) log.Info("prefix=" + input.Prefix) for { @@ -358,7 +366,7 @@ func GetAllObjectByBucketAndPrefix(bucket string, prefix string) ([]FileInfo, er IsDir: isDir, ParenDir: "", } - fileInfos = append(fileInfos, fileInfo) + fileInfoList = append(fileInfoList, fileInfo) } if output.IsTruncated { input.Marker = output.NextMarker @@ -373,13 +381,14 @@ func GetAllObjectByBucketAndPrefix(bucket string, prefix string) ([]FileInfo, er return nil, err } } - return fileInfos, nil + sort.Sort(fileInfoList) + return fileInfoList, nil } -func GetObsListObject(jobName, parentDir, versionName string) ([]FileInfo, error) { +func GetObsListObject(jobName, outPutPath, parentDir, versionName string) ([]FileInfo, error) { input := &obs.ListObjectsInput{} input.Bucket = setting.Bucket - input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, setting.OutPutPath, versionName, parentDir), "/") + input.Prefix = strings.TrimPrefix(path.Join(setting.TrainJobModelPath, jobName, outPutPath, versionName, parentDir), "/") strPrefix := strings.Split(input.Prefix, "/") output, err := ObsCli.ListObjects(input) fileInfos := make([]FileInfo, 0) @@ -401,7 +410,7 @@ func GetObsListObject(jobName, parentDir, versionName string) ([]FileInfo, error nextParentDir = parentDir + "/" + fileName } - if fileName == strPrefix[len(strPrefix)-1] || (fileName+"/") == setting.OutPutPath { + if fileName == strPrefix[len(strPrefix)-1] || (fileName+"/") == outPutPath { continue } } else { diff --git a/modules/structs/repo.go b/modules/structs/repo.go index 70de9b746..6e9ece4b0 100755 --- a/modules/structs/repo.go +++ b/modules/structs/repo.go @@ -46,31 +46,33 @@ type ExternalWiki struct { // Repository represents a repository type Repository struct { - ID int64 `json:"id"` - Owner *User `json:"owner"` - Name string `json:"name"` - FullName string `json:"full_name"` - Description string `json:"description"` - Empty bool `json:"empty"` - Private bool `json:"private"` - Fork bool `json:"fork"` - Template bool `json:"template"` - Parent *Repository `json:"parent"` - Mirror bool `json:"mirror"` - Size int `json:"size"` - HTMLURL string `json:"html_url"` - SSHURL string `json:"ssh_url"` - CloneURL string `json:"clone_url"` - OriginalURL string `json:"original_url"` - Website string `json:"website"` - Stars int `json:"stars_count"` - Forks int `json:"forks_count"` - Watchers int `json:"watchers_count"` - OpenIssues int `json:"open_issues_count"` - OpenPulls int `json:"open_pr_counter"` - Releases int `json:"release_counter"` - DefaultBranch string `json:"default_branch"` - Archived bool `json:"archived"` + ID int64 `json:"id"` + Owner *User `json:"owner"` + Name string `json:"name"` + Alias string `json:"alias"` + FullName string `json:"full_name"` + FullDisplayName string `json:"full_display_name"` + Description string `json:"description"` + Empty bool `json:"empty"` + Private bool `json:"private"` + Fork bool `json:"fork"` + Template bool `json:"template"` + Parent *Repository `json:"parent"` + Mirror bool `json:"mirror"` + Size int `json:"size"` + HTMLURL string `json:"html_url"` + SSHURL string `json:"ssh_url"` + CloneURL string `json:"clone_url"` + OriginalURL string `json:"original_url"` + Website string `json:"website"` + Stars int `json:"stars_count"` + Forks int `json:"forks_count"` + Watchers int `json:"watchers_count"` + OpenIssues int `json:"open_issues_count"` + OpenPulls int `json:"open_pr_counter"` + Releases int `json:"release_counter"` + DefaultBranch string `json:"default_branch"` + Archived bool `json:"archived"` // swagger:strfmt date-time Created time.Time `json:"created_at"` // swagger:strfmt date-time @@ -98,6 +100,10 @@ type CreateRepoOption struct { // required: true // unique: true Name string `json:"name" binding:"Required;AlphaDashDot;MaxSize(100)"` + // Alias of the repository to create + // required: false + // unique: true + Alias string `json:"alias" binding:"AlphaDashDotChinese;MaxSize(100)"` // Description of the repository to create Description string `json:"description" binding:"MaxSize(255)"` // Whether the repository is private @@ -217,6 +223,7 @@ type MigrateRepoOption struct { UID int `json:"uid" binding:"Required"` // required: true RepoName string `json:"repo_name" binding:"Required"` + Alias string `json:"alias" binding:"Required"` Mirror bool `json:"mirror"` Private bool `json:"private"` Description string `json:"description"` diff --git a/modules/task/task.go b/modules/task/task.go index 72f111ecc..722e39bec 100644 --- a/modules/task/task.go +++ b/modules/task/task.go @@ -84,6 +84,7 @@ func CreateMigrateTask(doer, u *models.User, opts base.MigrateOptions) (*models. repo, err := repo_module.CreateRepository(doer, u, models.CreateRepoOptions{ Name: opts.RepoName, + Alias: opts.Alias, Description: opts.Description, OriginalURL: opts.OriginalURL, GitServiceType: opts.GitServiceType, diff --git a/modules/templates/helper.go b/modules/templates/helper.go index da278ba32..3d31b611c 100755 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -93,6 +93,7 @@ func NewFuncMap() []template.FuncMap { "TimeSince": timeutil.TimeSince, "TimeSinceUnix": timeutil.TimeSinceUnix, "TimeSinceUnix1": timeutil.TimeSinceUnix1, + "TimeSinceUnixShort": timeutil.TimeSinceUnixShort, "RawTimeSince": timeutil.RawTimeSince, "FileSize": base.FileSize, "PrettyNumber": base.PrettyNumber, @@ -342,6 +343,7 @@ func NewTextFuncMap() []texttmpl.FuncMap { "TimeSince": timeutil.TimeSince, "TimeSinceUnix": timeutil.TimeSinceUnix, "TimeSinceUnix1": timeutil.TimeSinceUnix1, + "TimeSinceUnixShort": timeutil.TimeSinceUnixShort, "RawTimeSince": timeutil.RawTimeSince, "DateFmtLong": func(t time.Time) string { return t.Format(time.RFC1123Z) diff --git a/modules/timeutil/since.go b/modules/timeutil/since.go index a7854ed91..b19cc6401 100755 --- a/modules/timeutil/since.go +++ b/modules/timeutil/since.go @@ -165,5 +165,8 @@ func htmlTimeSinceUnix(then, now TimeStamp, lang string) template.HTML { func TimeSinceUnix1(then TimeStamp) string { format := time.Unix(int64(then), 0).Format("2006-01-02 15:04:05") return format - +} +func TimeSinceUnixShort(then TimeStamp) string { + format := time.Unix(int64(then), 0).Format("2006-01-02") + return format } diff --git a/modules/validation/binding.go b/modules/validation/binding.go index 1c67878ea..d52919475 100644 --- a/modules/validation/binding.go +++ b/modules/validation/binding.go @@ -19,6 +19,8 @@ const ( // ErrGlobPattern is returned when glob pattern is invalid ErrGlobPattern = "GlobPattern" + + ErrAlphaDashDotChinese = "AlphaDashDotChineseError" ) var ( @@ -26,6 +28,8 @@ var ( // They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere. // They cannot have question-mark ?, asterisk *, or open bracket [ anywhere GitRefNamePatternInvalid = regexp.MustCompile(`[\000-\037\177 \\~^:?*[]+`) + + AlphaDashDotChinese = regexp.MustCompile("^[\u4e00-\u9fa5\\.\\-_A-Za-z0-9]+$") ) // CheckGitRefAdditionalRulesValid check name is valid on additional rules @@ -53,6 +57,7 @@ func AddBindingRules() { addGitRefNameBindingRule() addValidURLBindingRule() addGlobPatternRule() + addAlphaDashDotChineseRule() } func addGitRefNameBindingRule() { @@ -117,6 +122,24 @@ func addGlobPatternRule() { }) } +func addAlphaDashDotChineseRule() { + binding.AddRule(&binding.Rule{ + IsMatch: func(rule string) bool { + return strings.HasPrefix(rule, "AlphaDashDotChinese") + }, + IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { + if val == "" { + return true, errs + } + if !ValidAlphaDashDotChinese(fmt.Sprintf("%v", val)) { + errs.Add([]string{name}, ErrAlphaDashDotChinese, "ErrAlphaDashDotChinese") + return false, errs + } + return true, errs + }, + }) +} + func portOnly(hostport string) string { colon := strings.IndexByte(hostport, ':') if colon == -1 { @@ -139,3 +162,7 @@ func validPort(p string) bool { } return true } + +func ValidAlphaDashDotChinese(value string) bool { + return AlphaDashDotChinese.MatchString(value) +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini old mode 100644 new mode 100755 index d07e048bf..b3ede3b06 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -50,6 +50,8 @@ repository = Repository organization = Organization mirror = Mirror new_repo = New Repository +new_course=Publish Course +course_desc = Course Description new_migrate = New Migration new_dataset = New Dataset edit_dataset = Edit Dataset @@ -219,46 +221,49 @@ show_only_public = Showing only public issues.in_your_repos = In your repositories contributors = Contributors +contributor = Contributor page_title=Explore Better AI -page_small_title=OpenI AI development cooperation platform +page_small_title=OpenI AI Development Cooperation Platform page_description=The one-stop collaborative development environment for AI field provides AI development pipeline integrating code development, data management, model debugging, reasoning and evaluation page_use=Use Now -page_only_dynamic=Show only open source project dynamics -page_recommend_org=Recommended organization -page_recommend_org_desc=These excellent organizations are using Qizhi AI to develop collaboration platforms; Your organization also wants to show here, -page_recommend_org_commit=Click here to submit -page_recommend_org_more=More organizations -page_recommend_repo=Recommended projects -page_recommend_repo_desc=Excellent AI project recommendation; Your project also wants to show here, -page_recommend_repo_commit=Click here to submit -page_recommend_repo_go=. Click here -page_recommend_repo_more=Project Square -page_dev_env=Collaborative development environment -page_dev_env_desc=The biggest difference between Qizhi AI collaborative development platform and traditional git platform is that it provides a collaborative development environment for AI development -page_dev_env_desc_title=Unified management of development elements -page_dev_env_desc_desc=The platform provides four elements of AI development: unified management of model code, data set, model and execution environment -page_dev_env_desc1_title=Data collaboration and sharing -page_dev_env_desc1_desc=By uploading data sets in the project, many project members cooperate to complete data preprocessing; You can also establish a better model with community developers by setting the data as a public dataset -page_dev_env_desc2_title=Model management and sharing -page_dev_env_desc2_desc=Associate the model with the code version, adjust the model in different ways based on the code history version, and save the results; The trained model can be open and shared, so that more people can use the model to test and give feedback -page_dev_env_desc3_title=One configuration, multiple use -page_dev_env_desc3_desc=Provide execution environment sharing, one-time configuration and multiple use, reduce the threshold of model development, and avoid spending repeated time configuring complex environments -page_dev_yunlao=PengCheng Cloudbrain open source collaboration -page_dev_yunlao_desc1=The platform has been connected with Pengcheng Cloudbrain and can use the rich computing resources of Pengcheng Cloudbrain to complete AI development tasks -page_dev_yunlao_desc2=Pengcheng Cloudbrain's existing AI computing power is 100p FLOPS@FP16 (billions of half precision floating-point calculations per second), the main hardware infrastructure is composed of GPU server equipped with NVIDIA Tesla V100 and Atlas 900 AI cluster equipped with Kunpeng and shengteng processors -page_dev_yunlao_desc3=Developers can freely choose the corresponding computing resources according to the use requirements, and can test the adaptability, performance and stability of the model in different hardware environments -page_dev_yunlao_desc4=If your model needs more computing resources, you can also apply separately -page_dev_yunlao_apply=Separate apply +page_only_dynamic=Only show the dynamics of open source projects +page_recommend_org=Recommended Organizations +page_recommend_org_desc=These excellent organizations are using the OpenI AI Collaboration Platform for collaborative development of projects. To show your organization here, +page_recommend_org_commit=Click here to submit. +page_recommend_org_more=More Organizations +page_recommend_repo=Recommended Projects +page_recommend_repo_desc=Excellent AI projects recommendation. To show your project here, +page_recommend_repo_commit=Click here to submit. +page_recommend_repo_go=Click here to +page_recommend_repo_more=explore more projects. +page_dev_env=Collaborative Development Environment +page_dev_env_desc=Provide a collaborative development environment for AI development, which is the biggest highlight that distinguishes the OpenI AI Collaboration Platform from other traditional Git platforms. +page_dev_env_desc_title=Unified Management of Development Elements +page_dev_env_desc_desc=The platform provides four elements of AI development: unified management of model code, data set, model and execution environment. +page_dev_env_desc1_title=Data Collaboration and Sharing +page_dev_env_desc1_desc=By uploading data sets in the project, many project members cooperate to complete data preprocessing. You can also establish a better model with community developers by setting the data as a public dataset. +page_dev_env_desc2_title=Model Management and Sharing +page_dev_env_desc2_desc=Associate the model with the code version, you can adjust the model in different ways based on the historical version of the code and save the results. The trained model can be open and shared, so that more people can use the model to test and give feedback. +page_dev_env_desc3_title=Once Configuration, Multiple Reuse +page_dev_env_desc3_desc=Provide execution environment sharing, Once Configuration, Multiple Reuse. Lower the threshold of model development, and avoid spending repetitive time configuring complex environments. +page_dev_yunlao=PengCheng Cloudbrain Open Source Collaboration +page_dev_yunlao_desc1=The platform has been connected with Pengcheng Cloudbrain and can use the rich computing resources of Pengcheng Cloudbrain to complete AI development tasks. +page_dev_yunlao_desc2=Pengcheng Cloudbrain's existing AI computing power is 100p FLOPS@FP16 (billions of half precision floating-point calculations per second), the main hardware infrastructure is composed of GPU server equipped with NVIDIA Tesla V100 and Atlas 900 AI cluster equipped with Kunpeng and Ascend processors. +page_dev_yunlao_desc3=Developers can freely choose the corresponding computing resources according to their needs, and can test the adaptability, performance, stability of the model in different hardware environments. +page_dev_yunlao_desc4=If your model requires more computing resources, you can also apply for it separately. +page_dev_yunlao_apply=Apply Separately [explore] repos = Repositories select_repos = Select the project users = Users organizations = Organizations -images = CloudImages +images = Cloudbrain Mirror search = Search +search_pro=Search projects code = Code +data_analysis=Digital Bulletin Board (test) repo_no_results = No matching repositories found. dataset_no_results = No matching datasets found. user_no_results = No matching users found. @@ -266,8 +271,8 @@ org_no_results = No matching organizations found. code_no_results = No source code matching your search term found. code_search_results = Search results for '%s' code_last_indexed_at = Last indexed %s -save=save -cancel=cancel +save=Save +cancel=Cancel [auth] create_new_account = Register Account @@ -321,7 +326,7 @@ openid_register_title = Create new account openid_register_desc = The chosen OpenID URI is unknown. Associate it with a new account here. openid_signin_desc = Enter your OpenID URI. For example: https://anne.me, bob.openid.org.cn or gnusocial.net/carry. disable_forgot_password_mail = Account recovery is disabled. Please contact your site administrator. -email_domain_blacklisted = You cannot register with your email address. +email_domain_blacklisted = You cannot register with this kind of email address. authorize_application = Authorize Application authorize_redirect_notice = You will be redirected to %s if you authorize this application. authorize_application_created_by = This application was created by %s. @@ -346,7 +351,12 @@ modify = Update [form] UserName = Username -RepoName = Repository name +Alias = Repository name +courseAlias = Course Name +courseAdress = Course Path +RepoPath = Repository path +RepoAdress = Repository Adress +course_Adress = Course Address Email = Email address Password = Password Retype = Re-Type Password @@ -370,7 +380,10 @@ SSPIDefaultLanguage = Default Language require_error = ` cannot be empty.` alpha_dash_error = ` should contain only alphanumeric, dash ('-') and underscore ('_') characters.` alpha_dash_dot_error = ` should contain only alphanumeric, dash ('-'), underscore ('_') and dot ('.') characters.` +reponame_dash_dot_error=` Please enter Chinese, alphanumeric, dash ('-') ,underscore ('_') and dot ('.')characters, up to 100 characters. ` +repoadd_dash_dot_error=` Path only allows input alphanumeric, dash ('-') ,underscore ('_') and dot ('.')characters, up to 100 characters. ` git_ref_name_error = ` must be a well-formed Git reference name.` +alpha_dash_dot_chinese_error= ` should contain only alphanumeric, chinese, dash ('-') and underscore ('_') characters.` size_error = ` must be size %s.` min_size_error = ` must contain at least %s characters.` max_size_error = ` must contain at most %s characters.` @@ -384,7 +397,8 @@ password_not_match = The passwords do not match. lang_select_error = Select a language from the list. username_been_taken = The username is already taken. -repo_name_been_taken = The repository name is already used. +repo_name_been_taken = The repository name or path is already used. +course_name_been_taken=The course name or path is already used. visit_rate_limit = Remote visit addressed rate limitation. 2fa_auth_required = Remote visit required two factors authentication. org_name_been_taken = The organization name is already taken. @@ -630,7 +644,7 @@ oauth2_application_create_description = OAuth2 applications gives your third-par oauth2_application_remove_description = Removing an OAuth2 application will prevent it to access authorized user accounts on this instance. Continue? authorized_oauth2_applications = Authorized OAuth2 Applications -authorized_oauth2_applications_description = You've granted access to your personal openi account to these third party applications. Please revoke access for applications no longer needed. +authorized_oauth2_applications_description = You have granted access to your personal openi account to these third party applications. Please revoke access for applications no longer needed. revoke_key = Revoke revoke_oauth2_grant = Revoke Access revoke_oauth2_grant_description = Revoking access for this third party application will prevent this application from accessing your data. Are you sure? @@ -789,6 +803,7 @@ generate_from = Generate From repo_desc = Description repo_lang = Language repo_gitignore_helper = Select .gitignore templates. +repo_label_helpe = Press Enter to complete issue_labels = Issue Labels issue_labels_helper = Select an issue label set. license = License @@ -797,6 +812,8 @@ readme = README readme_helper = Select a README file template. auto_init = Initialize Repository (Adds .gitignore, License and README) create_repo = Create Repository +create_course = Publish Course +failed_to_create_course=Fail to publish course, please try again later. default_branch = Default Branch mirror_prune = Prune mirror_prune_desc = Remove obsolete remote-tracking references @@ -860,6 +877,11 @@ get_repo_info_error=Can not get the information of the repository. generate_statistic_file_error=Fail to generate file. repo_stat_inspect=ProjectAnalysis all=All + +computing.all = All +computing.Introduction=Introduction +computing.success=Join Success + modelarts.status=Status modelarts.createtime=CreateTime modelarts.version_nums = Version Nums @@ -869,6 +891,7 @@ modelarts.notebook=Debug Task modelarts.train_job=Train Task modelarts.train_job.new_debug= New Debug Task modelarts.train_job.new_train=New Train Task +modelarts.train_job.new_infer=New Inference Task modelarts.train_job.config=Configuration information modelarts.train_job.new=New train Task modelarts.train_job.new_place=The description should not exceed 256 characters @@ -882,6 +905,8 @@ modelarts.parent_version=Parent Version modelarts.run_version=Run Version modelarts.train_job.compute_node=Compute Node modelarts.create_model = Create Model +modelarts.model_label=Model Label +modelarts.infer_dataset = Inference Dataset modelarts.train_job.basic_info=Basic Info @@ -894,7 +919,7 @@ modelarts.train_job.description=Description modelarts.train_job.parameter_setting=Parameter setting modelarts.train_job.parameter_setting_info=Parameter Info modelarts.train_job.fast_parameter_setting=fast_parameter_setting -modelarts.train_job.fast_parameter_setting_config=fast_parameter_setting_config +modelarts.train_job.fast_parameter_setting_config=fast_parameter_setting_config modelarts.train_job.fast_parameter_setting_config_link=fast_parameter_setting_config_link modelarts.train_job.frames=frames modelarts.train_job.algorithm_origin=Algorithm Origin @@ -928,6 +953,21 @@ modelarts.train_job_para_admin=train_job_para_admin modelarts.train_job_para.edit=train_job_para.edit modelarts.train_job_para.connfirm=train_job_para.connfirm + +modelarts.evaluate_job=Model Evaluation +modelarts.evaluate_job.new_job=New Model Evaluation +cloudbrain.benchmark.evaluate_type=Evaluation Type +cloudbrain.benchmark.evaluate_child_type=Child Type +cloudbrain.benchmark.evaluate_mirror=Mirror +cloudbrain.benchmark.evaluate_train=Train Script +cloudbrain.benchmark.evaluate_test=Test Script +modelarts.infer_job_model = Model +modelarts.infer_job_model_file = Model File +modelarts.infer_job = Inference Job +modelarts.infer_job.model_version = Model/Version +modelarts.infer_job.select_model = Select Model +modelarts.infer_job.tooltip = The model has been deleted and cannot be viewed. + model.manage.import_new_model=Import New Model model.manage.create_error=Equal Name and Version has existed. model.manage.model_name = Model Name @@ -954,14 +994,21 @@ template.avatar = Avatar template.issue_labels = Issue Labels template.one_item = Must select at least one template item template.invalid = Must select a template repository +template.repo_adress=Adress +template.repo_path=path +template.repo_name=Name archive.title = This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests. archive.issue.nocomment = This repo is archived. You cannot comment on issues. archive.pull.nocomment = This repo is archived. You cannot comment on pull requests. form.reach_limit_of_creation = You have already reached your limit of %d repositories. +form.reach_limit_of_course_creation=You have already reached your limit of %d courses or repositories. form.name_reserved = The repository name '%s' is reserved. +form.course_name_reserved=The course name '%s' is reserved. form.name_pattern_not_allowed = The pattern '%s' is not allowed in a repository name. +form.course_name_pattern_not_allowed=The pattern '%s' is not allowed in a course name. +add_course_org_fail=Fail to add organization, please try again later. need_auth = Clone Authorization migrate_type = Migration Type @@ -1174,7 +1221,7 @@ issues.filter_label_exclude = `Use alt + click/enter t issues.filter_label_no_select = All labels issues.filter_milestone = Milestone issues.filter_milestone_no_select = All milestones -issues.filter_milestone_no_add = Not add milestones +issues.filter_milestone_no_add = Not add milestones issues.filter_assignee = Assignee issues.filter_assginee_no_select = All assignees issues.filter_type = Type @@ -2006,6 +2053,7 @@ org_full_name_holder = Organization Full Name org_name_helper = Organization names should be short and memorable. create_org = Create Organization repo_updated = Updated +repo_released = Post home = Home people = People teams = Teams @@ -2022,6 +2070,14 @@ team_access_desc = Repository access team_permission_desc = Permission team_unit_desc = Allow Access to Repository Sections team_unit_disabled = (Disabled) +selected_couse=Selected Courses +release_course = Publish Course +all_keywords=All keywords +max_selectedPro= Select up to 9 public projects +custom_select_courses = Customize selected courses +recommend_remain_pro = Remain +save_fail_tips = The upper limit is exceeded +select_again = Select more than 9, please select again! form.name_reserved = The organization name '%s' is reserved. form.name_pattern_not_allowed = The pattern '%s' is not allowed in an organization name. @@ -2065,6 +2121,8 @@ members.remove = Remove members.leave = Leave members.invite_desc = Add a new member to %s: members.invite_now = Invite Now +course_members.remove = Remove +course_members.leave = Leave teams.join = Join teams.leave = Leave @@ -2107,6 +2165,7 @@ teams.all_repositories_helper = Team has access to all repositories. Selecting t teams.all_repositories_read_permission_desc = This team grants Read access to all repositories: members can view and clone repositories. teams.all_repositories_write_permission_desc = This team grants Write access to all repositories: members can read from and push to repositories. teams.all_repositories_admin_permission_desc = This team grants Admin access to all repositories: members can read from, push to and add collaborators to repositories. +teams.join_teams=Join in [admin] dashboard = Dashboard @@ -2687,6 +2746,7 @@ error.unit_not_allowed = You are not allowed to access this repository section. head.community = Community head.project = Repositories head.openi = OpenI +head.openi.repo = OpenI Projects head.dataset = Datasets foot.council = Council foot.technical_committee = Technical Committee diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 6dd44d848..86e88184c 100755 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -50,6 +50,8 @@ repository=项目 organization=组织 mirror=镜像 new_repo=创建项目 +new_course=发布课程 +course_desc=课程描述 new_dataset=创建数据集 new_migrate=迁移外部项目 edit_dataset = Edit Dataset @@ -221,6 +223,7 @@ show_only_public=只显示公开的 issues.in_your_repos=属于该用户项目的 contributors=贡献者 +contributor=贡献者 page_title=探索更好的AI page_small_title=启智AI开发协作平台 @@ -325,7 +328,7 @@ openid_register_title=创建新帐户 openid_register_desc=所选的 OpenID URI 未知。在这里关联一个新帐户。 openid_signin_desc=输入您的 OpenID URI。例如: https://anne.me、bob.openid.org.cn 或 gnusocial.net/carry。 disable_forgot_password_mail=帐户恢复功能已被禁用。请与网站管理员联系。 -email_domain_blacklisted=您不能使用您的电子邮件地址注册。 +email_domain_blacklisted=暂不支持此类电子邮件地址注册。 authorize_application=应用授权 authorize_redirect_notice=如果您授权此应用,您将会被重定向到 %s。 authorize_application_created_by=此应用由%s创建。 @@ -350,7 +353,13 @@ modify=更新 [form] UserName=用户名 -RepoName=项目名称 +RepoName=项目路径 +Alias=项目名称 +courseAlias=课程名称 +courseAdress=课程路径 +RepoPath=项目路径 +RepoAdress=项目地址 +course_Adress = 课程地址 Email=邮箱地址 Password=密码 Retype=重新输入密码 @@ -374,7 +383,10 @@ SSPIDefaultLanguage=默认语言 require_error=不能为空。 alpha_dash_error=应该只包含字母数字、破折号 ('-') 和下划线 ('_') 字符。 alpha_dash_dot_error=应该只包含字母数字, 破折号 ('-'), 下划线 ('_') 和点 ('. ') 。 +reponame_dash_dot_error=请输入中文、字母、数字和-_ .,最多100个字符。 +repoadd_dash_dot_error=路径只允许字母、数字和-_ .,最多100个字符。 git_ref_name_error=` 必须是格式良好的 git 引用名称。` +alpha_dash_dot_chinese_error=应该只包含字母数字中文, 破折号 ('-'), 下划线 ('_') 和点 ('. ') 。 size_error=长度必须为 %s。 min_size_error=长度最小为 %s 个字符。 max_size_error=长度最大为 %s 个字符。 @@ -388,7 +400,8 @@ password_not_match=密码不匹配。 lang_select_error=从列表中选出语言 username_been_taken=用户名已被使用。 -repo_name_been_taken=项目名称已被使用。 +repo_name_been_taken=项目名称或项目路径已被使用。 +course_name_been_taken=课程名称或地址已被使用。 visit_rate_limit=远程访问达到速度限制。 2fa_auth_required=远程访问需要双重验证。 org_name_been_taken=组织名称已被使用。 @@ -794,6 +807,7 @@ generate_from=生成自 repo_desc=项目描述 repo_lang=项目语言 repo_gitignore_helper=选择 .gitignore 模板。 +repo_label_helpe=输入完成后回车键完成标签确定。 issue_labels=任务标签 issue_labels_helper=选择一个任务标签集 license=授权许可 @@ -802,6 +816,8 @@ readme=自述 readme_helper=选择自述文件模板。 auto_init=初始化存储库 (添加. gitignore、许可证和自述文件) create_repo=创建项目 +create_course=发布课程 +failed_to_create_course=发布课程失败,请稍后再试。 default_branch=默认分支 mirror_prune=修剪 mirror_prune_desc=删除过时的远程跟踪引用 @@ -831,7 +847,7 @@ debug=调试 debug_again=再次调试 stop=停止 delete=删除 -model_download=模型下载 +model_download=结果下载 submit_image=提交镜像 download=模型下载 @@ -867,6 +883,10 @@ generate_statistic_file_error=生成文件失败。 repo_stat_inspect=项目分析 all=所有 +computing.all=全部 +computing.Introduction=简介 +computing.success=加入成功 + modelarts.status=状态 modelarts.createtime=创建时间 modelarts.version_nums=版本数 @@ -876,9 +896,10 @@ modelarts.notebook=调试任务 modelarts.train_job=训练任务 modelarts.train_job.new_debug=新建调试任务 modelarts.train_job.new_train=新建训练任务 +modelarts.train_job.new_infer=新建推理任务 modelarts.train_job.config=配置信息 modelarts.train_job.new=新建训练任务 -modelarts.train_job.new_place=描述字数不超过256个字符 +modelarts.train_job.new_place=描述字数不超过255个字符 modelarts.model_name=模型名称 modelarts.model_size=模型大小 modelarts.import_model=导入模型 @@ -888,6 +909,8 @@ modelarts.current_version=当前版本 modelarts.parent_version=父版本 modelarts.run_version=运行版本 modelarts.create_model=创建模型 +modelarts.model_label=模型标签 +modelarts.infer_dataset = 推理数据集 @@ -929,7 +952,7 @@ modelarts.train_job.NAS_mount_path=NAS挂载路径 modelarts.train_job.query_whether_save_parameter=保存作业参数 modelarts.train_job.save_helper=保存当前作业的配置参数,后续您可以使用已保存的配置参数快速创建训练作业。 modelarts.train_job.common_frame=常用框架 -modelarts.train_job.amount_of_compute_node=计算节点个数 +modelarts.train_job.amount_of_compute_node=计算节点数 modelarts.train_job.job_parameter_name=任务参数名称 modelarts.train_job.parameter_description=任务参数描述 modelarts.log=日志 @@ -938,6 +961,22 @@ modelarts.back=返回 modelarts.train_job_para_admin=任务参数管理 modelarts.train_job_para.edit=编辑 modelarts.train_job_para.connfirm=确定 +modelarts.evaluate_job=评测任务 +modelarts.evaluate_job.new_job=新建评测任务 +cloudbrain.benchmark.evaluate_type=评测类型 +cloudbrain.benchmark.evaluate_child_type=子类型 +cloudbrain.benchmark.evaluate_mirror=镜像 +cloudbrain.benchmark.evaluate_train=训练程序 +cloudbrain.benchmark.evaluate_test=测试程序 + + +modelarts.infer_job_model = 模型名称 +modelarts.infer_job_model_file = 模型文件 +modelarts.infer_job = 推理任务 +modelarts.infer_job.model_version = 模型/版本 +modelarts.infer_job.select_model = 选择模型 +modelarts.infer_job.boot_file_helper=启动文件是您程序执行的入口文件,必须是以.py结尾的文件。比如inference.py、main.py、example/inference.py、case/main.py。 +modelarts.infer_job.tooltip = 该模型已删除,无法查看。 model.manage.import_new_model=导入新模型 model.manage.create_error=相同的名称和版本的模型已经存在。 @@ -965,14 +1004,21 @@ template.avatar=头像 template.issue_labels=任务标签 template.one_item=必须至少选择一个模板项 template.invalid=必须选择一个模板项目 +template.repo_adress=项目地址 +template.repo_path=项目地址 +template.repo_name=项目名称 archive.title=此项目已存档。您可以查看文件和克隆,但不能推送或创建任务/合并请求。 archive.issue.nocomment=此项目已存档,您不能在此任务添加评论。 archive.pull.nocomment=此项目已存档,您不能在此合并请求添加评论。 form.reach_limit_of_creation=你已经达到了您的 %d 项目的限制。 +form.reach_limit_of_course_creation=你已经达到了您的 %d 课程的限制。 form.name_reserved=项目名称 '%s' 是被保留的。 +form.course_name_reserved=课程名称 '%s' 是被保留的。 form.name_pattern_not_allowed=项目名称中不允许使用模式 "%s"。 +form.course_name_pattern_not_allowed=课程名称中不允许使用模式 "%s"。 +add_course_org_fail=加入组织失败,请稍后重试。 need_auth=需要授权验证 migrate_type=迁移类型 @@ -2017,6 +2063,7 @@ org_full_name_holder=组织全名 org_name_helper=组织名字应该简单明了。 create_org=创建组织 repo_updated=最后更新于 +repo_released=发布于 home=组织主页 people=组织成员 teams=组织团队 @@ -2033,6 +2080,14 @@ team_access_desc=项目权限 team_permission_desc=权限 team_unit_desc=允许访问项目单元 team_unit_disabled=(已禁用) +selected_couse=精选课程 +release_course = 发布课程 +all_keywords=全部关键字 +max_selectedPro= 最多可选9个公开项目 +custom_select_courses = 自定义精选课程 +recommend_remain_pro = 还能推荐 +save_fail_tips = 最多可选9个,保存失败 +select_again = 选择超过9个,请重新选择! form.name_reserved=组织名称 '%s' 是被保留的。 form.name_pattern_not_allowed=组织名称中不允许使用 "%s"。 @@ -2076,6 +2131,8 @@ members.remove=移除成员 members.leave=离开组织 members.invite_desc=邀请新的用户加入 %s: members.invite_now=立即邀请 +course_members.remove=移除 +course_members.leave=离开 teams.join=加入团队 teams.leave=离开团队 @@ -2119,6 +2176,10 @@ teams.all_repositories_read_permission_desc=此团队授予读取修改所有项目的访问权限: 成员可以查看和推送至项目。 teams.all_repositories_admin_permission_desc=该团队拥有 管理 所有项目的权限:团队成员可以读取、克隆、推送以及添加其它项目协作者。 +teams.join_teams=加入该组织 + + + [admin] dashboard=管理面板 users=帐户管理 @@ -2696,6 +2757,7 @@ error.unit_not_allowed=您没有权限访问此项目单元 head.community=启智社区 head.project=项目 head.openi=启智项目 +head.openi.repo = 启智项目 head.dataset=数据集 foot.council=理事会 foot.technical_committee=技术委员会 diff --git a/public/home/home.js b/public/home/home.js index 92eb86c1d..f49c8248b 100644 --- a/public/home/home.js +++ b/public/home/home.js @@ -4,7 +4,6 @@ if(isEmpty(token)){ var meta = $("meta[name=_uid]"); if(!isEmpty(meta)){ token = meta.attr("content"); - console.log("token is uid:" + token); } } @@ -33,30 +32,30 @@ var swiperRepo = new Swiper(".homepro-list", { }); var output = document.getElementById("newmessage"); -var socket = new WebSocket("ws://" + document.location.host + "/action/notification"); +var url = "ws://" + document.location.host + "/action/notification"; +if(document.location.host == "git.openi.org.cn" || document.URL.startsWith("https")){ + url = "wss://" + document.location.host + "/action/notification" +} +var socket = new WebSocket(url); socket.onopen = function () { + messageQueue = []; console.log("message has connected."); }; -var messageQueue = []; var maxSize = 20; var html =document.documentElement; var lang = html.attributes["lang"] var isZh = true; if(lang != null && lang.nodeValue =="en-US" ){ - console.log("the language is " + lang.nodeValue); isZh=false; }else{ - console.log("default lang=zh"); } socket.onmessage = function (e) { var data =JSON.parse(e.data) - console.log("recevie data=" + e.data) var html = ""; if (data != null){ - console.log("queue length=" + messageQueue.length); if(messageQueue.length > maxSize){ delete messageQueue[0]; }else{ @@ -65,38 +64,38 @@ socket.onmessage = function (e) { var currentTime = new Date().getTime(); for(var i = 0; i < messageQueue.length; i++){ var record = messageQueue[i]; - + var recordPrefix = getMsg(record); var actionName = getAction(record.OpType,isZh); - + if(record.OpType == "6" || record.OpType == "10" || record.OpType == "12" || record.OpType == "13"){ html += recordPrefix + actionName; - html += " " + getIssueText(record) + "" + html += " " + getIssueText(record) + "" } else if(record.OpType == "7" || record.OpType == "11" || record.OpType == "14" || record.OpType == "15" || record.OpType == "22" || record.OpType == "23"){ html += recordPrefix + actionName; - html += " " + getPRText(record) + "" + html += " " + getPRText(record) + "" } else if(record.OpType == "1"){ html += recordPrefix + actionName; - html += " " + getRepoLink(record) + "" + html += " " +getRepotext(record) + "" } else if(record.OpType == "9" || record.OpType == "5"){ branch = "" + record.RefName + "" actionName = actionName.replace("{branch}",branch); html += recordPrefix + actionName; - html += " " + getRepoLink(record) + "" + html += " " + getRepotext(record) + "" }else if(record.OpType == "17"){ actionName = actionName.replace("{deleteBranchName}",record.RefName); - var repoLink = "" + getRepoLink(record) + "" + var repoLink = "" + getRepotext(record) + "" actionName = actionName.replace("{repoName}",repoLink); html += recordPrefix + actionName; } else if(record.OpType == "2"){ actionName = actionName.replace("{oldRepoName}",record.Content); html += recordPrefix + actionName; - html += " " + getRepoLink(record) + "" + html += " " + getRepotext(record) + "" } else{ continue; @@ -109,17 +108,8 @@ socket.onmessage = function (e) { html += ""; html += ""; } - /* -
- -
- zhoupzh 合并了合并请求 OpenI/aiforge#116822 分钟前 -
-
- */ } - console.log("html=" + html) output.innerHTML = html; swiperNewMessage.updateSlides(); swiperNewMessage.updateProgress(); @@ -130,15 +120,20 @@ function getMsg(record){ html += "
"; html += " \"\"" html += "
" - html += " " + record.ActUser.Name + "" + html += " " + record.ActUser.Name + "" return html; } -function getRepoLink(record){ - return "/" + record.Repo.OwnerName + "/" + record.Repo.Name; +function getRepotext(record){ + if(record.Repo.Alias){ + return record.Repo.OwnerName + "/" + record.Repo.Alias; + }else{ + return record.Repo.OwnerName + "/" + record.Repo.Name; + } } function getRepoLink(record){ return record.Repo.OwnerName + "/" + record.Repo.Name; + } function getTime(UpdatedUnix,currentTime){ @@ -148,8 +143,7 @@ function getTime(UpdatedUnix,currentTime){ if( timeEscSecond < 0){ timeEscSecond = 1; } - console.log("currentTime=" + currentTime + " updateUnix=" + UpdatedUnix); - + var hours= Math.floor(timeEscSecond / 3600); //计算相差分钟数 var leave2 = Math.floor(timeEscSecond % (3600)); //计算小时数后剩余的秒数 @@ -159,12 +153,12 @@ function getTime(UpdatedUnix,currentTime){ var seconds= leave3; if(hours == 0 && minutes == 0){ - return seconds + getRepoOrOrg(6,isZh); + return seconds + getRepoOrOrg(6,isZh,seconds); }else{ if(hours > 0){ - return hours + getRepoOrOrg(4,isZh); + return hours + getRepoOrOrg(4,isZh,hours); }else{ - return minutes + getRepoOrOrg(5,isZh); + return minutes + getRepoOrOrg(5,isZh,minutes); } } } @@ -173,11 +167,16 @@ function getPRLink(record){ return "/" + record.Repo.OwnerName + "/" + record.Repo.Name + "/pulls/" + getIssueId(record); } function getPRText(record){ - return record.Repo.OwnerName + "/" + record.Repo.Name + "#" + getIssueId(record); + if(record.Repo.Alias){ + return record.Repo.OwnerName + "/" + record.Repo.Alias + "#" + getIssueId(record); + }else{ + return record.Repo.OwnerName + "/" + record.Repo.Name + "#" + getIssueId(record); + } + } function getIssueLink(record){ - + return "/" + record.Repo.OwnerName + "/" + record.Repo.Name + "/issues/" + getIssueId(record); } @@ -198,7 +197,12 @@ function getIssueId(record){ } function getIssueText(record){ - return record.Repo.OwnerName + "/" + record.Repo.Name + "#" + getIssueId(record); + if(record.Repo.Alias){ + return record.Repo.OwnerName + "/" + record.Repo.Alias + "#" + getIssueId(record); + }else{ + return record.Repo.OwnerName + "/" + record.Repo.Name + "#" + getIssueId(record); + } + } /* @@ -233,7 +237,7 @@ var actionNameZH={ "5":"推送了 {branch} 分支的代码到", "6":"创建了任务", "7":"创建了合并请求", - "9":"推送了 {branch} 分支的代码到", + "9":"推送了标签 {branch} 到", "10":"评论了任务", "11":"合并了合并请求", "12":"关闭了任务", @@ -241,7 +245,7 @@ var actionNameZH={ "14":"关闭了合并请求", "15":"重新开启了合并请求", "17":"从 {repoName} 删除分支 {deleteBranchName}", - "22":"拒绝了合并请求", + "22":"建议变更", "23":"评论了合并请求" }; @@ -251,7 +255,7 @@ var actionNameEN={ "5":" pushed to {branch} at", "6":" opened issue", "7":" created pull request", - "9":" pushed to {branch} at", + "9":" pushed tag {branch} to ", "10":" commented on issue", "11":" merged pull request", "12":" closed issue", @@ -259,7 +263,7 @@ var actionNameEN={ "14":" closed pull request", "15":" reopened pull request", "17":" deleted branch {deleteBranchName} from {repoName}", - "22":" rejected pull request", + "22":" proposed changes", "23":" commented on pull request" }; @@ -267,18 +271,30 @@ var repoAndOrgZH={ "1":"项目", "2":"成员", "3":"团队", + "11":"项目", + "21":"成员", + "31":"团队", "4":"小时前", "5":"分钟前", - "6":"秒前" + "6":"秒前", + "41":"小时前", + "51":"分钟前", + "61":"秒前" }; var repoAndOrgEN={ - "1":"repository", - "2":"Members ", - "3":"Teams", - "4":" hours ago", - "5":" minutes ago", - "6":" seconds ago" + "1":"Repository", + "2":"Member ", + "3":"Team", + "11":"Repositories", + "21":"Members ", + "31":"Teams", + "4":" hour ago", + "5":" minute ago", + "6":" second ago", + "41":" hours ago", + "51":" minutes ago", + "61":" seconds ago" }; @@ -302,11 +318,9 @@ function queryRecommendData(){ dataType:"json", async:false, success:function(json){ - console.log(json); displayOrg(json); }, error:function(response) { - console.log(response); } }); @@ -319,40 +333,14 @@ function queryRecommendData(){ dataType:"json", async:false, success:function(json){ - console.log(json); displayRepo(json); }, error:function(response) { - console.log(response); } }); } -/* -
-
-
- - 276 32 - - - aiforge - -
- 本项目是群体化方法与技术的开源实现案例,在基于Gitea的基础上,进一步支持社交化的协同开发、协同学习、协同研究等群体创新实践服务,特别是针对新一代人工智能技术特点,重点支持项目管理、git代码管理、大数据集存储管理与智能计算平台接入。 -
- -
-
-
-*/ function displayRepo(json){ var orgRepo = document.getElementById("recommendrepo"); var html = ""; @@ -366,7 +354,7 @@ function displayRepo(json){ html += " " + record["NumStars"] + "" + record["NumForks"]; html += " "; html += " "; - html += " " + record["Name"] +""; + html += " " + record["Alias"] +""; html += "
" + record["Description"] + "
"; html += "
" if(record["Topics"] != null){ @@ -387,36 +375,17 @@ function displayRepo(json){ swiperRepo.updateProgress(); } -/** - * - *
-
-
-
- -
- OpenI 启智社区 -
39 项目 ・ 60 成员 ・ 23 团队
-
-
-
-
-
- - */ - -//var repoAndOrgZH = new Map([['1', "项目"], ['2', "成员"], ['3', "团队"]]); -//var repoAndOrgEN = new Map([['1', "Repository"], ['2', "Members"], ['3', "Teams"]]); - - -function getRepoOrOrg(key,isZhLang){ + +function getRepoOrOrg(key,isZhLang,numbers=1){ + if(numbers > 1){ + key+="1"; + } if(isZhLang){ return repoAndOrgZH[key]; }else{ return repoAndOrgEN[key]; } } - function displayOrg(json){ var orgDiv = document.getElementById("recommendorg"); var html = ""; @@ -430,7 +399,7 @@ function displayOrg(json){ html += " "; html += "
"; html += " " + record["Name"] + " " + record["FullName"]; - html += "
" + record["NumRepos"] +" " + getRepoOrOrg(1,isZh) + " ・ " + record["NumMembers"] +" " + getRepoOrOrg(2,isZh) + " ・ " + record["NumTeams"] + " " + getRepoOrOrg(3,isZh) + "
"; + html += "
" + record["NumRepos"] +" " + getRepoOrOrg(1,isZh,record["NumRepos"]) + " ・ " + record["NumMembers"] +" " + getRepoOrOrg(2,isZh,record["NumMembers"]) + " ・ " + record["NumTeams"] + " " + getRepoOrOrg(3,isZh,record["NumTeams"]) + "
"; html += "
"; html += "
"; html += "
"; @@ -439,4 +408,4 @@ function displayOrg(json){ } } orgDiv.innerHTML = html; -} \ No newline at end of file +} diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index dcea46ed6..72a68a6d3 100755 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -534,6 +534,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/downloadAllOpenI", repo.ServeAllProjectsOpenIStatisticsFile) m.Group("/project", func() { m.Get("", repo.GetAllProjectsPeriodStatistics) + m.Get("/numVisit", repo.ProjectNumVisit) m.Group("/:id", func() { m.Get("", repo.GetProjectLatestStatistics) @@ -878,10 +879,12 @@ func RegisterRoutes(m *macaron.Macaron) { }, reqAnyRepoReader()) m.Group("/cloudbrain", func() { m.Get("/:jobid", repo.GetCloudbrainTask) + m.Get("/:jobid/log", repo.CloudbrainGetLog) }, reqRepoReader(models.UnitTypeCloudBrain)) m.Group("/modelarts", func() { m.Group("/notebook", func() { - m.Get("/:jobid", repo.GetModelArtsNotebook) + //m.Get("/:jobid", repo.GetModelArtsNotebook) + m.Get("/:jobid", repo.GetModelArtsNotebook2) }) m.Group("/train-job", func() { m.Group("/:jobid", func() { @@ -892,6 +895,15 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/model_list", repo.ModelList) }) }) + m.Group("/inference-job", func() { + m.Group("/:jobid", func() { + m.Get("", repo.GetModelArtsInferenceJob) + m.Get("/log", repo.TrainJobGetLog) + m.Post("/del_version", repo.DelTrainJobVersion) + m.Post("/stop_version", repo.StopTrainJobVersion) + m.Get("/result_list", repo.ResultList) + }) + }) }, reqRepoReader(models.UnitTypeCloudBrain)) }, repoAssignment()) }) diff --git a/routers/api/v1/repo/cloudbrain.go b/routers/api/v1/repo/cloudbrain.go index bfba5236b..dd468783d 100755 --- a/routers/api/v1/repo/cloudbrain.go +++ b/routers/api/v1/repo/cloudbrain.go @@ -8,6 +8,7 @@ package repo import ( "code.gitea.io/gitea/modules/log" "net/http" + "sort" "time" "code.gitea.io/gitea/models" @@ -91,3 +92,59 @@ func GetCloudbrainTask(ctx *context.APIContext) { }) } + +func CloudbrainGetLog(ctx *context.Context) { + jobID := ctx.Params(":jobid") + _, err := models.GetCloudbrainByJobID(jobID) + if err != nil { + log.Error("GetCloudbrainByJobID failed: %v", err, ctx.Data["MsgID"]) + ctx.ServerError(err.Error(), err) + return + } + + var hits []models.Hits + result, err := cloudbrain.GetJobLog(jobID) + if err != nil{ + log.Error("GetJobLog failed: %v", err, ctx.Data["MsgID"]) + ctx.ServerError(err.Error(), err) + return + } + hits = result.Hits.Hits + + //if the size equal page_size, then take the scroll_id to get all log and delete the scroll_id(the num of scroll_id is limited) + if len(result.Hits.Hits) >= cloudbrain.LogPageSize { + for { + resultNext, err := cloudbrain.GetJobAllLog(result.ScrollID) + if err != nil{ + log.Error("GetJobAllLog failed: %v", err, ctx.Data["MsgID"]) + } else { + for _, hit := range resultNext.Hits.Hits { + hits = append(hits, hit) + } + } + + if len(resultNext.Hits.Hits) < cloudbrain.LogPageSize { + log.Info("get all log already") + break + } + } + } + + cloudbrain.DeleteJobLogToken(result.ScrollID) + + sort.Slice(hits, func(i, j int) bool { + return hits[i].Sort[0] < hits[j].Sort[0] + }) + + var content string + for _, log := range hits { + content += log.Source.Message + "\n" + } + + ctx.JSON(http.StatusOK, map[string]interface{}{ + "JobID": jobID, + "Content": content, + }) + + return +} diff --git a/routers/api/v1/repo/fork.go b/routers/api/v1/repo/fork.go index 3536b7f43..a753f192d 100644 --- a/routers/api/v1/repo/fork.go +++ b/routers/api/v1/repo/fork.go @@ -118,7 +118,7 @@ func CreateFork(ctx *context.APIContext, form api.CreateForkOption) { forker = org } - fork, err := repo_service.ForkRepository(ctx.User, forker, repo, repo.Name, repo.Description) + fork, err := repo_service.ForkRepository(ctx.User, forker, repo, repo.Name, repo.Description, repo.Alias) if err != nil { ctx.Error(http.StatusInternalServerError, "ForkRepository", err) return diff --git a/routers/api/v1/repo/modelarts.go b/routers/api/v1/repo/modelarts.go index 05c31b5f5..c9f8761c9 100755 --- a/routers/api/v1/repo/modelarts.go +++ b/routers/api/v1/repo/modelarts.go @@ -6,11 +6,12 @@ package repo import ( - "code.gitea.io/gitea/modules/util" "net/http" "strconv" "strings" + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" @@ -50,6 +51,37 @@ func GetModelArtsNotebook(ctx *context.APIContext) { } +func GetModelArtsNotebook2(ctx *context.APIContext) { + var ( + err error + ) + + jobID := ctx.Params(":jobid") + repoID := ctx.Repo.Repository.ID + job, err := models.GetRepoCloudBrainByJobID(repoID, jobID) + if err != nil { + ctx.NotFound(err) + return + } + result, err := modelarts.GetNotebook2(jobID) + if err != nil { + ctx.NotFound(err) + return + } + + job.Status = result.Status + err = models.UpdateJob(job) + if err != nil { + log.Error("UpdateJob failed:", err) + } + + ctx.JSON(http.StatusOK, map[string]interface{}{ + "JobID": jobID, + "JobStatus": result.Status, + }) + +} + func GetModelArtsTrainJob(ctx *context.APIContext) { var ( err error @@ -133,7 +165,6 @@ func TrainJobGetLog(ctx *context.APIContext) { var jobID = ctx.Params(":jobid") var versionName = ctx.Query("version_name") - // var logFileName = ctx.Query("file_name") var baseLine = ctx.Query("base_line") var order = ctx.Query("order") var lines = ctx.Query("lines") @@ -222,12 +253,14 @@ func DelTrainJobVersion(ctx *context.APIContext) { } //获取删除后的版本数量 + var jobTypes []string + jobTypes = append(jobTypes, string(models.JobTypeTrain)) repo := ctx.Repo.Repository VersionTaskList, VersionListCount, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{ - RepoID: repo.ID, - Type: models.TypeCloudBrainTwo, - JobType: string(models.JobTypeTrain), - JobID: jobID, + RepoID: repo.ID, + Type: models.TypeCloudBrainTwo, + JobTypes: jobTypes, + JobID: jobID, }) if err != nil { ctx.ServerError("get VersionListCount failed", err) @@ -299,7 +332,80 @@ func ModelList(ctx *context.APIContext) { log.Error("GetCloudbrainByJobID(%s) failed:%v", task.JobName, err.Error()) return } - models, err := storage.GetObsListObject(task.JobName, parentDir, versionName) + models, err := storage.GetObsListObject(task.JobName, "output/", parentDir, versionName) + if err != nil { + log.Info("get TrainJobListModel failed:", err) + ctx.ServerError("GetObsListObject:", err) + return + } + + ctx.JSON(http.StatusOK, map[string]interface{}{ + "JobID": jobID, + "VersionName": versionName, + "StatusOK": 0, + "Path": dirArray, + "Dirs": models, + "task": task, + "PageIsCloudBrain": true, + }) +} + +func GetModelArtsInferenceJob(ctx *context.APIContext) { + var ( + err error + ) + + jobID := ctx.Params(":jobid") + job, err := models.GetCloudbrainByJobID(jobID) + if err != nil { + ctx.NotFound(err) + return + } + result, err := modelarts.GetTrainJob(jobID, strconv.FormatInt(job.VersionID, 10)) + if err != nil { + ctx.NotFound(err) + return + } + + job.Status = modelarts.TransTrainJobStatus(result.IntStatus) + job.Duration = result.Duration + job.TrainJobDuration = result.TrainJobDuration + + if result.Duration != 0 { + job.TrainJobDuration = util.AddZero(result.Duration/3600000) + ":" + util.AddZero(result.Duration%3600000/60000) + ":" + util.AddZero(result.Duration%60000/1000) + + } else { + job.TrainJobDuration = "00:00:00" + } + + err = models.UpdateInferenceJob(job) + if err != nil { + log.Error("UpdateJob failed:", err) + } + + ctx.JSON(http.StatusOK, map[string]interface{}{ + "JobID": jobID, + "JobStatus": job.Status, + "JobDuration": job.TrainJobDuration, + }) + +} + +func ResultList(ctx *context.APIContext) { + var ( + err error + ) + + var jobID = ctx.Params(":jobid") + var versionName = ctx.Query("version_name") + parentDir := ctx.Query("parentDir") + dirArray := strings.Split(parentDir, "/") + task, err := models.GetCloudbrainByJobIDAndVersionName(jobID, versionName) + if err != nil { + log.Error("GetCloudbrainByJobID(%s) failed:%v", task.JobName, err.Error()) + return + } + models, err := storage.GetObsListObject(task.JobName, "result/", parentDir, versionName) if err != nil { log.Info("get TrainJobListModel failed:", err) ctx.ServerError("GetObsListObject:", err) diff --git a/routers/api/v1/repo/repo.go b/routers/api/v1/repo/repo.go index a724ebcc3..a85f88cb8 100755 --- a/routers/api/v1/repo/repo.go +++ b/routers/api/v1/repo/repo.go @@ -232,6 +232,7 @@ func CreateUserRepo(ctx *context.APIContext, owner *models.User, opt api.CreateR } repo, err := repo_service.CreateRepository(ctx.User, owner, models.CreateRepoOptions{ Name: opt.Name, + Alias: opt.Alias, Description: opt.Description, IssueLabels: opt.IssueLabels, Gitignores: opt.Gitignores, diff --git a/routers/api/v1/repo/repo_dashbord.go b/routers/api/v1/repo/repo_dashbord.go index a8887a744..46596330f 100644 --- a/routers/api/v1/repo/repo_dashbord.go +++ b/routers/api/v1/repo/repo_dashbord.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" @@ -640,3 +641,26 @@ func getTotalPage(total int64, pageSize int) int { return int(total)/pageSize + another } + +func ProjectNumVisit(ctx *context.APIContext) { + var ( + err error + ) + + var userName = ctx.Query("user") + var projectName = ctx.Query("project") + var beginTime = ctx.Query("begintime") + var endTime = ctx.Query("endtime") + + var ProjectNumVisits int + ProjectNumVisits, err = repository.AppointProjectView(userName, projectName, beginTime, endTime) //访问量 + if err != nil { + ctx.NotFound(err) + } + log.Info("ProjectNumVisits is:", ProjectNumVisits) + + ctx.JSON(http.StatusOK, map[string]interface{}{ + "ProjectNumVisits": ProjectNumVisits, + "StatusOK": 0, + }) +} diff --git a/routers/home.go b/routers/home.go index 24de1a10c..397e1990d 100755 --- a/routers/home.go +++ b/routers/home.go @@ -7,11 +7,11 @@ package routers import ( "bytes" - "fmt" - "io/ioutil" "net/http" "strings" + "code.gitea.io/gitea/services/repository" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -133,6 +133,7 @@ type RepoSearchOptions struct { Restricted bool PageSize int TplName base.TplName + Course util.OptionalBool } var ( @@ -211,6 +212,7 @@ func RenderRepoSearch(ctx *context.Context, opts *RepoSearchOptions) { AllLimited: true, TopicName: topic, IncludeDescription: setting.UI.SearchRepoDescription, + Course: opts.Course, }) if err != nil { ctx.ServerError("SearchRepository", err) @@ -559,7 +561,7 @@ func NotFound(ctx *context.Context) { func RecommendOrgFromPromote(ctx *context.Context) { url := setting.RecommentRepoAddr + "organizations" - result, err := recommendFromPromote(url) + result, err := repository.RecommendFromPromote(url) if err != nil { ctx.ServerError("500", err) return @@ -586,62 +588,11 @@ func RecommendOrgFromPromote(ctx *context.Context) { ctx.JSON(200, resultOrg) } -func recommendFromPromote(url string) ([]string, error) { - resp, err := http.Get(url) - if err != nil || resp.StatusCode != 200 { - log.Info("Get organizations url error=" + err.Error()) - return nil, err - } - bytes, err := ioutil.ReadAll(resp.Body) - resp.Body.Close() - if err != nil { - log.Info("Get organizations url error=" + err.Error()) - return nil, err - } - - allLineStr := string(bytes) - lines := strings.Split(allLineStr, "\n") - result := make([]string, len(lines)) - for i, line := range lines { - log.Info("i=" + fmt.Sprint(i) + " line=" + line) - result[i] = strings.Trim(line, " ") - } - return result, nil -} - func RecommendRepoFromPromote(ctx *context.Context) { - url := setting.RecommentRepoAddr + "projects" - result, err := recommendFromPromote(url) + result, err := repository.GetRecommendRepoFromPromote("projects") if err != nil { ctx.ServerError("500", err) - return - } - resultRepo := make([]map[string]interface{}, 0) - //resultRepo := make([]*models.Repository, 0) - for _, repoName := range result { - tmpIndex := strings.Index(repoName, "/") - if tmpIndex == -1 { - log.Info("error repo name format.") - } else { - ownerName := strings.Trim(repoName[0:tmpIndex], " ") - repoName := strings.Trim(repoName[tmpIndex+1:], " ") - repo, err := models.GetRepositoryByOwnerAndName(ownerName, repoName) - if err == nil { - repoMap := make(map[string]interface{}) - repoMap["ID"] = fmt.Sprint(repo.ID) - repoMap["Name"] = repo.Name - repoMap["OwnerName"] = repo.OwnerName - repoMap["NumStars"] = repo.NumStars - repoMap["NumForks"] = repo.NumForks - repoMap["Description"] = repo.Description - repoMap["NumWatchs"] = repo.NumWatches - repoMap["Topics"] = repo.Topics - repoMap["Avatar"] = repo.RelAvatarLink() - resultRepo = append(resultRepo, repoMap) - } else { - log.Info("query repo error," + err.Error()) - } - } + } else { + ctx.JSON(200, result) } - ctx.JSON(200, resultRepo) } diff --git a/routers/notice/notice.go b/routers/notice/notice.go index f1e996e2d..5a39ee684 100644 --- a/routers/notice/notice.go +++ b/routers/notice/notice.go @@ -16,27 +16,31 @@ const ( ) type Notice struct { - Title string - Link string - Visible int //0 invisible, 1 visible + Title string + Link string + Visible int //0 invisible, 1 visible +} + +type NoticeResponse struct { + Notices []*Notice CommitId string } var lock int32 = 0 -func GetNewestNotice() (*Notice, error) { +func GetNewestNotice() (*NoticeResponse, error) { defer func() { if err := recover(); err != nil { log.Error("recover error", err) } }() - var notice *Notice + var notice *NoticeResponse var err error if setting.CacheOn { - notice, err = getNewestNoticeFromCacheAndDisk() + notice, err = getNewestNoticesFromCacheAndDisk() } else { - notice, err = getNewestNoticeFromDisk() + notice, err = getNewestNoticesFromDisk() } if err != nil { @@ -49,34 +53,39 @@ func getNoticeTimeout() time.Duration { return time.Duration(setting.CacheTimeOutSecond) * time.Second } -func getNewestNoticeFromDisk() (*Notice, error) { +func getNewestNoticesFromDisk() (*NoticeResponse, error) { log.Debug("Get notice from disk") - repoFile, err := models.ReadLatestFileInRepo(setting.UserNameOfNoticeRepo, setting.RepoNameOfNoticeRepo, setting.RefNameOfNoticeRepo, setting.TreePathOfNoticeRepo) + repo, err := models.GetRepositoryByOwnerAndAlias(setting.UserNameOfNoticeRepo, setting.RepoNameOfNoticeRepo) + if err != nil { + log.Error("get notice repo failed, error=%v", err) + return nil, err + } + repoFile, err := models.ReadLatestFileInRepo(repo.OwnerName, repo.Name, setting.RefNameOfNoticeRepo, setting.TreePathOfNoticeRepo) if err != nil { log.Error("GetNewestNotice failed, error=%v", err) return nil, err } - notice := &Notice{} - json.Unmarshal(repoFile.Content, notice) - if notice.Title == "" { + res := &NoticeResponse{} + json.Unmarshal(repoFile.Content, res) + if res == nil || len(res.Notices) == 0 { return nil, err } - notice.CommitId = repoFile.CommitId - return notice, nil + res.CommitId = repoFile.CommitId + return res, nil } -func getNewestNoticeFromCacheAndDisk() (*Notice, error) { +func getNewestNoticesFromCacheAndDisk() (*NoticeResponse, error) { v, success := noticeCache.Get(NOTICE_CACHE_KEY) if success { log.Debug("Get notice from cache,value = %v", v) if v == nil { return nil, nil } - n := v.(*Notice) + n := v.(*NoticeResponse) return n, nil } - notice, err := getNewestNoticeFromDisk() + notice, err := getNewestNoticesFromDisk() if err != nil { log.Error("GetNewestNotice failed, error=%v", err) noticeCache.Set(NOTICE_CACHE_KEY, nil, 30*time.Second) diff --git a/routers/org/home.go b/routers/org/home.go index df600d96d..c9769f559 100755 --- a/routers/org/home.go +++ b/routers/org/home.go @@ -7,6 +7,10 @@ package org import ( "strings" + "code.gitea.io/gitea/services/repository" + + "code.gitea.io/gitea/modules/util" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/context" @@ -14,7 +18,8 @@ import ( ) const ( - tplOrgHome base.TplName = "org/home" + tplOrgHome base.TplName = "org/home" + tplOrgCourseHome base.TplName = "org/home_courses" ) // Home show organization home page @@ -59,10 +64,16 @@ func Home(ctx *context.Context) { case "fewestforks": orderBy = models.SearchOrderByForks default: - ctx.Data["SortType"] = "recentupdate" - orderBy = models.SearchOrderByRecentUpdated - } + if setting.Course.OrgName == org.Name { + ctx.Data["SortType"] = "newest" + orderBy = models.SearchOrderByNewest + } else { + ctx.Data["SortType"] = "recentupdate" + orderBy = models.SearchOrderByRecentUpdated + } + } + orderBy = orderBy + ",id" keyword := strings.Trim(ctx.Query("q"), " ") ctx.Data["Keyword"] = keyword @@ -76,9 +87,18 @@ func Home(ctx *context.Context) { count int64 err error ) + pageSize := setting.UI.User.RepoPagingNum + var CourseOptional util.OptionalBool = util.OptionalBoolNone + if setting.Course.OrgName == org.Name { + pageSize = 15 + recommendCourseKeyWords, _ := repository.GetRecommendCourseKeyWords() + ctx.Data["CoursesKeywords"] = recommendCourseKeyWords + + } + repos, count, err = models.SearchRepository(&models.SearchRepoOptions{ ListOptions: models.ListOptions{ - PageSize: setting.UI.User.RepoPagingNum, + PageSize: pageSize, Page: page, }, Keyword: keyword, @@ -87,6 +107,7 @@ func Home(ctx *context.Context) { Private: ctx.IsSigned, Actor: ctx.User, IncludeDescription: setting.UI.SearchRepoDescription, + Course: CourseOptional, }) if err != nil { ctx.ServerError("SearchRepository", err) @@ -132,11 +153,27 @@ func Home(ctx *context.Context) { //find org tag info tags, err := models.GetAllOfficialTagRepos(org.ID, ctx.Org.IsOwner) + + if setting.Course.OrgName == org.Name { + for _, tag := range tags { + for _, repo := range tag.RepoList { + repo.GetCreator() + repo.GetOwner() + } + } + + } + if err != nil { ctx.ServerError("GetAllOfficialTagRepos", err) return } ctx.Data["tags"] = tags - ctx.HTML(200, tplOrgHome) + if setting.Course.OrgName == org.Name { + ctx.HTML(200, tplOrgCourseHome) + } else { + ctx.HTML(200, tplOrgHome) + } + } diff --git a/routers/org/members.go b/routers/org/members.go index 9f13d1be3..39df692d2 100755 --- a/routers/org/members.go +++ b/routers/org/members.go @@ -17,7 +17,8 @@ import ( const ( // tplMembers template for organization members page - tplMembers base.TplName = "org/member/members" + tplMembers base.TplName = "org/member/members" + tplCourseMembers base.TplName = "org/member/course_members" ) // Members render organization users page @@ -64,8 +65,11 @@ func Members(ctx *context.Context) { ctx.Data["MembersIsPublicMember"] = membersIsPublic ctx.Data["MembersIsUserOrgOwner"] = members.IsUserOrgOwner(org.ID) ctx.Data["MembersTwoFaStatus"] = members.GetTwoFaStatus() - - ctx.HTML(200, tplMembers) + if setting.Course.OrgName == org.Name { + ctx.HTML(200, tplCourseMembers) + } else { + ctx.HTML(200, tplMembers) + } } // MembersAction response for operation to a member of organization diff --git a/routers/org/teams.go b/routers/org/teams.go index 03fbf068d..17c424578 100644 --- a/routers/org/teams.go +++ b/routers/org/teams.go @@ -10,6 +10,8 @@ import ( "path" "strings" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/base" @@ -22,7 +24,8 @@ import ( const ( // tplTeams template path for teams list page - tplTeams base.TplName = "org/team/teams" + tplTeams base.TplName = "org/team/teams" + tplCourseTeams base.TplName = "org/team/courseTeams" // tplTeamNew template path for create new team page tplTeamNew base.TplName = "org/team/new" // tplTeamMembers template path for showing team members page @@ -44,8 +47,12 @@ func Teams(ctx *context.Context) { } } ctx.Data["Teams"] = org.Teams + if setting.Course.OrgName == org.Name { + ctx.HTML(200, tplCourseTeams) + } else { + ctx.HTML(200, tplTeams) + } - ctx.HTML(200, tplTeams) } // TeamsAction response for join, leave, remove, add operations to team @@ -143,7 +150,7 @@ func TeamsRepoAction(ctx *context.Context) { case "add": repoName := path.Base(ctx.Query("repo_name")) var repo *models.Repository - repo, err = models.GetRepositoryByName(ctx.Org.Organization.ID, repoName) + repo, err = models.GetRepositoryByOwnerAndAlias(ctx.Org.Organization.Name, repoName) if err != nil { if models.IsErrRepoNotExist(err) { ctx.Flash.Error(ctx.Tr("org.teams.add_nonexistent_repo")) diff --git a/routers/repo/ai_model_manage.go b/routers/repo/ai_model_manage.go index 845dbbc6b..fe4d9794c 100644 --- a/routers/repo/ai_model_manage.go +++ b/routers/repo/ai_model_manage.go @@ -146,7 +146,8 @@ func SaveModel(ctx *context.Context) { if !trainTaskCreate { if !ctx.Repo.CanWrite(models.UnitTypeModelManage) { - ctx.ServerError("No right.", errors.New(ctx.Tr("repo.model_noright"))) + //ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + ctx.JSON(403, ctx.Tr("repo.model_noright")) return } } @@ -209,20 +210,11 @@ func DeleteModel(ctx *context.Context) { }) } } -func isCanDeleteOrDownload(ctx *context.Context, model *models.AiModelManage) bool { - if ctx.User.IsAdmin || ctx.User.ID == model.UserId { - return true - } - if ctx.Repo.IsOwner() { - return true - } - return false -} func deleteModelByID(ctx *context.Context, id string) error { log.Info("delete model start. id=" + id) model, err := models.QueryModelById(id) - if !isCanDeleteOrDownload(ctx, model) { + if !isCanDelete(ctx, model.UserId) { return errors.New(ctx.Tr("repo.model_noright")) } if err == nil { @@ -278,8 +270,8 @@ func DownloadMultiModelFile(ctx *context.Context) { ctx.ServerError("no such model:", err) return } - if !isCanDeleteOrDownload(ctx, task) { - ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright"))) + if !isOper(ctx, task.UserId) { + ctx.NotFound(ctx.Req.URL.RequestURI(), nil) return } @@ -371,7 +363,16 @@ func DownloadSingleModelFile(ctx *context.Context) { parentDir := ctx.Query("parentDir") fileName := ctx.Query("fileName") path := Model_prefix + models.AttachmentRelativePath(id) + "/" + parentDir + fileName - + task, err := models.QueryModelById(id) + if err != nil { + log.Error("no such model!", err.Error()) + ctx.ServerError("no such model:", err) + return + } + if !isOper(ctx, task.UserId) { + ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + return + } if setting.PROXYURL != "" { body, err := storage.ObsDownloadAFile(setting.Bucket, path) if err != nil { @@ -414,6 +415,8 @@ func ShowModelInfo(ctx *context.Context) { ctx.Data["ID"] = ctx.Query("ID") ctx.Data["name"] = ctx.Query("name") ctx.Data["isModelManage"] = true + ctx.Data["ModelManageAccess"] = ctx.Repo.CanWrite(models.UnitTypeModelManage) + ctx.HTML(200, tplModelInfo) } @@ -426,6 +429,7 @@ func ShowSingleModel(ctx *context.Context) { userIds := make([]int64, len(models)) for i, model := range models { model.IsCanOper = isOper(ctx, model.UserId) + model.IsCanDelete = isCanDelete(ctx, model.UserId) userIds[i] = model.UserId } userNameMap := queryUserName(userIds) @@ -468,6 +472,7 @@ func ShowOneVersionOtherModel(ctx *context.Context) { userIds := make([]int64, len(aimodels)) for i, model := range aimodels { model.IsCanOper = isOper(ctx, model.UserId) + model.IsCanDelete = isCanDelete(ctx, model.UserId) userIds[i] = model.UserId } userNameMap := queryUserName(userIds) @@ -487,8 +492,7 @@ func ShowOneVersionOtherModel(ctx *context.Context) { } } -func ShowModelTemplate(ctx *context.Context) { - ctx.Data["isModelManage"] = true +func SetModelCount(ctx *context.Context) { repoId := ctx.Repo.Repository.ID Type := -1 _, count, _ := models.QueryModel(&models.AiModelQueryOptions{ @@ -501,10 +505,15 @@ func ShowModelTemplate(ctx *context.Context) { New: MODEL_LATEST, }) ctx.Data["MODEL_COUNT"] = count +} +func ShowModelTemplate(ctx *context.Context) { + ctx.Data["isModelManage"] = true + repoId := ctx.Repo.Repository.ID + SetModelCount(ctx) + ctx.Data["ModelManageAccess"] = ctx.Repo.CanWrite(models.UnitTypeModelManage) _, trainCount, _ := models.QueryModelTrainJobList(repoId) log.Info("query train count=" + fmt.Sprint(trainCount)) - ctx.Data["TRAIN_COUNT"] = trainCount ctx.HTML(200, tplModelManageIndex) } @@ -520,11 +529,24 @@ func isQueryRight(ctx *context.Context) bool { } } +func isCanDelete(ctx *context.Context, modelUserId int64) bool { + if ctx.User == nil { + return false + } + if ctx.User.IsAdmin || ctx.User.ID == modelUserId { + return true + } + if ctx.Repo.IsOwner() { + return true + } + return false +} + func isOper(ctx *context.Context, modelUserId int64) bool { if ctx.User == nil { return false } - if ctx.User.IsAdmin || ctx.Repo.IsOwner() || ctx.User.ID == modelUserId { + if ctx.User.IsAdmin || ctx.User.ID == modelUserId { return true } return false @@ -533,7 +555,7 @@ func isOper(ctx *context.Context, modelUserId int64) bool { func ShowModelPageInfo(ctx *context.Context) { log.Info("ShowModelInfo start.") if !isQueryRight(ctx) { - ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright"))) + ctx.NotFound(ctx.Req.URL.RequestURI(), nil) return } page := ctx.QueryInt("page") @@ -563,6 +585,7 @@ func ShowModelPageInfo(ctx *context.Context) { userIds := make([]int64, len(modelResult)) for i, model := range modelResult { model.IsCanOper = isOper(ctx, model.UserId) + model.IsCanDelete = isCanDelete(ctx, model.UserId) userIds[i] = model.UserId } @@ -603,8 +626,9 @@ func ModifyModelInfo(ctx *context.Context) { ctx.ServerError("no such model:", err) return } - if !isCanDeleteOrDownload(ctx, task) { - ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright"))) + if !isOper(ctx, task.UserId) { + ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + //ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright"))) return } diff --git a/routers/repo/attachment.go b/routers/repo/attachment.go index 8443d6488..c2d096416 100755 --- a/routers/repo/attachment.go +++ b/routers/repo/attachment.go @@ -542,7 +542,7 @@ func GetSuccessChunks(ctx *context.Context) { log.Error("GetPartInfos failed:%v", err.Error()) } } else { - chunks, err = storage.GetObsPartInfos(fileChunk.UUID, fileChunk.UploadID) + chunks, err = storage.GetObsPartInfos(fileChunk.UUID, fileChunk.UploadID, fileName) if err != nil { log.Error("GetObsPartInfos failed:%v", err.Error()) } diff --git a/routers/repo/cloudbrain.go b/routers/repo/cloudbrain.go index ca1d08101..0599fb03f 100755 --- a/routers/repo/cloudbrain.go +++ b/routers/repo/cloudbrain.go @@ -28,15 +28,21 @@ import ( ) const ( - tplCloudBrainIndex base.TplName = "repo/cloudbrain/index" tplCloudBrainNew base.TplName = "repo/cloudbrain/new" tplCloudBrainShow base.TplName = "repo/cloudbrain/show" tplCloudBrainShowModels base.TplName = "repo/cloudbrain/models/index" + + tplCloudBrainBenchmarkIndex base.TplName = "repo/cloudbrain/benchmark/index" + tplCloudBrainBenchmarkNew base.TplName = "repo/cloudbrain/benchmark/new" + tplCloudBrainBenchmarkShow base.TplName = "repo/cloudbrain/benchmark/show" ) var ( - gpuInfos *models.GpuInfos - categories *models.Categories + gpuInfos *models.GpuInfos + categories *models.Categories + benchmarkTypes *models.BenchmarkTypes + benchmarkGpuInfos *models.GpuInfos + benchmarkResourceSpecs *models.ResourceSpecs ) var jobNamePattern = regexp.MustCompile(`^[a-z0-9][a-z0-9-_]{1,34}[a-z0-9-]$`) @@ -124,11 +130,28 @@ func cloudBrainNewDataPrepare(ctx *context.Context) error { } ctx.Data["benchmark_categories"] = categories.Category + if benchmarkTypes == nil { + if err := json.Unmarshal([]byte(setting.BenchmarkTypes), &benchmarkTypes); err != nil { + log.Error("json.Unmarshal BenchmarkTypes(%s) failed:%v", setting.BenchmarkTypes, err, ctx.Data["MsgID"]) + } + } + ctx.Data["benchmark_types"] = benchmarkTypes.BenchmarkType + if gpuInfos == nil { json.Unmarshal([]byte(setting.GpuTypes), &gpuInfos) } ctx.Data["gpu_types"] = gpuInfos.GpuInfo + if benchmarkGpuInfos == nil { + json.Unmarshal([]byte(setting.BenchmarkGpuTypes), &benchmarkGpuInfos) + } + ctx.Data["benchmark_gpu_types"] = benchmarkGpuInfos.GpuInfo + + if benchmarkResourceSpecs == nil { + json.Unmarshal([]byte(setting.BenchmarkResourceSpecs), &benchmarkResourceSpecs) + } + ctx.Data["benchmark_resource_specs"] = benchmarkResourceSpecs.ResourceSpec + if cloudbrain.ResourceSpecs == nil { json.Unmarshal([]byte(setting.ResourceSpecs), &cloudbrain.ResourceSpecs) } @@ -155,9 +178,9 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { ctx.Data["PageIsCloudBrain"] = true jobName := form.JobName image := form.Image - command := form.Command uuid := form.Attachment jobType := form.JobType + command := cloudbrain.Command gpuQueue := form.GpuType codePath := setting.JobPath + jobName + cloudbrain.CodeMountPath resourceSpecId := form.ResourceSpecId @@ -174,7 +197,7 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { return } - count, err := models.GetCloudbrainCountByUserID(ctx.User.ID) + count, err := models.GetCloudbrainCountByUserID(ctx.User.ID, jobType) if err != nil { log.Error("GetCloudbrainCountByUserID failed:%v", err, ctx.Data["MsgID"]) cloudBrainNewDataPrepare(ctx) @@ -238,12 +261,14 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { err = cloudbrain.GenerateTask(ctx, jobName, image, command, uuid, storage.GetMinioPath(jobName, cloudbrain.CodeMountPath+"/"), storage.GetMinioPath(jobName, cloudbrain.ModelMountPath+"/"), storage.GetMinioPath(jobName, cloudbrain.BenchMarkMountPath+"/"), storage.GetMinioPath(jobName, cloudbrain.Snn4imagenetMountPath+"/"), - storage.GetMinioPath(jobName, cloudbrain.BrainScoreMountPath+"/"), jobType, gpuQueue, resourceSpecId) + storage.GetMinioPath(jobName, cloudbrain.BrainScoreMountPath+"/"), jobType, gpuQueue, form.Description, + 0, 0, resourceSpecId) if err != nil { cloudBrainNewDataPrepare(ctx) ctx.RenderWithErr(err.Error(), tplCloudBrainNew, &form) return } + ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob?debugListType=all") } @@ -276,7 +301,7 @@ func CloudBrainRestart(ctx *context.Context) { break } - count, err := models.GetCloudbrainCountByUserID(ctx.User.ID) + count, err := models.GetCloudbrainCountByUserID(ctx.User.ID, string(models.JobTypeDebug)) if err != nil { log.Error("GetCloudbrainCountByUserID failed:%v", err, ctx.Data["MsgID"]) resultCode = "-1" @@ -310,7 +335,20 @@ func CloudBrainRestart(ctx *context.Context) { }) } +func CloudBrainBenchMarkShow(ctx *context.Context) { + if benchmarkTypes == nil { + if err := json.Unmarshal([]byte(setting.BenchmarkTypes), &benchmarkTypes); err != nil { + log.Error("json.Unmarshal BenchmarkTypes(%s) failed:%v", setting.BenchmarkTypes, err, ctx.Data["MsgID"]) + } + } + cloudBrainShow(ctx, tplCloudBrainBenchmarkShow) +} + func CloudBrainShow(ctx *context.Context) { + cloudBrainShow(ctx, tplCloudBrainShow) +} + +func cloudBrainShow(ctx *context.Context, tpName base.TplName) { ctx.Data["PageIsCloudBrain"] = true var jobID = ctx.Params(":jobid") @@ -327,6 +365,8 @@ func CloudBrainShow(ctx *context.Context) { if result != nil { jobRes, _ := models.ConvertToJobResultPayload(result.Payload) jobRes.Resource.Memory = strings.ReplaceAll(jobRes.Resource.Memory, "Mi", "MB") + spec := "GPU数:" + strconv.Itoa(jobRes.Resource.NvidiaComGpu) + ",CPU数:" + strconv.Itoa(jobRes.Resource.CPU) + ",内存(MB):" + jobRes.Resource.Memory + ctx.Data["resource_spec"] = spec taskRoles := jobRes.TaskRoles if jobRes.JobStatus.State != string(models.JobFailed) { taskRes, _ := models.ConvertToTaskPod(taskRoles[cloudbrain.SubTaskName].(map[string]interface{})) @@ -351,11 +391,43 @@ func CloudBrainShow(ctx *context.Context) { } ctx.Data["result"] = jobRes + } else { + log.Info("error:" + err.Error()) + } + user, err := models.GetUserByID(task.UserID) + if err == nil { + task.User = user + } + + var duration int64 + if task.Status == string(models.JobRunning) { + duration = time.Now().Unix() - int64(task.CreatedUnix) + } else { + duration = int64(task.UpdatedUnix) - int64(task.CreatedUnix) + } + if benchmarkTypes != nil { + for _, benchmarkType := range benchmarkTypes.BenchmarkType { + if task.BenchmarkTypeID == benchmarkType.Id { + ctx.Data["BenchmarkTypeName"] = benchmarkType.First + for _, benchmarkChildType := range benchmarkType.Second { + if task.BenchmarkChildTypeID == benchmarkChildType.Id { + ctx.Data["BenchmarkChildTypeName"] = benchmarkChildType.Value + break + } + } + break + } + } } + ctx.Data["duration"] = util.AddZero(duration/3600000) + ":" + util.AddZero(duration%3600000/60000) + ":" + util.AddZero(duration%60000/1000) ctx.Data["task"] = task ctx.Data["jobID"] = jobID - ctx.HTML(200, tplCloudBrainShow) + ctx.Data["jobName"] = task.JobName + version_list_task := make([]*models.Cloudbrain, 0) + version_list_task = append(version_list_task, task) + ctx.Data["version_list_task"] = version_list_task + ctx.HTML(200, tpName) } func CloudBrainDebug(ctx *context.Context) { @@ -393,7 +465,7 @@ func CloudBrainStop(ctx *context.Context) { task := ctx.Cloudbrain for { - if task.Status == string(models.JobStopped) || task.Status == string(models.JobFailed) { + if task.Status == string(models.JobStopped) || task.Status == string(models.JobFailed) || task.Status == string(models.JobSucceeded) { log.Error("the job(%s) has been stopped", task.JobName, ctx.Data["msgID"]) resultCode = "-1" errorMsg = "system error" @@ -510,22 +582,31 @@ func logErrorAndUpdateJobStatus(err error, taskInfo *models.Cloudbrain) { } func CloudBrainDel(ctx *context.Context) { + if err := deleteCloudbrainJob(ctx); err != nil { + log.Error("deleteCloudbrainJob failed: %v", err, ctx.Data["msgID"]) + ctx.ServerError(err.Error(), err) + return + } + ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob?debugListType=all") +} + +func deleteCloudbrainJob(ctx *context.Context) error { task := ctx.Cloudbrain - if task.Status != string(models.JobStopped) && task.Status != string(models.JobFailed) { + if task.Status != string(models.JobStopped) && task.Status != string(models.JobFailed) && task.Status != string(models.JobSucceeded) { log.Error("the job(%s) has not been stopped", task.JobName, ctx.Data["msgID"]) - ctx.ServerError("the job has not been stopped", errors.New("the job has not been stopped")) - return + return errors.New("the job has not been stopped") } err := models.DeleteJob(task) if err != nil { - ctx.ServerError("DeleteJob failed", err) - return + log.Error("DeleteJob failed: %v", err, ctx.Data["msgID"]) + return err } deleteJobStorage(task.JobName, models.TypeCloudBrainOne) - ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob?debugListType=all") + + return nil } func CloudBrainShowModels(ctx *context.Context) { @@ -632,6 +713,12 @@ func CloudBrainDownloadModel(ctx *context.Context) { } func GetRate(ctx *context.Context) { + isObjectDetcionAll := ctx.QueryBool("isObjectDetcionAll") + if isObjectDetcionAll { + ctx.Redirect(setting.BenchmarkServerHost + "?username=admin") + return + } + var jobID = ctx.Params(":jobid") job, err := models.GetCloudbrainByJobID(jobID) if err != nil { @@ -640,6 +727,7 @@ func GetRate(ctx *context.Context) { } if job.JobType == string(models.JobTypeBenchmark) { + log.Info("url=" + setting.BenchmarkServerHost + "?username=" + ctx.User.Name) ctx.Redirect(setting.BenchmarkServerHost + "?username=" + ctx.User.Name) } else if job.JobType == string(models.JobTypeSnn4imagenet) { ctx.Redirect(setting.Snn4imagenetServerHost) @@ -855,7 +943,14 @@ func SyncCloudbrainStatus() { log.Error("UpdateJob(%s) failed:%v", task.JobName, err) } - if task.Duration >= setting.MaxDuration { + var maxDuration int64 + if task.JobType == string(models.JobTypeBenchmark) { + maxDuration = setting.BenchmarkMaxDuration + } else { + maxDuration = setting.MaxDuration + } + + if task.Duration >= maxDuration { log.Info("begin to stop job(%s), because of the duration", task.JobName) err = cloudbrain.StopJob(task.JobID) if err != nil { @@ -872,7 +967,8 @@ func SyncCloudbrainStatus() { } } else if task.Type == models.TypeCloudBrainTwo { if task.JobType == string(models.JobTypeDebug) { - result, err := modelarts.GetJob(task.JobID) + //result, err := modelarts.GetJob(task.JobID) + result, err := modelarts.GetNotebook2(task.JobID) if err != nil { log.Error("GetJob(%s) failed:%v", task.JobName, err) continue @@ -923,3 +1019,329 @@ func SyncCloudbrainStatus() { return } + +func CloudBrainBenchmarkIndex(ctx *context.Context) { + MustEnableCloudbrain(ctx) + repo := ctx.Repo.Repository + page := ctx.QueryInt("page") + if page <= 0 { + page = 1 + } + + var jobTypes []string + jobTypes = append(jobTypes, string(models.JobTypeBenchmark)) + ciTasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{ + ListOptions: models.ListOptions{ + Page: page, + PageSize: setting.UI.IssuePagingNum, + }, + RepoID: repo.ID, + Type: models.TypeCloudBrainOne, + JobTypes: jobTypes, + }) + if err != nil { + ctx.ServerError("Get debugjob faild:", err) + return + } + + for i, task := range ciTasks { + ciTasks[i].CanDel = cloudbrain.CanDeleteJob(ctx, &task.Cloudbrain) + ciTasks[i].Cloudbrain.ComputeResource = task.ComputeResource + var duration int64 + if task.Status == string(models.JobRunning) { + duration = time.Now().Unix() - int64(task.Cloudbrain.CreatedUnix) + } else { + duration = int64(task.Cloudbrain.UpdatedUnix) - int64(task.Cloudbrain.CreatedUnix) + } + ciTasks[i].TrainJobDuration = util.AddZero(duration/3600000) + ":" + util.AddZero(duration%3600000/60000) + ":" + util.AddZero(duration%60000/1000) + } + + pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5) + + ctx.Data["Page"] = pager + ctx.Data["PageIsCloudBrain"] = true + ctx.Data["Tasks"] = ciTasks + ctx.Data["CanCreate"] = cloudbrain.CanCreateOrDebugJob(ctx) + ctx.Data["RepoIsEmpty"] = repo.IsEmpty + ctx.HTML(200, tplCloudBrainBenchmarkIndex) +} + +func GetChildTypes(ctx *context.Context) { + benchmarkTypeID := ctx.QueryInt("benchmark_type_id") + re := make(map[string]interface{}) + for { + if benchmarkTypes == nil { + if err := json.Unmarshal([]byte(setting.BenchmarkTypes), &benchmarkTypes); err != nil { + log.Error("json.Unmarshal BenchmarkTypes(%s) failed:%v", setting.BenchmarkTypes, err, ctx.Data["MsgID"]) + re["errMsg"] = "system error" + break + } + } + var isExist bool + for _, benchmarkType := range benchmarkTypes.BenchmarkType { + if benchmarkTypeID == benchmarkType.Id { + isExist = true + re["child_types"] = benchmarkType.Second + re["result_code"] = "0" + break + } + } + if !isExist { + re["result_code"] = "1" + log.Error("no such benchmark_type_id", ctx.Data["MsgID"]) + re["errMsg"] = "system error" + break + } + break + } + ctx.JSON(200, re) +} + +func CloudBrainBenchmarkNew(ctx *context.Context) { + err := cloudBrainNewDataPrepare(ctx) + if err != nil { + ctx.ServerError("get new cloudbrain info failed", err) + return + } + ctx.HTML(200, tplCloudBrainBenchmarkNew) +} + +func getBenchmarkAttachment(benchmarkTypeID, benchmarkChildTypeID int) (*models.BenchmarkDataset, error) { + var childInfo *models.BenchmarkDataset + if benchmarkTypes == nil { + if err := json.Unmarshal([]byte(setting.BenchmarkTypes), &benchmarkTypes); err != nil { + log.Error("json.Unmarshal BenchmarkTypes(%s) failed:%v", setting.BenchmarkTypes, err) + return childInfo, err + } + } + + var isExist bool + for _, benchmarkType := range benchmarkTypes.BenchmarkType { + if benchmarkType.Id == benchmarkTypeID { + for _, childType := range benchmarkType.Second { + if childType.Id == benchmarkChildTypeID { + childInfo = childType + isExist = true + break + } + } + break + } + } + + if !isExist { + log.Error("no such benchmark_type_id&benchmark_child_type_id") + return childInfo, errors.New("no such benchmark_type_id&benchmark_child_type_id") + } + + return childInfo, nil +} + +func getBenchmarkGpuQueue(gpuQueue string) (string, error) { + queue := "" + if benchmarkGpuInfos == nil { + if err := json.Unmarshal([]byte(setting.BenchmarkGpuTypes), &benchmarkGpuInfos); err != nil { + log.Error("json.Unmarshal BenchmarkGpuTypes(%s) failed:%v", setting.BenchmarkGpuTypes, err) + return queue, err + } + } + + var isExist bool + for _, gpuInfo := range benchmarkGpuInfos.GpuInfo { + if gpuQueue == gpuInfo.Queue { + isExist = true + queue = gpuQueue + break + } + } + + if !isExist { + log.Error("no such gpuQueue, %s", gpuQueue) + return queue, errors.New("no such gpuQueue") + } + + return queue, nil +} + +func getBenchmarkResourceSpec(resourceSpecID int) (int, error) { + var id int + if benchmarkResourceSpecs == nil { + if err := json.Unmarshal([]byte(setting.BenchmarkResourceSpecs), &benchmarkResourceSpecs); err != nil { + log.Error("json.Unmarshal BenchmarkResourceSpecs(%s) failed:%v", setting.BenchmarkResourceSpecs, err) + return id, err + } + } + + var isExist bool + for _, resourceSpec := range benchmarkResourceSpecs.ResourceSpec { + if resourceSpecID == resourceSpec.Id { + isExist = true + id = resourceSpecID + break + } + } + + if !isExist { + log.Error("no such resourceSpecID, %d", resourceSpecID) + return id, errors.New("no such resourceSpec") + } + + return id, nil +} + +func CloudBrainBenchmarkCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { + ctx.Data["PageIsCloudBrain"] = true + jobName := form.JobName + image := form.Image + gpuQueue := form.GpuType + command := cloudbrain.CommandBenchmark + codePath := setting.JobPath + jobName + cloudbrain.CodeMountPath + resourceSpecId := cloudbrain.BenchMarkResourceID + benchmarkTypeID := form.BenchmarkTypeID + benchmarkChildTypeID := form.BenchmarkChildTypeID + + if !jobNamePattern.MatchString(jobName) { + cloudBrainNewDataPrepare(ctx) + ctx.RenderWithErr(ctx.Tr("repo.cloudbrain_jobname_err"), tplCloudBrainBenchmarkNew, &form) + return + } + + childInfo, err := getBenchmarkAttachment(benchmarkTypeID, benchmarkChildTypeID) + if err != nil { + log.Error("getBenchmarkAttachment failed:%v", err, ctx.Data["MsgID"]) + cloudBrainNewDataPrepare(ctx) + ctx.RenderWithErr("benchmark type error", tplCloudBrainBenchmarkNew, &form) + return + } + + _, err = getBenchmarkGpuQueue(gpuQueue) + if err != nil { + log.Error("getBenchmarkGpuQueue failed:%v", err, ctx.Data["MsgID"]) + cloudBrainNewDataPrepare(ctx) + ctx.RenderWithErr("gpu queue error", tplCloudBrainBenchmarkNew, &form) + return + } + + _, err = getBenchmarkResourceSpec(resourceSpecId) + if err != nil { + log.Error("getBenchmarkResourceSpec failed:%v", err, ctx.Data["MsgID"]) + cloudBrainNewDataPrepare(ctx) + ctx.RenderWithErr("resource spec error", tplCloudBrainBenchmarkNew, &form) + return + } + + count, err := models.GetCloudbrainCountByUserID(ctx.User.ID, string(models.JobTypeBenchmark)) + if err != nil { + log.Error("GetCloudbrainCountByUserID failed:%v", err, ctx.Data["MsgID"]) + cloudBrainNewDataPrepare(ctx) + ctx.RenderWithErr("system error", tplCloudBrainBenchmarkNew, &form) + return + } else { + if count >= 1 { + log.Error("the user already has running or waiting task", ctx.Data["MsgID"]) + cloudBrainNewDataPrepare(ctx) + ctx.RenderWithErr("you have already a running or waiting task, can not create more", tplCloudBrainBenchmarkNew, &form) + return + } + } + + _, err = models.GetCloudbrainByName(jobName) + if err == nil { + log.Error("the job name did already exist", ctx.Data["MsgID"]) + cloudBrainNewDataPrepare(ctx) + ctx.RenderWithErr("the job name did already exist", tplCloudBrainBenchmarkNew, &form) + return + } else { + if !models.IsErrJobNotExist(err) { + log.Error("GetCloudbrainByName failed, %v", err, ctx.Data["MsgID"]) + cloudBrainNewDataPrepare(ctx) + ctx.RenderWithErr("system error", tplCloudBrainBenchmarkNew, &form) + return + } + } + repo := ctx.Repo.Repository + os.RemoveAll(codePath) + if err := downloadCode(repo, codePath); err != nil { + log.Error("downloadCode failed, %v", err, ctx.Data["MsgID"]) + cloudBrainNewDataPrepare(ctx) + ctx.RenderWithErr("system error", tplCloudBrainBenchmarkNew, &form) + return + } + + if _, err := os.Stat(codePath + "/train.py"); err != nil { + if os.IsNotExist(err) { + // file does not exist + log.Error("train.py does not exist, %v", err, ctx.Data["MsgID"]) + cloudBrainNewDataPrepare(ctx) + ctx.RenderWithErr("train.py does not exist", tplCloudBrainBenchmarkNew, &form) + } else { + log.Error("Stat failed, %v", err, ctx.Data["MsgID"]) + cloudBrainNewDataPrepare(ctx) + ctx.RenderWithErr("system error", tplCloudBrainBenchmarkNew, &form) + } + return + } else if _, err := os.Stat(codePath + "/test.py"); err != nil { + if os.IsNotExist(err) { + // file does not exist + log.Error("test.py does not exist, %v", err, ctx.Data["MsgID"]) + cloudBrainNewDataPrepare(ctx) + ctx.RenderWithErr("test.py does not exist", tplCloudBrainBenchmarkNew, &form) + } else { + log.Error("Stat failed, %v", err, ctx.Data["MsgID"]) + cloudBrainNewDataPrepare(ctx) + ctx.RenderWithErr("system error", tplCloudBrainBenchmarkNew, &form) + } + return + } + + if err := uploadCodeToMinio(codePath+"/", jobName, cloudbrain.CodeMountPath+"/"); err != nil { + log.Error("uploadCodeToMinio failed, %v", err, ctx.Data["MsgID"]) + cloudBrainNewDataPrepare(ctx) + ctx.RenderWithErr("system error", tplCloudBrainBenchmarkNew, &form) + return + } + + benchmarkPath := setting.JobPath + jobName + cloudbrain.BenchMarkMountPath + var gpuType string + for _, gpuInfo := range gpuInfos.GpuInfo { + if gpuInfo.Queue == gpuQueue { + gpuType = gpuInfo.Value + } + } + + if err := downloadRateCode(repo, jobName, childInfo.Owner, childInfo.RepoName, benchmarkPath, form.BenchmarkCategory, gpuType); err != nil { + log.Error("downloadRateCode failed, %v", err, ctx.Data["MsgID"]) + //cloudBrainNewDataPrepare(ctx) + //ctx.RenderWithErr("system error", tplCloudBrainBenchmarkNew, &form) + //return + } + + if err := uploadCodeToMinio(benchmarkPath+"/", jobName, cloudbrain.BenchMarkMountPath+"/"); err != nil { + log.Error("uploadCodeToMinio failed, %v", err, ctx.Data["MsgID"]) + //cloudBrainNewDataPrepare(ctx) + //ctx.RenderWithErr("system error", tplCloudBrainBenchmarkNew, &form) + //return + } + + err = cloudbrain.GenerateTask(ctx, jobName, image, command, childInfo.Attachment, storage.GetMinioPath(jobName, cloudbrain.CodeMountPath+"/"), + storage.GetMinioPath(jobName, cloudbrain.ModelMountPath+"/"), + storage.GetMinioPath(jobName, cloudbrain.BenchMarkMountPath+"/"), storage.GetMinioPath(jobName, cloudbrain.Snn4imagenetMountPath+"/"), + storage.GetMinioPath(jobName, cloudbrain.BrainScoreMountPath+"/"), string(models.JobTypeBenchmark), gpuQueue, form.Description, + benchmarkTypeID, benchmarkChildTypeID, resourceSpecId) + if err != nil { + cloudBrainNewDataPrepare(ctx) + ctx.RenderWithErr(err.Error(), tplCloudBrainBenchmarkNew, &form) + return + } + + ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/cloudbrain/benchmark") +} + +func BenchmarkDel(ctx *context.Context) { + if err := deleteCloudbrainJob(ctx); err != nil { + log.Error("deleteCloudbrainJob failed: %v", err, ctx.Data["msgID"]) + ctx.ServerError(err.Error(), err) + return + } + ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/cloudbrain/benchmark") +} diff --git a/routers/repo/course.go b/routers/repo/course.go new file mode 100644 index 000000000..44acdfcc0 --- /dev/null +++ b/routers/repo/course.go @@ -0,0 +1,196 @@ +package repo + +import ( + "net/http" + "strings" + + "code.gitea.io/gitea/models" + "code.gitea.io/gitea/modules/auth" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + repo_service "code.gitea.io/gitea/services/repository" +) + +const ( + tplCreateCourse base.TplName = "repo/createCourse" +) + +func CreateCourse(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("new_course") + org, _ := models.GetUserByName(setting.Course.OrgName) + + ctx.Data["Owner"] = org + ctx.Data["IsCourse"] = true + + ctx.HTML(200, tplCreateCourse) + +} + +func CreateCoursePost(ctx *context.Context, form auth.CreateCourseForm) { + ctx.Data["Title"] = ctx.Tr("new_course") + + if ctx.Written() { + return + } + + org, _ := models.GetUserByName(setting.Course.OrgName) + + ctx.Data["Owner"] = org + ctx.Data["IsCourse"] = true + + var topics = make([]string, 0) + var topicsStr = strings.TrimSpace(form.Topics) + if len(topicsStr) > 0 { + topics = strings.Split(topicsStr, ",") + } + + validTopics, invalidTopics := models.SanitizeAndValidateTopics(topics) + + if len(validTopics) > 25 { + ctx.RenderWithErr(ctx.Tr("repo.topic.count_prompt"), tplCreateCourse, form) + return + } + + if len(invalidTopics) > 0 { + ctx.RenderWithErr(ctx.Tr("repo.topic.format_prompt"), tplCreateCourse, form) + return + } + + var repo *models.Repository + var err error + + if setting.Course.OrgName == "" || setting.Course.TeamName == "" { + log.Error("then organization name or team name of course is empty.") + ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form) + return + } + + org, team, err := getOrgAndTeam() + + if err != nil { + log.Error("Failed to get team from db.", err) + ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form) + return + } + isInTeam, err := models.IsUserInTeams(ctx.User.ID, []int64{team.ID}) + if err != nil { + log.Error("Failed to get user in team from db.") + ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form) + return + } + + if !isInTeam { + err = models.AddTeamMember(team, ctx.User.ID) + if err != nil { + log.Error("Failed to add user to team.") + ctx.RenderWithErr(ctx.Tr("repo.failed_to_create_course"), tplCreateCourse, form) + return + } + } + + if ctx.HasError() { + ctx.HTML(200, tplCreateCourse) + return + } + + repo, err = repo_service.CreateRepository(ctx.User, org, models.CreateRepoOptions{ + Name: form.RepoName, + Alias: form.Alias, + Description: form.Description, + Gitignores: "", + IssueLabels: "", + License: "", + Readme: "Default", + IsPrivate: false, + DefaultBranch: "master", + AutoInit: true, + IsCourse: true, + Topics: validTopics, + }) + if err == nil { + log.Trace("Repository created [%d]: %s/%s", repo.ID, org.Name, repo.Name) + ctx.Redirect(setting.AppSubURL + "/" + org.Name + "/" + repo.Name) + return + } + + handleCreateCourseError(ctx, org, err, "CreateCoursePost", tplCreateCourse, &form) +} + +func AddCourseOrg(ctx *context.Context) { + + _, team, err := getOrgAndTeam() + + if err != nil { + log.Error("Failed to get team from db.", err) + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 1, + "message": ctx.Tr("repo.addCourseOrgFail"), + }) + return + } + isInTeam, err := models.IsUserInTeams(ctx.User.ID, []int64{team.ID}) + if err != nil { + log.Error("Failed to get user in team from db.", err) + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 1, + "message": ctx.Tr("repo.add_course_org_fail"), + }) + return + } + + if !isInTeam { + err = models.AddTeamMember(team, ctx.User.ID) + if err != nil { + log.Error("Failed to add user to team.", err) + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 1, + "message": ctx.Tr("repo.add_course_org_fail"), + }) + return + } + } + + ctx.JSON(http.StatusOK, map[string]interface{}{ + "code": 0, + "message": "", + }) + +} + +func getOrgAndTeam() (*models.User, *models.Team, error) { + org, err := models.GetUserByName(setting.Course.OrgName) + + if err != nil { + log.Error("Failed to get organization from db.", err) + return nil, nil, err + } + + team, err := models.GetTeam(org.ID, setting.Course.TeamName) + + if err != nil { + log.Error("Failed to get team from db.", err) + + return nil, nil, err + } + return org, team, nil +} + +func handleCreateCourseError(ctx *context.Context, owner *models.User, err error, name string, tpl base.TplName, form interface{}) { + switch { + case models.IsErrReachLimitOfRepo(err): + ctx.RenderWithErr(ctx.Tr("repo.form.reach_limit_of_course_creation", owner.MaxCreationLimit()), tpl, form) + case models.IsErrRepoAlreadyExist(err): + ctx.Data["Err_RepoName"] = true + ctx.RenderWithErr(ctx.Tr("form.course_name_been_taken"), tpl, form) + case models.IsErrNameReserved(err): + ctx.Data["Err_RepoName"] = true + ctx.RenderWithErr(ctx.Tr("repo.form.course_name_reserved", err.(models.ErrNameReserved).Name), tpl, form) + case models.IsErrNamePatternNotAllowed(err): + ctx.Data["Err_RepoName"] = true + ctx.RenderWithErr(ctx.Tr("repo.form.course_name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tpl, form) + default: + ctx.ServerError(name, err) + } +} diff --git a/routers/repo/download.go b/routers/repo/download.go index 5b8982102..f1b6e09b7 100755 --- a/routers/repo/download.go +++ b/routers/repo/download.go @@ -28,7 +28,8 @@ func ServeData(ctx *context.Context, name string, reader io.Reader) error { buf = buf[:n] } - ctx.Resp.Header().Set("Cache-Control", "public,max-age=86400") + //ctx.Resp.Header().Set("Cache-Control", "public,max-age=86400") + ctx.Resp.Header().Set("Cache-Control", "max-age=0") name = path.Base(name) // Google Chrome dislike commas in filenames, so let's change it to a space diff --git a/routers/repo/modelarts.go b/routers/repo/modelarts.go index 7a952394e..7e9ae3c09 100755 --- a/routers/repo/modelarts.go +++ b/routers/repo/modelarts.go @@ -1,15 +1,18 @@ package repo import ( + "archive/zip" "encoding/json" "errors" "io" + "io/ioutil" "net/http" "os" "path" "strconv" "strings" "time" + "unicode/utf8" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" @@ -22,8 +25,6 @@ import ( "code.gitea.io/gitea/modules/obs" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/storage" - - "github.com/unknwon/com" ) const ( @@ -37,6 +38,10 @@ const ( tplModelArtsTrainJobNew base.TplName = "repo/modelarts/trainjob/new" tplModelArtsTrainJobShow base.TplName = "repo/modelarts/trainjob/show" tplModelArtsTrainJobVersionNew base.TplName = "repo/modelarts/trainjob/version_new" + + tplModelArtsInferenceJobIndex base.TplName = "repo/modelarts/inferencejob/index" + tplModelArtsInferenceJobNew base.TplName = "repo/modelarts/inferencejob/new" + tplModelArtsInferenceJobShow base.TplName = "repo/modelarts/inferencejob/show" ) func DebugJobIndex(ctx *context.Context) { @@ -49,12 +54,15 @@ func DebugJobIndex(ctx *context.Context) { page = 1 } debugType := modelarts.DebugType + jobTypeNot := false if debugListType == models.GPUResource { debugType = models.TypeCloudBrainOne } else if debugListType == models.NPUResource { debugType = models.TypeCloudBrainTwo } + var jobTypes []string + jobTypes = append(jobTypes, string(models.JobTypeSnn4imagenet), string(models.JobTypeBrainScore), string(models.JobTypeDebug)) ciTasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{ ListOptions: models.ListOptions{ Page: page, @@ -62,8 +70,8 @@ func DebugJobIndex(ctx *context.Context) { }, RepoID: repo.ID, Type: debugType, - JobTypeNot: true, - JobType: string(models.JobTypeTrain), + JobTypeNot: jobTypeNot, + JobTypes: jobTypes, }) if err != nil { ctx.ServerError("Get debugjob faild:", err) @@ -119,6 +127,20 @@ func NotebookNew(ctx *context.Context) { ctx.HTML(200, tplModelArtsNotebookNew) } +func notebookNewDataPrepare(ctx *context.Context) error { + ctx.Data["PageIsCloudBrain"] = true + t := time.Now() + var jobName = jobNamePrefixValid(cutString(ctx.User.Name, 5)) + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:] + ctx.Data["job_name"] = jobName + + if modelarts.FlavorInfos == nil { + json.Unmarshal([]byte(setting.FlavorInfos), &modelarts.FlavorInfos) + } + ctx.Data["flavors"] = modelarts.FlavorInfos.FlavorInfo + + return nil +} + func NotebookCreate(ctx *context.Context, form auth.CreateModelArtsNotebookForm) { ctx.Data["PageIsNotebook"] = true jobName := form.JobName @@ -163,6 +185,54 @@ func NotebookCreate(ctx *context.Context, form auth.CreateModelArtsNotebookForm) ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob?debugListType=all") } +func Notebook2Create(ctx *context.Context, form auth.CreateModelArtsNotebookForm) { + ctx.Data["PageIsNotebook"] = true + jobName := form.JobName + uuid := form.Attachment + description := form.Description + flavor := form.Flavor + + flavor = "modelarts.bm.910.arm.public.1" + + count, err := models.GetCloudbrainNotebookCountByUserID(ctx.User.ID) + if err != nil { + log.Error("GetCloudbrainNotebookCountByUserID failed:%v", err, ctx.Data["MsgID"]) + notebookNewDataPrepare(ctx) + ctx.RenderWithErr("system error", tplModelArtsNotebookNew, &form) + return + } else { + if count >= 1 { + log.Error("the user already has running or waiting task", ctx.Data["MsgID"]) + notebookNewDataPrepare(ctx) + ctx.RenderWithErr("you have already a running or waiting task, can not create more", tplModelArtsNotebookNew, &form) + return + } + } + _, err = models.GetCloudbrainByName(jobName) + if err == nil { + log.Error("the job name did already exist", ctx.Data["MsgID"]) + notebookNewDataPrepare(ctx) + ctx.RenderWithErr("the job name did already exist", tplModelArtsNotebookNew, &form) + return + } else { + if !models.IsErrJobNotExist(err) { + log.Error("system error, %v", err, ctx.Data["MsgID"]) + notebookNewDataPrepare(ctx) + ctx.RenderWithErr("system error", tplModelArtsNotebookNew, &form) + return + } + } + + err = modelarts.GenerateNotebook2(ctx, jobName, uuid, description, flavor) + if err != nil { + log.Error("GenerateNotebook2 failed, %v", err, ctx.Data["MsgID"]) + notebookNewDataPrepare(ctx) + ctx.RenderWithErr(err.Error(), tplModelArtsNotebookNew, &form) + return + } + ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/debugjob?debugListType=all") +} + func NotebookShow(ctx *context.Context) { ctx.Data["PageIsCloudBrain"] = true @@ -174,7 +244,7 @@ func NotebookShow(ctx *context.Context) { return } - result, err := modelarts.GetJob(jobID) + result, err := modelarts.GetNotebook2(jobID) if err != nil { ctx.Data["error"] = err.Error() ctx.RenderWithErr(err.Error(), tplModelArtsNotebookShow, nil) @@ -190,12 +260,10 @@ func NotebookShow(ctx *context.Context) { return } - createTime, _ := com.StrTo(result.CreationTimestamp).Int64() - result.CreateTime = time.Unix(int64(createTime/1000), 0).Format("2006-01-02 15:04:05") - endTime, _ := com.StrTo(result.LatestUpdateTimestamp).Int64() - result.LatestUpdateTime = time.Unix(int64(endTime/1000), 0).Format("2006-01-02 15:04:05") - result.QueuingInfo.BeginTime = time.Unix(int64(result.QueuingInfo.BeginTimestamp/1000), 0).Format("2006-01-02 15:04:05") - result.QueuingInfo.EndTime = time.Unix(int64(result.QueuingInfo.EndTimestamp/1000), 0).Format("2006-01-02 15:04:05") + result.CreateTime = time.Unix(int64(result.CreateAt/1000), 0).Format("2006-01-02 15:04:05") + result.LatestUpdateTime = time.Unix(int64(result.UpdateAt/1000), 0).Format("2006-01-02 15:04:05") + //result.QueuingInfo.BeginTime = time.Unix(int64(result.QueuingInfo.BeginTimestamp/1000), 0).Format("2006-01-02 15:04:05") + //result.QueuingInfo.EndTime = time.Unix(int64(result.QueuingInfo.EndTimestamp/1000), 0).Format("2006-01-02 15:04:05") } ctx.Data["task"] = task @@ -231,6 +299,18 @@ func NotebookDebug(ctx *context.Context) { ctx.Redirect(debugUrl) } +func NotebookDebug2(ctx *context.Context) { + var jobID = ctx.Params(":jobid") + + result, err := modelarts.GetNotebook2(jobID) + if err != nil { + ctx.RenderWithErr(err.Error(), tplModelArtsNotebookIndex, nil) + return + } + + ctx.Redirect(result.Url) +} + func NotebookManage(ctx *context.Context) { var jobID = ctx.Params(":jobid") var action = ctx.Params(":action") @@ -302,15 +382,18 @@ func NotebookManage(ctx *context.Context) { param := models.NotebookAction{ Action: action, } - res, err := modelarts.ManageNotebook(jobID, param) + res, err := modelarts.ManageNotebook2(jobID, param) if err != nil { - log.Error("ManageNotebook(%s) failed:%v", task.JobName, err.Error(), ctx.Data["MsgID"]) + log.Error("ManageNotebook2(%s) failed:%v", task.JobName, err.Error(), ctx.Data["MsgID"]) resultCode = "-1" - errorMsg = "启动失败" + errorMsg = err.Error() + if strings.Contains(err.Error(), "ModelArts.6404") { + errorMsg = "the job's version is too old and can not be restarted" + } break } - task.Status = res.CurrentStatus + task.Status = res.Status err = models.UpdateJob(task) if err != nil { log.Error("UpdateJob(%s) failed:%v", task.JobName, err.Error(), ctx.Data["MsgID"]) @@ -342,10 +425,10 @@ func NotebookDel(ctx *context.Context) { return } - _, err := modelarts.DelNotebook(jobID) + _, err := modelarts.DelNotebook2(jobID) if err != nil { - log.Error("DelJob(%s) failed:%v", task.JobName, err.Error()) - ctx.ServerError("DelJob failed", err) + log.Error("DelNotebook2(%s) failed:%v", task.JobName, err.Error()) + ctx.ServerError("DelNotebook2 failed", err) return } @@ -367,6 +450,8 @@ func TrainJobIndex(ctx *context.Context) { page = 1 } + var jobTypes []string + jobTypes = append(jobTypes, string(models.JobTypeTrain)) tasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{ ListOptions: models.ListOptions{ Page: page, @@ -375,7 +460,7 @@ func TrainJobIndex(ctx *context.Context) { RepoID: repo.ID, Type: models.TypeCloudBrainTwo, JobTypeNot: false, - JobType: string(models.JobTypeTrain), + JobTypes: jobTypes, IsLatestVersion: modelarts.IsLatestVersion, }) if err != nil { @@ -749,7 +834,7 @@ func versionErrorDataPrepare(ctx *context.Context, form auth.CreateModelArtsTrai func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm) { ctx.Data["PageIsTrainJob"] = true - VersionOutputPath := modelarts.GetVersionOutputPathByTotalVersionCount(modelarts.TotalVersionCount) + VersionOutputPath := modelarts.GetOutputPathByCount(modelarts.TotalVersionCount) jobName := form.JobName uuid := form.Attachment description := form.Description @@ -794,18 +879,11 @@ func TrainJobCreate(ctx *context.Context, form auth.CreateModelArtsTrainJobForm) return } - // attach, err := models.GetAttachmentByUUID(uuid) - // if err != nil { - // log.Error("GetAttachmentByUUID(%s) failed:%v", uuid, err.Error()) - // return - // } - //todo: del the codeLocalPath - // _, err := ioutil.ReadDir(codeLocalPath) - // if err == nil { - // os.RemoveAll(codeLocalPath) - // } - os.RemoveAll(codeLocalPath) + _, err = ioutil.ReadDir(codeLocalPath) + if err == nil { + os.RemoveAll(codeLocalPath) + } gitRepo, _ := git.OpenRepository(repo.RepoPath()) commitID, _ := gitRepo.GetBranchCommitID(branch_name) @@ -973,7 +1051,7 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ ctx.ServerError("GetCloudbrainByJobIDAndIsLatestVersion faild:", err) return } - VersionOutputPath := modelarts.GetVersionOutputPathByTotalVersionCount(latestTask.TotalVersionCount + 1) + VersionOutputPath := modelarts.GetOutputPathByCount(latestTask.TotalVersionCount + 1) jobName := form.JobName uuid := form.Attachment @@ -1011,18 +1089,17 @@ func TrainJobCreateVersion(ctx *context.Context, form auth.CreateModelArtsTrainJ return } - // attach, err := models.GetAttachmentByUUID(uuid) - // if err != nil { - // log.Error("GetAttachmentByUUID(%s) failed:%v", uuid, err.Error()) - // return - // } - //todo: del the codeLocalPath - // _, err = ioutil.ReadDir(codeLocalPath) - // if err == nil { - // os.RemoveAll(codeLocalPath) - // } - os.RemoveAll(codeLocalPath) + _, err = ioutil.ReadDir(codeLocalPath) + if err == nil { + os.RemoveAll(codeLocalPath) + } else { + log.Error("创建任务失败,原代码还未删除,请重试!: %s (%v)", repo.FullName(), err) + versionErrorDataPrepare(ctx, form) + ctx.RenderWithErr("创建任务失败,原代码还未删除,请重试!", tplModelArtsTrainJobVersionNew, &form) + return + } + // os.RemoveAll(codeLocalPath) gitRepo, _ := git.OpenRepository(repo.RepoPath()) commitID, _ := gitRepo.GetBranchCommitID(branch_name) @@ -1264,6 +1341,42 @@ func paramCheckCreateTrainJob(form auth.CreateModelArtsTrainJobForm) error { return nil } +func paramCheckCreateInferenceJob(form auth.CreateModelArtsInferenceJobForm) error { + if !strings.HasSuffix(form.BootFile, ".py") { + log.Error("the boot file(%s) must be a python file", form.BootFile) + return errors.New("启动文件必须是python文件") + } + + if form.WorkServerNumber > 25 || form.WorkServerNumber < 1 { + log.Error("the WorkServerNumber(%d) must be in (1,25)", form.WorkServerNumber) + return errors.New("计算节点数必须在1-25之间") + } + + if form.ModelName == "" { + log.Error("the ModelName(%d) must not be nil", form.ModelName) + return errors.New("模型名称不能为空") + } + if form.ModelVersion == "" { + log.Error("the ModelVersion(%d) must not be nil", form.ModelVersion) + return errors.New("模型版本不能为空") + } + if form.CkptName == "" { + log.Error("the CkptName(%d) must not be nil", form.CkptName) + return errors.New("权重文件不能为空") + } + if form.BranchName == "" { + log.Error("the Branch(%d) must not be nil", form.BranchName) + return errors.New("分支名不能为空") + } + + if utf8.RuneCountInString(form.Description) > 255 { + log.Error("the Description length(%d) must not more than 255", form.Description) + return errors.New("描述字符不能超过255个字符") + } + + return nil +} + func TrainJobShow(ctx *context.Context) { ctx.Data["PageIsCloudBrain"] = true var jobID = ctx.Params(":jobid") @@ -1273,15 +1386,18 @@ func TrainJobShow(ctx *context.Context) { if page <= 0 { page = 1 } + + var jobTypes []string + jobTypes = append(jobTypes, string(models.JobTypeTrain)) VersionListTasks, VersionListCount, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{ ListOptions: models.ListOptions{ Page: page, PageSize: setting.UI.IssuePagingNum, }, - RepoID: repo.ID, - Type: models.TypeCloudBrainTwo, - JobType: string(models.JobTypeTrain), - JobID: jobID, + RepoID: repo.ID, + Type: models.TypeCloudBrainTwo, + JobTypes: jobTypes, + JobID: jobID, }) if err != nil { @@ -1392,11 +1508,13 @@ func TrainJobDel(ctx *context.Context) { var jobID = ctx.Params(":jobid") repo := ctx.Repo.Repository + var jobTypes []string + jobTypes = append(jobTypes, string(models.JobTypeTrain)) VersionListTasks, _, err := models.CloudbrainsVersionList(&models.CloudbrainsOptions{ - RepoID: repo.ID, - Type: models.TypeCloudBrainTwo, - JobType: string(models.JobTypeTrain), - JobID: jobID, + RepoID: repo.ID, + Type: models.TypeCloudBrainTwo, + JobTypes: jobTypes, + JobID: jobID, }) if err != nil { ctx.ServerError("get VersionListTasks failed", err) @@ -1518,6 +1636,427 @@ func getConfigList(perPage, page int, sortBy, order, searchContent, configType s return list, nil } +func InferenceJobCreate(ctx *context.Context, form auth.CreateModelArtsInferenceJobForm) { + ctx.Data["PageIsTrainJob"] = true + VersionOutputPath := modelarts.GetOutputPathByCount(modelarts.TotalVersionCount) + jobName := form.JobName + uuid := form.Attachment + description := form.Description + workServerNumber := form.WorkServerNumber + engineID := form.EngineID + bootFile := form.BootFile + flavorCode := form.Flavor + params := form.Params + poolID := form.PoolID + repo := ctx.Repo.Repository + codeLocalPath := setting.JobPath + jobName + modelarts.CodePath + codeObsPath := "/" + setting.Bucket + modelarts.JobPath + jobName + modelarts.CodePath + resultObsPath := "/" + setting.Bucket + modelarts.JobPath + jobName + modelarts.ResultPath + VersionOutputPath + "/" + logObsPath := "/" + setting.Bucket + modelarts.JobPath + jobName + modelarts.LogPath + VersionOutputPath + "/" + dataPath := "/" + setting.Bucket + "/" + setting.BasePath + path.Join(uuid[0:1], uuid[1:2]) + "/" + uuid + uuid + "/" + branch_name := form.BranchName + FlavorName := form.FlavorName + EngineName := form.EngineName + LabelName := form.LabelName + isLatestVersion := modelarts.IsLatestVersion + VersionCount := modelarts.VersionCount + trainUrl := form.TrainUrl + modelName := form.ModelName + modelVersion := form.ModelVersion + ckptName := form.CkptName + + ckptUrl := form.TrainUrl + form.CkptName + + if err := paramCheckCreateInferenceJob(form); err != nil { + log.Error("paramCheckCreateInferenceJob failed:(%v)", err) + inferenceJobErrorNewDataPrepare(ctx, form) + ctx.RenderWithErr(err.Error(), tplModelArtsInferenceJobNew, &form) + return + } + + count, err := models.GetCloudbrainInferenceJobCountByUserID(ctx.User.ID) + if err != nil { + log.Error("GetCloudbrainInferenceJobCountByUserID failed:%v", err, ctx.Data["MsgID"]) + inferenceJobErrorNewDataPrepare(ctx, form) + ctx.RenderWithErr("system error", tplModelArtsInferenceJobNew, &form) + return + } else { + if count >= 1 { + log.Error("the user already has running or waiting inference task", ctx.Data["MsgID"]) + inferenceJobErrorNewDataPrepare(ctx, form) + ctx.RenderWithErr("you have already a running or waiting inference task, can not create more", tplModelArtsInferenceJobNew, &form) + return + } + } + + //todo: del the codeLocalPath + _, err = ioutil.ReadDir(codeLocalPath) + if err == nil { + os.RemoveAll(codeLocalPath) + } + + gitRepo, _ := git.OpenRepository(repo.RepoPath()) + commitID, _ := gitRepo.GetBranchCommitID(branch_name) + + if err := git.Clone(repo.RepoPath(), codeLocalPath, git.CloneRepoOptions{ + Branch: branch_name, + }); err != nil { + log.Error("创建任务失败,服务器超时!: %s (%v)", repo.FullName(), err) + inferenceJobErrorNewDataPrepare(ctx, form) + ctx.RenderWithErr("创建任务失败,服务器超时!", tplModelArtsInferenceJobNew, &form) + return + } + + //todo: upload code (send to file_server todo this work?) + if err := obsMkdir(setting.CodePathPrefix + jobName + modelarts.ResultPath + VersionOutputPath + "/"); err != nil { + log.Error("Failed to obsMkdir_result: %s (%v)", repo.FullName(), err) + inferenceJobErrorNewDataPrepare(ctx, form) + ctx.RenderWithErr("Failed to obsMkdir_result", tplModelArtsInferenceJobNew, &form) + return + } + + if err := obsMkdir(setting.CodePathPrefix + jobName + modelarts.LogPath + VersionOutputPath + "/"); err != nil { + log.Error("Failed to obsMkdir_log: %s (%v)", repo.FullName(), err) + inferenceJobErrorNewDataPrepare(ctx, form) + ctx.RenderWithErr("Failed to obsMkdir_log", tplModelArtsInferenceJobNew, &form) + return + } + + if err := uploadCodeToObs(codeLocalPath, jobName, ""); err != nil { + log.Error("Failed to uploadCodeToObs: %s (%v)", repo.FullName(), err) + inferenceJobErrorNewDataPrepare(ctx, form) + ctx.RenderWithErr("Failed to uploadCodeToObs", tplModelArtsInferenceJobNew, &form) + return + } + + //todo: del local code? + var parameters models.Parameters + param := make([]models.Parameter, 0) + param = append(param, models.Parameter{ + Label: modelarts.ResultUrl, + Value: "s3:/" + resultObsPath, + }, models.Parameter{ + Label: modelarts.CkptUrl, + Value: "s3:/" + ckptUrl, + }) + if len(params) != 0 { + err := json.Unmarshal([]byte(params), ¶meters) + if err != nil { + log.Error("Failed to Unmarshal params: %s (%v)", params, err) + inferenceJobErrorNewDataPrepare(ctx, form) + ctx.RenderWithErr("运行参数错误", tplModelArtsInferenceJobNew, &form) + return + } + + for _, parameter := range parameters.Parameter { + if parameter.Label != modelarts.TrainUrl && parameter.Label != modelarts.DataUrl { + param = append(param, models.Parameter{ + Label: parameter.Label, + Value: parameter.Value, + }) + } + } + } + + req := &modelarts.GenerateInferenceJobReq{ + JobName: jobName, + DataUrl: dataPath, + Description: description, + CodeObsPath: codeObsPath, + BootFileUrl: codeObsPath + bootFile, + BootFile: bootFile, + TrainUrl: trainUrl, + FlavorCode: flavorCode, + WorkServerNumber: workServerNumber, + EngineID: int64(engineID), + LogUrl: logObsPath, + PoolID: poolID, + Uuid: uuid, + Parameters: param, //modelarts训练时用到 + CommitID: commitID, + BranchName: branch_name, + Params: form.Params, + FlavorName: FlavorName, + EngineName: EngineName, + LabelName: LabelName, + IsLatestVersion: isLatestVersion, + VersionCount: VersionCount, + TotalVersionCount: modelarts.TotalVersionCount, + ModelName: modelName, + ModelVersion: modelVersion, + CkptName: ckptName, + ResultUrl: resultObsPath, + } + + //将params转换Parameters.Parameter,出错时返回给前端 + // var Parameters modelarts.Parameters + // if err := json.Unmarshal([]byte(params), &Parameters); err != nil { + // ctx.ServerError("json.Unmarshal failed:", err) + // return + // } + + err = modelarts.GenerateInferenceJob(ctx, req) + if err != nil { + log.Error("GenerateTrainJob failed:%v", err.Error()) + inferenceJobErrorNewDataPrepare(ctx, form) + ctx.RenderWithErr(err.Error(), tplModelArtsInferenceJobNew, &form) + return + } + ctx.Redirect(setting.AppSubURL + ctx.Repo.RepoLink + "/modelarts/inference-job") +} +func InferenceJobIndex(ctx *context.Context) { + MustEnableModelArts(ctx) + + repo := ctx.Repo.Repository + page := ctx.QueryInt("page") + if page <= 0 { + page = 1 + } + + var jobTypes []string + jobTypes = append(jobTypes, string(models.JobTypeInference)) + tasks, count, err := models.Cloudbrains(&models.CloudbrainsOptions{ + ListOptions: models.ListOptions{ + Page: page, + PageSize: setting.UI.IssuePagingNum, + }, + RepoID: repo.ID, + Type: models.TypeCloudBrainTwo, + JobTypes: jobTypes, + }) + if err != nil { + ctx.ServerError("Cloudbrain", err) + return + } + + for i, task := range tasks { + tasks[i].CanDel = cloudbrain.CanDeleteJob(ctx, &task.Cloudbrain) + tasks[i].CanModify = cloudbrain.CanModifyJob(ctx, &task.Cloudbrain) + tasks[i].ComputeResource = models.NPUResource + } + + repoId := ctx.Repo.Repository.ID + Type := -1 + _, model_count, _ := models.QueryModel(&models.AiModelQueryOptions{ + ListOptions: models.ListOptions{ + Page: 1, + PageSize: 2, + }, + RepoID: repoId, + Type: Type, + New: MODEL_LATEST, + }) + ctx.Data["MODEL_COUNT"] = model_count + + pager := context.NewPagination(int(count), setting.UI.IssuePagingNum, page, 5) + pager.SetDefaultParams(ctx) + ctx.Data["Page"] = pager + + ctx.Data["PageIsCloudBrain"] = true + ctx.Data["Tasks"] = tasks + ctx.Data["CanCreate"] = cloudbrain.CanCreateOrDebugJob(ctx) + ctx.Data["RepoIsEmpty"] = repo.IsEmpty + ctx.HTML(200, tplModelArtsInferenceJobIndex) +} +func InferenceJobNew(ctx *context.Context) { + err := inferenceJobNewDataPrepare(ctx) + if err != nil { + ctx.ServerError("get new inference-job info failed", err) + return + } + ctx.HTML(200, tplModelArtsInferenceJobNew) +} +func inferenceJobNewDataPrepare(ctx *context.Context) error { + ctx.Data["PageIsCloudBrain"] = true + + t := time.Now() + var jobName = cutString(ctx.User.Name, 5) + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:] + ctx.Data["job_name"] = jobName + + attachs, err := models.GetModelArtsTrainAttachments(ctx.User.ID) + if err != nil { + ctx.ServerError("GetAllUserAttachments failed:", err) + return err + } + ctx.Data["attachments"] = attachs + + var resourcePools modelarts.ResourcePool + if err = json.Unmarshal([]byte(setting.ResourcePools), &resourcePools); err != nil { + ctx.ServerError("json.Unmarshal failed:", err) + return err + } + ctx.Data["resource_pools"] = resourcePools.Info + + var engines modelarts.Engine + if err = json.Unmarshal([]byte(setting.Engines), &engines); err != nil { + ctx.ServerError("json.Unmarshal failed:", err) + return err + } + ctx.Data["engines"] = engines.Info + + var versionInfos modelarts.VersionInfo + if err = json.Unmarshal([]byte(setting.EngineVersions), &versionInfos); err != nil { + ctx.ServerError("json.Unmarshal failed:", err) + return err + } + ctx.Data["engine_versions"] = versionInfos.Version + + var flavorInfos modelarts.Flavor + if err = json.Unmarshal([]byte(setting.TrainJobFLAVORINFOS), &flavorInfos); err != nil { + ctx.ServerError("json.Unmarshal failed:", err) + return err + } + + ctx.Data["flavor_infos"] = flavorInfos.Info + ctx.Data["params"] = "" + ctx.Data["branchName"] = ctx.Repo.BranchName + + configList, err := getConfigList(modelarts.PerPage, 1, modelarts.SortByCreateTime, "desc", "", modelarts.ConfigTypeCustom) + if err != nil { + ctx.ServerError("getConfigList failed:", err) + return err + } + ctx.Data["config_list"] = configList.ParaConfigs + + repoId := ctx.Repo.Repository.ID + Type := -1 + _, model_count, _ := models.QueryModel(&models.AiModelQueryOptions{ + ListOptions: models.ListOptions{ + Page: 1, + PageSize: 2, + }, + RepoID: repoId, + Type: Type, + New: MODEL_LATEST, + }) + ctx.Data["MODEL_COUNT"] = model_count + + return nil +} + +func inferenceJobErrorNewDataPrepare(ctx *context.Context, form auth.CreateModelArtsInferenceJobForm) error { + ctx.Data["PageIsCloudBrain"] = true + + t := time.Now() + var jobName = "inference" + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:] + ctx.Data["job_name"] = jobName + + attachs, err := models.GetModelArtsTrainAttachments(ctx.User.ID) + if err != nil { + ctx.ServerError("GetAllUserAttachments failed:", err) + return err + } + ctx.Data["attachments"] = attachs + + var resourcePools modelarts.ResourcePool + if err = json.Unmarshal([]byte(setting.ResourcePools), &resourcePools); err != nil { + ctx.ServerError("json.Unmarshal failed:", err) + return err + } + ctx.Data["resource_pools"] = resourcePools.Info + + var engines modelarts.Engine + if err = json.Unmarshal([]byte(setting.Engines), &engines); err != nil { + ctx.ServerError("json.Unmarshal failed:", err) + return err + } + ctx.Data["engines"] = engines.Info + + var versionInfos modelarts.VersionInfo + if err = json.Unmarshal([]byte(setting.EngineVersions), &versionInfos); err != nil { + ctx.ServerError("json.Unmarshal failed:", err) + return err + } + ctx.Data["engine_versions"] = versionInfos.Version + + var flavorInfos modelarts.Flavor + if err = json.Unmarshal([]byte(setting.TrainJobFLAVORINFOS), &flavorInfos); err != nil { + ctx.ServerError("json.Unmarshal failed:", err) + return err + } + ctx.Data["flavor_infos"] = flavorInfos.Info + + configList, err := getConfigList(modelarts.PerPage, 1, modelarts.SortByCreateTime, "desc", "", modelarts.ConfigTypeCustom) + if err != nil { + ctx.ServerError("getConfigList failed:", err) + return err + } + var Parameters modelarts.Parameters + if err = json.Unmarshal([]byte(form.Params), &Parameters); err != nil { + ctx.ServerError("json.Unmarshal failed:", err) + return err + } + ctx.Data["params"] = Parameters.Parameter + ctx.Data["config_list"] = configList.ParaConfigs + ctx.Data["bootFile"] = form.BootFile + ctx.Data["uuid"] = form.Attachment + ctx.Data["branch_name"] = form.BranchName + ctx.Data["model_name"] = form.ModelName + ctx.Data["model_version"] = form.ModelVersion + ctx.Data["ckpt_name"] = form.CkptName + ctx.Data["train_url"] = form.TrainUrl + + return nil +} +func InferenceJobShow(ctx *context.Context) { + ctx.Data["PageIsCloudBrain"] = true + var jobID = ctx.Params(":jobid") + + page := ctx.QueryInt("page") + if page <= 0 { + page = 1 + } + task, err := models.GetCloudbrainByJobID(jobID) + + if err != nil { + log.Error("GetInferenceTask(%s) failed:%v", jobID, err.Error()) + ctx.RenderWithErr(err.Error(), tplModelArtsInferenceJobShow, nil) + return + } + //设置权限 + canNewJob, err := canUserCreateTrainJobVersion(ctx, task.UserID) + if err != nil { + ctx.ServerError("canNewJob failed", err) + return + } + ctx.Data["canNewJob"] = canNewJob + + //将运行参数转化为epoch_size = 3, device_target = Ascend的格式 + var parameters models.Parameters + err = json.Unmarshal([]byte(task.Parameters), ¶meters) + if err != nil { + log.Error("Failed to Unmarshal Parameters: %s (%v)", task.Parameters, err) + trainJobNewDataPrepare(ctx) + return + } + + if len(parameters.Parameter) > 0 { + paramTemp := "" + for _, Parameter := range parameters.Parameter { + param := Parameter.Label + " = " + Parameter.Value + "; " + paramTemp = paramTemp + param + } + task.Parameters = paramTemp[:len(paramTemp)-2] + } else { + task.Parameters = "" + } + + LabelName := strings.Fields(task.LabelName) + ctx.Data["labelName"] = LabelName + ctx.Data["jobID"] = jobID + ctx.Data["jobName"] = task.JobName + ctx.Data["task"] = task + + tempUids := []int64{} + tempUids = append(tempUids, task.UserID) + JobCreater, err := models.GetUserNamesByIDs(tempUids) + if err != nil { + log.Error("GetUserNamesByIDs (WhitelistUserIDs): %v", err) + } + ctx.Data["userName"] = JobCreater[0] + ctx.HTML(http.StatusOK, tplModelArtsInferenceJobShow) +} + func ModelDownload(ctx *context.Context) { var ( err error @@ -1546,6 +2085,31 @@ func ModelDownload(ctx *context.Context) { http.Redirect(ctx.Resp, ctx.Req.Request, url, http.StatusMovedPermanently) } +func ResultDownload(ctx *context.Context) { + var ( + err error + ) + + var jobID = ctx.Params(":jobid") + versionName := ctx.Query("version_name") + parentDir := ctx.Query("parent_dir") + fileName := ctx.Query("file_name") + log.Info("DownloadResult start.") + task, err := models.GetCloudbrainByJobID(jobID) + if err != nil { + ctx.Data["error"] = err.Error() + } + path := strings.TrimPrefix(path.Join(setting.TrainJobModelPath, task.JobName, "result/", versionName, parentDir, fileName), "/") + log.Info("Download path is:%s", path) + + url, err := storage.GetObsCreateSignedUrlByBucketAndKey(setting.Bucket, path) + if err != nil { + log.Error("GetObsCreateSignedUrl failed: %v", err.Error(), ctx.Data["msgID"]) + ctx.ServerError("GetObsCreateSignedUrl", err) + return + } + http.Redirect(ctx.Resp, ctx.Req.Request, url, http.StatusMovedPermanently) +} func DeleteJobStorage(jobName string) error { //delete local localJobPath := setting.JobPath + jobName @@ -1563,3 +2127,82 @@ func DeleteJobStorage(jobName string) error { return nil } + +func DownloadMultiResultFile(ctx *context.Context) { + var jobID = ctx.Params(":jobid") + var versionName = ctx.Query("version_name") + task, err := models.GetCloudbrainByJobIDAndVersionName(jobID, versionName) + if err != nil { + log.Error("GetCloudbrainByJobID(%s) failed:%v", task.JobName, err.Error()) + return + } + // if !isCanDeleteOrDownload(ctx, task) { + // ctx.ServerError("no right.", errors.New(ctx.Tr("repo.model_noright"))) + // return + // } + + // path := Model_prefix + models.AttachmentRelativePath(id) + "/" + path := strings.TrimPrefix(path.Join(setting.TrainJobModelPath, task.JobName, "result/", versionName), "/") + "/" + + allFile, err := storage.GetAllObjectByBucketAndPrefix(setting.Bucket, path) + if err == nil { + //count++ + // models.ModifyModelDownloadCount(id) + + returnFileName := task.JobName + ".zip" + ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+returnFileName) + ctx.Resp.Header().Set("Content-Type", "application/octet-stream") + w := zip.NewWriter(ctx.Resp) + defer w.Close() + for _, oneFile := range allFile { + if oneFile.IsDir { + log.Info("zip dir name:" + oneFile.FileName) + } else { + log.Info("zip file name:" + oneFile.FileName) + fDest, err := w.Create(oneFile.FileName) + if err != nil { + log.Info("create zip entry error, download file failed: %s\n", err.Error()) + ctx.ServerError("download file failed:", err) + return + } + body, err := storage.ObsDownloadAFile(setting.Bucket, path+oneFile.FileName) + if err != nil { + log.Info("download file failed: %s\n", err.Error()) + ctx.ServerError("download file failed:", err) + return + } else { + defer body.Close() + p := make([]byte, 1024) + var readErr error + var readCount int + // 读取对象内容 + for { + readCount, readErr = body.Read(p) + if readCount > 0 { + fDest.Write(p[:readCount]) + } + if readErr != nil { + break + } + } + } + } + } + } else { + log.Info("error,msg=" + err.Error()) + ctx.ServerError("no file to download.", err) + } +} + +func SetJobCount(ctx *context.Context) { + repoId := ctx.Repo.Repository.ID + _, jobCount, err := models.Cloudbrains(&models.CloudbrainsOptions{ + RepoID: repoId, + Type: modelarts.DebugType, + }) + if err != nil { + ctx.ServerError("Get job faild:", err) + return + } + ctx.Data["jobCount"] = jobCount +} diff --git a/routers/repo/pull.go b/routers/repo/pull.go index 8841e4755..22ea3a54c 100755 --- a/routers/repo/pull.go +++ b/routers/repo/pull.go @@ -105,6 +105,7 @@ func getForkRepository(ctx *context.Context) *models.Repository { return nil } ctx.Data["ForkFrom"] = forkRepo.Owner.Name + "/" + forkRepo.Name + ctx.Data["ForkDisplayName"] = forkRepo.FullDisplayName() ctx.Data["ForkFromOwnerID"] = forkRepo.Owner.ID if err := ctx.User.GetOwnedOrganizations(); err != nil { @@ -221,7 +222,7 @@ func ForkPost(ctx *context.Context, form auth.CreateRepoForm) { } } - repo, err := repo_service.ForkRepository(ctx.User, ctxUser, forkRepo, form.RepoName, form.Description) + repo, err := repo_service.ForkRepository(ctx.User, ctxUser, forkRepo, form.RepoName, form.Description, form.Alias) if err != nil { ctx.Data["Err_RepoName"] = true switch { diff --git a/routers/repo/repo.go b/routers/repo/repo.go index a182e9087..70705dcf1 100644 --- a/routers/repo/repo.go +++ b/routers/repo/repo.go @@ -6,11 +6,14 @@ package repo import ( + "code.gitea.io/gitea/modules/validation" "fmt" "net/url" "os" "path" + "regexp" "strings" + "unicode/utf8" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" @@ -201,6 +204,7 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { if form.RepoTemplate > 0 { opts := models.GenerateRepoOptions{ Name: form.RepoName, + Alias: form.Alias, Description: form.Description, Private: form.Private, GitContent: form.GitContent, @@ -235,6 +239,7 @@ func CreatePost(ctx *context.Context, form auth.CreateRepoForm) { } else { repo, err = repo_service.CreateRepository(ctx.User, ctxUser, models.CreateRepoOptions{ Name: form.RepoName, + Alias: form.Alias, Description: form.Description, Gitignores: form.Gitignores, IssueLabels: form.IssueLabels, @@ -358,6 +363,7 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) { GitServiceType: gitServiceType, CloneAddr: remoteAddr, RepoName: form.RepoName, + Alias: form.Alias, Description: form.Description, Private: form.Private || setting.Repository.ForcePrivate, Mirror: form.Mirror, @@ -380,7 +386,7 @@ func MigratePost(ctx *context.Context, form auth.MigrateRepoForm) { opts.Releases = false } - err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName) + err = models.CheckCreateRepository(ctx.User, ctxUser, opts.RepoName, opts.Alias) if err != nil { handleMigrateError(ctx, ctxUser, err, "MigratePost", tplMigrate, &form) return @@ -552,3 +558,27 @@ func Status(ctx *context.Context) { "err": task.Errors, }) } + +var repoNamePattern = regexp.MustCompile("^[0-9a-zA-Z\\.\\-_]{1,100}$") +var repoAliasPattern = regexp.MustCompile("^[\u4e00-\u9fa5\\.\\-_A-Za-z0-9]{1,100}$") + +// CheckName returns repository's default name(by given alias) +func CheckName(ctx *context.Context) { + var r = make(map[string]string, 1) + q := ctx.Query("q") + owner := ctx.Query("owner") + if q == "" || owner == "" || utf8.RuneCountInString(q) > 100 || !validation.ValidAlphaDashDotChinese(q) { + r["name"] = "" + ctx.JSON(200, r) + return + } + if repoNamePattern.MatchString(q) { + r["name"] = q + ctx.JSON(200, r) + return + } + n := models.GenerateDefaultRepoName(owner) + r["name"] = n + ctx.JSON(200, r) + return +} diff --git a/routers/repo/repo_statistic.go b/routers/repo/repo_statistic.go index 11b421659..dce183f49 100755 --- a/routers/repo/repo_statistic.go +++ b/routers/repo/repo_statistic.go @@ -51,12 +51,14 @@ func RepoStatisticDaily(date string) { isInitMinMaxRadar := false + var error_projects = make([]string, 0) for _, repo := range repos { - log.Info("start statistic: %s", getDistinctProjectName(repo)) + projectName := getDistinctProjectName(repo) + log.Info("start statistic: %s", projectName) var numDevMonths, numWikiViews, numContributor, numKeyContributor, numCommitsGrowth, numCommitLinesGrowth, numContributorsGrowth, numCommits int64 repoGitStat, err := models.GetRepoKPIStats(repo) if err != nil { - log.Error("GetRepoKPIStats failed: %s", getDistinctProjectName(repo)) + log.Error("GetRepoKPIStats failed: %s", projectName) } else { numDevMonths = repoGitStat.DevelopAge numKeyContributor = repoGitStat.KeyContributors @@ -79,26 +81,26 @@ func RepoStatisticDaily(date string) { var numVersions int64 numVersions, err = models.GetReleaseCountByRepoID(repo.ID, models.FindReleasesOptions{}) if err != nil { - log.Error("GetReleaseCountByRepoID failed(%s): %v", getDistinctProjectName(repo), err) + log.Error("GetReleaseCountByRepoID failed(%s): %v", projectName, err) } var datasetSize int64 datasetSize, err = getDatasetSize(repo) if err != nil { - log.Error("getDatasetSize failed(%s): %v", getDistinctProjectName(repo), err) + log.Error("getDatasetSize failed(%s): %v", projectName, err) } var numComments int64 numComments, err = models.GetCommentCountByRepoID(repo.ID) if err != nil { - log.Error("GetCommentCountByRepoID failed(%s): %v", getDistinctProjectName(repo), err) + log.Error("GetCommentCountByRepoID failed(%s): %v", projectName, err) } beginTime, endTime := getStatTime(date) var numVisits int numVisits, err = repository.AppointProjectView(repo.OwnerName, repo.Name, beginTime, endTime) if err != nil { - log.Error("AppointProjectView failed(%s): %v", getDistinctProjectName(repo), err) + log.Error("AppointProjectView failed(%s): %v", projectName, err) } repoStat := models.RepoStatistic{ @@ -162,9 +164,10 @@ func RepoStatisticDaily(date string) { } if _, err = models.InsertRepoStat(&repoStat); err != nil { - log.Error("InsertRepoStat failed(%s): %v", getDistinctProjectName(repo), err) - log.Error("failed statistic: %s", getDistinctProjectName(repo)) - mailer.SendWarnNotifyMail(setting.Warn_Notify_Mails, warnEmailMessage) + log.Error("InsertRepoStat failed(%s): %v", projectName, err) + log.Error("failed statistic: %s", projectName) + error_projects = append(error_projects, projectName) + continue } @@ -247,6 +250,10 @@ func RepoStatisticDaily(date string) { log.Info("finish statistic: %s", getDistinctProjectName(repo)) } + if len(error_projects) > 0 { + mailer.SendWarnNotifyMail(setting.Warn_Notify_Mails, warnEmailMessage) + } + //radar map log.Info("begin statistic radar") for _, radarInit := range reposRadar { diff --git a/routers/repo/setting.go b/routers/repo/setting.go index f7da8f4a8..5b057dbe5 100644 --- a/routers/repo/setting.go +++ b/routers/repo/setting.go @@ -6,6 +6,7 @@ package repo import ( + "code.gitea.io/gitea/modules/notification" "errors" "fmt" "io/ioutil" @@ -50,6 +51,8 @@ func Settings(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["PageIsSettingsOptions"] = true ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate + SetModelCount(ctx) + SetJobCount(ctx) ctx.HTML(200, tplSettingsOptions) } @@ -57,7 +60,8 @@ func Settings(ctx *context.Context) { func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { ctx.Data["Title"] = ctx.Tr("repo.settings") ctx.Data["PageIsSettingsOptions"] = true - + SetModelCount(ctx) + SetJobCount(ctx) repo := ctx.Repo.Repository switch ctx.Query("action") { @@ -67,6 +71,30 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { return } + newAlias := form.Alias + var aliasChanged = false + // Check if repository alias has been changed. + if strings.ToLower(repo.Alias) != strings.ToLower(newAlias) { + aliasChanged = true + //check new alias is available or not + if err := models.IsRepositoryAliasAvailable(ctx.Repo.Owner, newAlias); err != nil { + ctx.Data["Err_Alias"] = true + switch { + case models.IsErrRepoAlreadyExist(err): + ctx.RenderWithErr(ctx.Tr("form.repo_name_been_taken"), tplSettingsOptions, &form) + case models.IsErrNameReserved(err): + ctx.RenderWithErr(ctx.Tr("repo.form.name_reserved", err.(models.ErrNameReserved).Name), tplSettingsOptions, &form) + case models.IsErrNamePatternNotAllowed(err): + ctx.RenderWithErr(ctx.Tr("repo.form.name_pattern_not_allowed", err.(models.ErrNamePatternNotAllowed).Pattern), tplSettingsOptions, &form) + default: + ctx.ServerError("ChangeRepositoryName", err) + } + return + } + + log.Trace("Repository alias changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Alias, newAlias) + } + newRepoName := form.RepoName // Check if repository name has been changed. if repo.LowerName != strings.ToLower(newRepoName) { @@ -92,12 +120,19 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { log.Trace("Repository name changed: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newRepoName) } + //notify + if aliasChanged { + notification.NotifyRenameRepository(ctx.Repo.Owner, repo, repo.Alias) + } + // In case it's just a case change. repo.Name = newRepoName repo.LowerName = strings.ToLower(newRepoName) repo.Description = form.Description repo.Website = form.Website repo.IsTemplate = form.Template + repo.Alias = newAlias + repo.LowerAlias = strings.ToLower(newAlias) // Visibility of forked repository is forced sync with base repository. if repo.IsFork { @@ -377,7 +412,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { ctx.Error(404) return } - if repo.Name != form.RepoName { + if repo.Alias != form.RepoName { ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) return } @@ -404,7 +439,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { ctx.Error(404) return } - if repo.Name != form.RepoName { + if repo.Alias != form.RepoName { ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) return } @@ -442,7 +477,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { ctx.Error(404) return } - if repo.Name != form.RepoName { + if repo.Alias != form.RepoName { ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) return } @@ -462,7 +497,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) { ctx.Error(404) return } - if repo.Name != form.RepoName { + if repo.Alias != form.RepoName { ctx.RenderWithErr(ctx.Tr("form.enterred_invalid_repo_name"), tplSettingsOptions, nil) return } diff --git a/routers/repo/user_data_analysis.go b/routers/repo/user_data_analysis.go index 7df384cc4..e71e8cb3a 100755 --- a/routers/repo/user_data_analysis.go +++ b/routers/repo/user_data_analysis.go @@ -88,22 +88,22 @@ func queryUserDataPage(ctx *context.Context, tableName string, queryObj interfac xlsx.SetCellValue(sheetName, "P"+rows, formatTime[0:len(formatTime)-3]) formatTime = userRecord.DataDate - xlsx.SetCellValue(sheetName, "Q"+rows, formatTime+" 00:01") + xlsx.SetCellValue(sheetName, "Q"+rows, formatTime) } - //设置默认打开的表单 - xlsx.SetActiveSheet(index) - filename := sheetName + "_" + ctx.Tr("user.static."+tableName) + ".xlsx" - ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+url.QueryEscape(filename)) - ctx.Resp.Header().Set("Content-Type", "application/octet-stream") - if _, err := xlsx.WriteTo(ctx.Resp); err != nil { - log.Info("writer exel error." + err.Error()) - } indexTotal += PAGE_SIZE if indexTotal >= count { break } } + //设置默认打开的表单 + xlsx.SetActiveSheet(index) + filename := sheetName + "_" + ctx.Tr("user.static."+tableName) + ".xlsx" + ctx.Resp.Header().Set("Content-Disposition", "attachment; filename="+url.QueryEscape(filename)) + ctx.Resp.Header().Set("Content-Type", "application/octet-stream") + if _, err := xlsx.WriteTo(ctx.Resp); err != nil { + log.Info("writer exel error." + err.Error()) + } } else { re, count := models.QueryUserStaticDataByTableName((page-1)*pageSize, pageSize, tableName, queryObj, userName) mapInterface := make(map[string]interface{}) diff --git a/routers/repo/view.go b/routers/repo/view.go index b08243240..320102ba4 100755 --- a/routers/repo/view.go +++ b/routers/repo/view.go @@ -35,6 +35,7 @@ import ( const ( tplRepoEMPTY base.TplName = "repo/empty" tplRepoHome base.TplName = "repo/home" + tplCourseHome base.TplName = "repo/courseHome" tplWatchers base.TplName = "repo/watchers" tplForks base.TplName = "repo/forks" tplMigrating base.TplName = "repo/migrating" @@ -855,7 +856,12 @@ func renderCode(ctx *context.Context) { ctx.Data["TreeLink"] = treeLink ctx.Data["TreeNames"] = treeNames ctx.Data["BranchLink"] = branchLink - ctx.HTML(200, tplRepoHome) + if ctx.Repo.Repository.RepoType == models.RepoCourse { + ctx.HTML(200, tplCourseHome) + } else { + ctx.HTML(200, tplRepoHome) + } + } // RenderUserCards render a page show users according the input templaet diff --git a/routers/routes/routes.go b/routers/routes/routes.go index 439c17a92..aa85b8f2b 100755 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -708,6 +708,13 @@ func RegisterRoutes(m *macaron.Macaron) { }, reqSignIn) // ***** END: Organization ***** + m.Group("/course", func() { + m.Get("/create", repo.CreateCourse) + m.Post("/create", bindIgnErr(auth.CreateCourseForm{}), repo.CreateCoursePost) + m.Get("/addOrg", repo.AddCourseOrg) + + }, reqSignIn) + // ***** START: Repository ***** m.Group("/repo", func() { m.Get("/create", repo.Create) @@ -718,6 +725,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Combo("/:repoid").Get(repo.Fork). Post(bindIgnErr(auth.CreateRepoForm{}), repo.ForkPost) }, context.RepoIDAssignment(), context.UnitTypes(), reqRepoCodeReader) + m.Get("/check_name", repo.CheckName) }, reqSignIn) // ***** Release Attachment Download without Signin @@ -979,6 +987,19 @@ func RegisterRoutes(m *macaron.Macaron) { }) m.Get("/create", reqRepoCloudBrainWriter, repo.CloudBrainNew) m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainCreate) + + m.Group("/benchmark", func() { + m.Get("", reqRepoCloudBrainReader, repo.CloudBrainBenchmarkIndex) + m.Group("/:jobid", func() { + m.Get("", reqRepoCloudBrainReader, repo.CloudBrainBenchMarkShow) + m.Post("/stop", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.CloudBrainStop) + m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.BenchmarkDel) + m.Get("/rate", reqRepoCloudBrainReader, repo.GetRate) + }) + m.Get("/create", reqRepoCloudBrainWriter, repo.CloudBrainBenchmarkNew) + m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateCloudBrainForm{}), repo.CloudBrainBenchmarkCreate) + m.Get("/get_child_types", repo.GetChildTypes) + }) }, context.RepoRef()) m.Group("/modelmanage", func() { m.Post("/create_model", reqRepoModelManageWriter, repo.SaveModel) @@ -1008,6 +1029,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Group("/modelarts", func() { m.Group("/notebook", func() { + /* v1.0 m.Group("/:jobid", func() { m.Get("", reqRepoCloudBrainReader, repo.NotebookShow) m.Get("/debug", cloudbrain.AdminOrJobCreaterRight, repo.NotebookDebug) @@ -1016,6 +1038,15 @@ func RegisterRoutes(m *macaron.Macaron) { }) m.Get("/create", reqRepoCloudBrainWriter, repo.NotebookNew) m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsNotebookForm{}), repo.NotebookCreate) + */ + m.Group("/:jobid", func() { + m.Get("", reqRepoCloudBrainReader, repo.NotebookShow) + m.Get("/debug", cloudbrain.AdminOrJobCreaterRight, repo.NotebookDebug2) + m.Post("/:action", reqRepoCloudBrainWriter, repo.NotebookManage) + m.Post("/del", cloudbrain.AdminOrOwnerOrJobCreaterRight, repo.NotebookDel) + }) + m.Get("/create", reqRepoCloudBrainWriter, repo.NotebookNew) + m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsNotebookForm{}), repo.Notebook2Create) }) m.Group("/train-job", func() { @@ -1033,6 +1064,17 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/para-config-list", reqRepoCloudBrainReader, repo.TrainJobGetConfigList) }) + + m.Group("/inference-job", func() { + m.Get("", reqRepoCloudBrainReader, repo.InferenceJobIndex) + m.Group("/:jobid", func() { + m.Get("", reqRepoCloudBrainReader, repo.InferenceJobShow) + m.Get("/result_download", cloudbrain.AdminOrJobCreaterRight, repo.ResultDownload) + m.Get("/downloadall", repo.DownloadMultiResultFile) + }) + m.Get("/create", reqRepoCloudBrainWriter, repo.InferenceJobNew) + m.Post("/create", reqRepoCloudBrainWriter, bindIgnErr(auth.CreateModelArtsInferenceJobForm{}), repo.InferenceJobCreate) + }) }, context.RepoRef()) m.Group("/blockchain", func() { diff --git a/routers/user/auth.go b/routers/user/auth.go index 16af84b66..a02cf24dc 100755 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -116,8 +116,16 @@ func checkAutoLogin(ctx *context.Context) bool { } if isSucceed { + + isCourse := ctx.QueryBool("course") ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true) - ctx.RedirectToFirst(redirectTo, setting.AppSubURL+string(setting.LandingPageURL)) + if redirectTo == "" && isCourse { + redirectToCourse := setting.AppSubURL + "/" + setting.Course.OrgName + ctx.RedirectToFirst(redirectToCourse) + } else { + ctx.RedirectToFirst(redirectTo, setting.AppSubURL+string(setting.LandingPageURL)) + + } return true } @@ -143,6 +151,7 @@ func SignIn(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("sign_in") ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" ctx.Data["PageIsSignIn"] = true + ctx.Data["IsCourse"] = ctx.QueryBool("course") ctx.Data["PageIsLogin"] = true ctx.Data["EnableSSPI"] = models.IsSSPIEnabled() ctx.Data["EnableCloudBrain"] = true @@ -182,6 +191,7 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) { ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" ctx.Data["PageIsSignIn"] = true ctx.Data["PageIsLogin"] = true + ctx.Data["IsCourse"] = ctx.QueryBool("course") ctx.Data["EnableSSPI"] = models.IsSSPIEnabled() if ctx.HasError() { @@ -565,6 +575,12 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR return setting.AppSubURL + "/dashboard" } + isCourse := ctx.QueryBool("course") + if isCourse { + redirectToCourse := setting.AppSubURL + "/" + setting.Course.OrgName + ctx.RedirectToFirst(redirectToCourse) + return redirectToCourse + } if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) { ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true) if obeyRedirect { diff --git a/services/repository/repository.go b/services/repository/repository.go index eafad988e..cea16516a 100644 --- a/services/repository/repository.go +++ b/services/repository/repository.go @@ -5,12 +5,17 @@ package repository import ( + "fmt" + "io/ioutil" + "net/http" + "strings" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/notification" repo_module "code.gitea.io/gitea/modules/repository" + "code.gitea.io/gitea/modules/setting" pull_service "code.gitea.io/gitea/services/pull" - "fmt" ) // CreateRepository creates a repository for the user/organization. @@ -31,8 +36,8 @@ func CreateRepository(doer, owner *models.User, opts models.CreateRepoOptions) ( } // ForkRepository forks a repository -func ForkRepository(doer, u *models.User, oldRepo *models.Repository, name, desc string) (*models.Repository, error) { - repo, err := repo_module.ForkRepository(doer, u, oldRepo, name, desc) +func ForkRepository(doer, u *models.User, oldRepo *models.Repository, name, desc, alias string) (*models.Repository, error) { + repo, err := repo_module.ForkRepository(doer, u, oldRepo, name, desc, alias) if err != nil { if repo != nil { if errDelete := models.DeleteRepository(doer, u.ID, repo.ID); errDelete != nil { @@ -86,3 +91,84 @@ func PushCreateRepo(authUser, owner *models.User, repoName string) (*models.Repo return repo, nil } + +func GetRecommendCourseKeyWords() ([]string, error) { + + url := setting.RecommentRepoAddr + "course_keywords" + result, err := RecommendFromPromote(url) + + if err != nil { + return []string{}, err + } + return result, err + +} + +func GetRecommendRepoFromPromote(filename string) ([]map[string]interface{}, error) { + resultRepo := make([]map[string]interface{}, 0) + url := setting.RecommentRepoAddr + filename + result, err := RecommendFromPromote(url) + + if err != nil { + + return resultRepo, err + } + + //resultRepo := make([]*models.Repository, 0) + for _, repoName := range result { + tmpIndex := strings.Index(repoName, "/") + if tmpIndex == -1 { + log.Info("error repo name format.") + } else { + ownerName := strings.Trim(repoName[0:tmpIndex], " ") + repoName := strings.Trim(repoName[tmpIndex+1:], " ") + repo, err := models.GetRepositoryByOwnerAndAlias(ownerName, repoName) + if err == nil { + repoMap := make(map[string]interface{}) + repoMap["ID"] = fmt.Sprint(repo.ID) + repoMap["Name"] = repo.Name + repoMap["Alias"] = repo.Alias + if repo.RepoType == models.RepoCourse { + //Load creator + repo.GetCreator() + repoMap["Creator"] = repo.Creator + } + + repoMap["OwnerName"] = repo.OwnerName + repoMap["NumStars"] = repo.NumStars + repoMap["NumForks"] = repo.NumForks + repoMap["Description"] = repo.Description + repoMap["NumWatchs"] = repo.NumWatches + repoMap["Topics"] = repo.Topics + repoMap["Avatar"] = repo.RelAvatarLink() + resultRepo = append(resultRepo, repoMap) + } else { + log.Info("query repo error," + err.Error()) + } + } + } + return resultRepo, nil +} + +func RecommendFromPromote(url string) ([]string, error) { + resp, err := http.Get(url) + if err != nil || resp.StatusCode != 200 { + log.Info("Get organizations url error=" + err.Error()) + return nil, err + } + bytes, err := ioutil.ReadAll(resp.Body) + resp.Body.Close() + if err != nil { + log.Info("Get organizations url error=" + err.Error()) + return nil, err + } + + allLineStr := string(bytes) + lines := strings.Split(allLineStr, "\n") + result := make([]string, len(lines)) + for i, line := range lines { + log.Info("i=" + fmt.Sprint(i) + " line=" + line) + result[i] = strings.Trim(line, " ") + } + return result, nil +} diff --git a/services/repository/transfer.go b/services/repository/transfer.go index d34c812b8..0173f81d1 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -55,7 +55,7 @@ func TransferOwnership(doer, newOwner *models.User, repo *models.Repository, tea // ChangeRepositoryName changes all corresponding setting from old repository name to new one. func ChangeRepositoryName(doer *models.User, repo *models.Repository, newRepoName string) error { - oldRepoName := repo.Name + //oldRepoName := repo.Name // Change repository directory name. We must lock the local copy of the // repo so that we can atomically rename the repo path and updates the @@ -68,7 +68,7 @@ func ChangeRepositoryName(doer *models.User, repo *models.Repository, newRepoNam } repoWorkingPool.CheckOut(com.ToStr(repo.ID)) - notification.NotifyRenameRepository(doer, repo, oldRepoName) + //notification.NotifyRenameRepository(doer, repo, oldRepoName) return nil } diff --git a/services/socketwrap/clientManager.go b/services/socketwrap/clientManager.go index eeb496108..53de73673 100644 --- a/services/socketwrap/clientManager.go +++ b/services/socketwrap/clientManager.go @@ -10,6 +10,8 @@ import ( "github.com/elliotchance/orderedmap" ) +var opTypes = []int{1, 2, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 17, 22, 23} + type ClientsManager struct { Clients *orderedmap.OrderedMap Register chan *Client @@ -47,13 +49,16 @@ func (h *ClientsManager) Run() { close(client.Send) } case message := <-models.ActionChan: - LastActionsQueue.Push(message) - for _, client := range h.Clients.Keys() { - select { - case client.(*Client).Send <- message: - default: - close(client.(*Client).Send) - h.Clients.Delete(client) + if isInOpTypes(opTypes, message.OpType) { + filterUserPrivateInfo(message) + LastActionsQueue.Push(message) + for _, client := range h.Clients.Keys() { + select { + case client.(*Client).Send <- message: + default: + close(client.(*Client).Send) + h.Clients.Delete(client) + } } } case s := <-sig: @@ -71,14 +76,26 @@ func (h *ClientsManager) Run() { } } +func isInOpTypes(types []int, opType models.ActionType) bool { + isFound := false + for _, value := range types { + if value == int(opType) { + isFound = true + break + } + } + return isFound +} + func initActionQueue() { - actions, err := models.GetLast20PublicFeeds() + actions, err := models.GetLast20PublicFeeds(opTypes) if err == nil { for i := len(actions) - 1; i >= 0; i-- { user, err := models.GetUserByID(actions[i].UserID) if err == nil { if !user.IsOrganization() { + filterUserPrivateInfo(actions[i]) LastActionsQueue.Push(actions[i]) } @@ -87,3 +104,19 @@ func initActionQueue() { } } } + +func filterUserPrivateInfo(action *models.Action) { + action.Comment = nil + action.ActUser.Email = "" + action.ActUser.Passwd = "" + action.ActUser.PasswdHashAlgo = "" + action.ActUser.PrivateKey = "" + action.ActUser.PublicKey = "" + action.ActUser.Salt = "" + action.ActUser.FullName = "" + action.ActUser.AvatarEmail = "" + action.ActUser.IsAdmin = false + action.ActUser.EmailNotificationsPreference = "" + action.ActUser.IsOperator = false + +} diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl index f946b8a46..3dee62bfb 100644 --- a/templates/admin/repo/list.tmpl +++ b/templates/admin/repo/list.tmpl @@ -36,7 +36,7 @@ {{svg "octicon-lock" 16}} {{end}} - {{.Name}} + {{.Alias}} {{.NumWatches}} {{.NumStars}} diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 75496aed5..10015a61b 100755 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -200,14 +200,7 @@ var _hmt = _hmt || []; -
- - - {{.notice.Title}} - - - -
+ {{template "base/head_notice" .}} {{end}} {{/*
@@ -231,7 +224,15 @@ var _hmt = _hmt || []; }else{ isNewNotice=false; } - if (JSON.parse("{{.notice.Visible}}")){ + let isShowNoticeTag = false; + let notices= {{.notices.Notices}} + for (i =0;i \ No newline at end of file + + if(!("{{.IsCourse}}" == true || "{{.IsCourse}}" =='true')) { + isShowNotice(); + } + diff --git a/templates/base/head_course.tmpl b/templates/base/head_course.tmpl new file mode 100644 index 000000000..3fff0b8d2 --- /dev/null +++ b/templates/base/head_course.tmpl @@ -0,0 +1,209 @@ + + + + + + + {{if .Title}}{{.Title}} - {{end}} {{if .Repository.Name}}{{.Repository.Name}} - {{end}}{{AppName}} + + {{if UseServiceWorker}} + + {{else}} + + {{end}} + + + + + + + {{if .IsSigned}} + + {{end}} + {{if .ContextUser}} + + {{end}} + {{if .SearchLimit}} + + {{end}} +{{if .GoGetImport}} + + +{{end}} + + + + + + + + +{{if .RequireSimpleMDE}} + +{{end}} + +{{if .RequireTribute}} + +{{end}} + + + +{{if .RequireMinicolors}} + +{{end}} + +{{if .PageIsUserProfile}} + + + + + {{if .Owner.Description}} + + {{end}} +{{else if .Repository}} + {{if .Issue}} + + + {{if .Issue.Content}} + + {{end}} + {{else}} + + + {{if .Repository.Description}} + + {{end}} + {{end}} + + +{{else}} + + + + + +{{end}} + +{{if .IsSigned }} + {{ if ne .SignedUser.Theme "gitea" }} + + {{end}} +{{else if ne DefaultTheme "gitea"}} + +{{end}} + +{{template "custom/header" .}} + + + + + + {{template "custom/body_outer_pre" .}} + +
+ + + {{template "custom/body_inner_pre" .}} + + {{if not .PageIsInstall}} + + {{end}} +{{/* +
+ + +*/}} + diff --git a/templates/base/head_fluid.tmpl b/templates/base/head_fluid.tmpl index 5878bfb13..ad5464dcc 100644 --- a/templates/base/head_fluid.tmpl +++ b/templates/base/head_fluid.tmpl @@ -201,14 +201,7 @@ var _hmt = _hmt || []; -
- - - {{.notice.Title}} - - - -
+ {{template "base/head_notice" .}} {{end}} {{/* @@ -232,7 +225,15 @@ var _hmt = _hmt || []; }else{ isNewNotice=false; } - if (JSON.parse("{{.notice.Visible}}")){ + let isShowNoticeTag = false; + let notices= {{.notices.Notices}} + for (i =0;i \ No newline at end of file diff --git a/templates/base/head_home.tmpl b/templates/base/head_home.tmpl index fc5a94c0c..47919133e 100644 --- a/templates/base/head_home.tmpl +++ b/templates/base/head_home.tmpl @@ -205,14 +205,7 @@ var _hmt = _hmt || []; - + {{template "base/head_notice" .}} {{end}} {{/* @@ -236,7 +229,15 @@ var _hmt = _hmt || []; }else{ isNewNotice=false; } - if (JSON.parse("{{.notice.Visible}}")){ + let isShowNoticeTag = false; + let notices= {{.notices.Notices}} + for (i =0;i \ No newline at end of file diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index d1d40d1d6..0ce3f2fcf 100755 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -25,7 +25,7 @@ @@ -43,7 +43,7 @@ {{if .IsOperator}} {{.i18n.Tr "explore.data_analysis"}} {{end}} - {{.i18n.Tr "custom.head.openi"}} + {{.i18n.Tr "custom.head.openi.repo"}} {{else if .IsLandingPageHome}} @@ -55,7 +55,7 @@ @@ -73,7 +73,7 @@ {{if .IsOperator}} {{.i18n.Tr "explore.data_analysis"}} {{end}} - {{.i18n.Tr "custom.head.openi"}} + {{.i18n.Tr "custom.head.openi.repo"}} {{else if .IsLandingPageExplore}} @@ -211,13 +211,25 @@ {{if .ShowRegistrationButton}} - - {{svg "octicon-person" 16}} {{.i18n.Tr "register"}} + {{if .IsCourse}} + + {{svg "octicon-person" 16}} {{.i18n.Tr "register"}} + + {{else}} + + {{svg "octicon-person" 16}} {{.i18n.Tr "register"}} + + {{end}} + {{end}} + {{if .IsCourse}} + + {{svg "octicon-sign-in" 16}} {{.i18n.Tr "sign_in"}} + + {{else}} + + {{svg "octicon-sign-in" 16}} {{.i18n.Tr "sign_in"}} {{end}} - - {{svg "octicon-sign-in" 16}} {{.i18n.Tr "sign_in"}} - {{end}} diff --git a/templates/base/head_navbar_fluid.tmpl b/templates/base/head_navbar_fluid.tmpl index f364518cc..9475f8f5f 100644 --- a/templates/base/head_navbar_fluid.tmpl +++ b/templates/base/head_navbar_fluid.tmpl @@ -25,7 +25,7 @@ @@ -42,7 +42,7 @@ {{if .IsOperator}} {{.i18n.Tr "explore.data_analysis"}} {{end}} - {{.i18n.Tr "custom.head.openi"}} + {{.i18n.Tr "custom.head.openi.repo"}} {{else if .IsLandingPageHome}} @@ -54,7 +54,7 @@ @@ -71,7 +71,7 @@ {{if .IsOperator}} {{.i18n.Tr "explore.data_analysis"}} {{end}} - {{.i18n.Tr "custom.head.openi"}} + {{.i18n.Tr "custom.head.openi.repo"}} {{else if .IsLandingPageExplore}} diff --git a/templates/base/head_navbar_home.tmpl b/templates/base/head_navbar_home.tmpl index 539b8bc21..49b0b60e8 100644 --- a/templates/base/head_navbar_home.tmpl +++ b/templates/base/head_navbar_home.tmpl @@ -17,7 +17,7 @@ @@ -34,7 +34,7 @@ {{if .IsOperator}} {{.i18n.Tr "explore.data_analysis"}} {{end}} - {{.i18n.Tr "custom.head.openi"}} + {{.i18n.Tr "custom.head.openi.repo"}} {{else if .IsLandingPageHome}} @@ -46,7 +46,7 @@ @@ -64,7 +64,7 @@ {{if .IsOperator}} {{.i18n.Tr "explore.data_analysis"}} {{end}} - {{.i18n.Tr "custom.head.openi"}} + {{.i18n.Tr "custom.head.openi.repo"}} {{else if .IsLandingPageExplore}} diff --git a/templates/base/head_navbar_pro.tmpl b/templates/base/head_navbar_pro.tmpl index 45b1e7925..0c1243fb0 100644 --- a/templates/base/head_navbar_pro.tmpl +++ b/templates/base/head_navbar_pro.tmpl @@ -26,7 +26,7 @@ @@ -44,7 +44,7 @@ {{if .IsOperator}} {{.i18n.Tr "explore.data_analysis"}} {{end}} - {{.i18n.Tr "custom.head.openi"}} + {{.i18n.Tr "custom.head.openi.repo"}} {{else if .IsLandingPageHome}} @@ -56,7 +56,7 @@ @@ -74,7 +74,7 @@ {{if .IsOperator}} {{.i18n.Tr "explore.data_analysis"}} {{end}} - {{.i18n.Tr "custom.head.openi"}} + {{.i18n.Tr "custom.head.openi.repo"}} {{else if .IsLandingPageExplore}} diff --git a/templates/base/head_notice.tmpl b/templates/base/head_notice.tmpl new file mode 100644 index 000000000..88615cc38 --- /dev/null +++ b/templates/base/head_notice.tmpl @@ -0,0 +1,32 @@ +{{if not .IsCourse}} + {{ if .notices}} +
+
+ + {{ $firstTag := true }} + {{range .notices.Notices}} + {{if eq .Visible 1}} + {{if $firstTag}} + + + {{.Title}} + + {{else}} + + + {{.Title}} + + {{end}} + {{ $firstTag = false }} + {{end}} + {{end}} + + +
+ +
+
+ +
+ {{end}} +{{end}} \ No newline at end of file diff --git a/templates/base/head_pro.tmpl b/templates/base/head_pro.tmpl index 10d86f809..85719cf9b 100644 --- a/templates/base/head_pro.tmpl +++ b/templates/base/head_pro.tmpl @@ -201,14 +201,7 @@ var _hmt = _hmt || []; -
- - - {{.notice.Title}} - - - {{svg "octicon-x" 16}} -
+ {{template "base/head_notice" .}} {{end}} {{/* @@ -233,7 +226,15 @@ var _hmt = _hmt || []; }else{ isNewNotice=false; } - if (JSON.parse("{{.notice.Visible}}")){ + let isShowNoticeTag = false; + let notices= {{.notices.Notices}} + for (i =0;i \ No newline at end of file diff --git a/templates/explore/dataset_list.tmpl b/templates/explore/dataset_list.tmpl index 7abc03363..242f99dc9 100755 --- a/templates/explore/dataset_list.tmpl +++ b/templates/explore/dataset_list.tmpl @@ -26,7 +26,7 @@ + \ No newline at end of file diff --git a/templates/explore/repo_list.tmpl b/templates/explore/repo_list.tmpl index d7560837f..0e01186b0 100755 --- a/templates/explore/repo_list.tmpl +++ b/templates/explore/repo_list.tmpl @@ -5,7 +5,7 @@ border-radius: 0.8rem; margin-bottom: 1.0rem; padding: 1.0rem !important; - } + } .ui.repository.list>.item .header { font-size: 1.4rem !important; font-weight: 200; @@ -24,7 +24,7 @@ content: ""; height: 1px; background-color: #E1E3E6; - bottom: 2.8rem; + bottom: 2.8rem; } .repository .ui.mini.menu{ font-size: .6rem; @@ -43,13 +43,13 @@ + 热门{{.i18n.Tr "explore.repos"}} + 活跃{{.i18n.Tr "explore.repos"}} {{end}} @@ -93,7 +93,7 @@
- {{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.Name}} + {{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.DisplayName}} {{if .IsArchived}}{{end}} {{if .IsPrivate}} @@ -114,7 +114,7 @@ - + {{.Hot}} {{else if eq $.SortType "active"}} @@ -130,7 +130,7 @@ {{svg "octicon-git-branch" 16}} {{.NumForks}} - {{end}} + {{end}} {{svg "octicon-star" 16}} {{.NumStars}} diff --git a/templates/explore/repo_orgtop.tmpl b/templates/explore/repo_orgtop.tmpl index c2865d7c8..d9c29fd54 100755 --- a/templates/explore/repo_orgtop.tmpl +++ b/templates/explore/repo_orgtop.tmpl @@ -1,5 +1,6 @@ - - + + + +
+
+ {{range .Repos}} +
+
+
+ {{if .Topics }} +
+ {{range .Topics}} + {{if ne . "" }}{{.}}{{end}} + {{end}} + +
+ {{end}} +
+ + +
+ {{.Description}} +
+
+
+
+ + + {{if .Creator }} + + + + + {{else}} + + + + + {{end}} + + {{$.i18n.Tr "org.repo_released"}} :   {{TimeSinceUnixShort .CreatedUnix}} + +
+
+
+ {{end}} +
+ +
diff --git a/templates/org/header.tmpl b/templates/org/header.tmpl index a2ceaac5f..2dc1c35d1 100644 --- a/templates/org/header.tmpl +++ b/templates/org/header.tmpl @@ -8,20 +8,58 @@ {{.DisplayName}} {{end}} - - {{if .IsOrganizationOwner}} - - {{end}} - {{if .CanCreateOrgRepo}} - - {{end}} + {{if .IsCourse}} + {{if .CanCreateOrgRepo}} + + {{end}} + {{else}} + {{if .IsOrganizationOwner}} + + {{end}} + {{if .CanCreateOrgRepo}} + + {{end}} + {{end}}
+ + diff --git a/templates/org/header_course.tmpl b/templates/org/header_course.tmpl new file mode 100644 index 000000000..ae7cceff3 --- /dev/null +++ b/templates/org/header_course.tmpl @@ -0,0 +1,32 @@ +
+
+
+
+
+ {{with .Org}} + + {{.DisplayName}} + {{end}} + {{if .IsCourse}} + {{if .IsOrganizationOwner}} + + {{end}} + {{else}} + {{if .IsOrganizationOwner}} + + {{end}} + {{if .CanCreateOrgRepo}} + + {{end}} + {{end}} +
+
+
+
+
\ No newline at end of file diff --git a/templates/org/home_courses.tmpl b/templates/org/home_courses.tmpl new file mode 100644 index 000000000..dd287c87f --- /dev/null +++ b/templates/org/home_courses.tmpl @@ -0,0 +1,450 @@ + + + {{template "base/head" .}} + +
+
+ {{/* overflow: auto is the clearfix - this avoids the image going beyond + the container where it is supposed to stay inside. */}} + +
+
+ +
+
+ {{.Org.DisplayName}} +
+
+ {{if .Org.Description}}

{{.Org.Description}}

{{end}} +
+ +
+ {{if .Org.Location}}
{{svg "octicon-location" 16}} {{.Org.Location}}
{{end}} + {{if .Org.Website}}
{{svg "octicon-link" 16}} {{.Org.Website}}
{{end}} +
+
+
+
+ {{template "org/navber_course" .}} + +
+ + +
+ +
+
+
+ + {{$.i18n.Tr "org.all_keywords"}} + + {{range .CoursesKeywords}} + {{if ne . ""}} + + {{.}} + + {{end}} + {{end}} +
+ + +
+
+
+ +
+
+
+
+ {{template "org/course_list" .}} + {{template "base/paginate" .}} +
+
+ {{if .tags}} +

+ {{.i18n.Tr "org.selected_couse"}} + {{if .IsOrganizationOwner}} + + {{end}} +

+ +
+ {{ range .tags}} + {{if eq .TagName "精选项目"}} + {{range $i, $v := .RepoList}} + {{if gt $i 0}} +
+ {{end}} +
+ +
+ {{.Alias}} +

+ {{if ne .CreatorID 0}} + {{$.i18n.Tr "home.contributor"}} : {{.Creator.Name}} + {{else}} + {{$.i18n.Tr "home.contributor"}}:{{.Owner.Name}} + {{end}} +

+
+
+ + {{end}} + {{end}} + {{end}} +
+ {{end}} +

+ {{.i18n.Tr "org.people"}} + +

+
+ {{$isMember := .IsOrganizationMember}} + {{range .Members}} + {{if or $isMember (.IsPublicMember $.Org.ID)}} + + {{end}} + {{end}} + + +
+ {{if .IsOrganizationMember}} +
+ {{.i18n.Tr "org.teams"}} + +
+ + {{if .IsOrganizationOwner}} + + {{end}} + {{end}} +
+
+
+ +
+
+
+ + + + {{template "base/footer" .}} + \ No newline at end of file diff --git a/templates/org/member/course_members.tmpl b/templates/org/member/course_members.tmpl new file mode 100644 index 000000000..83018739f --- /dev/null +++ b/templates/org/member/course_members.tmpl @@ -0,0 +1,130 @@ + + +{{template "base/head" .}} + +
+
+ {{template "org/header" .}} + {{template "org/navber_course" .}} +
+ {{template "base/alert" .}} +
+ + +
+
+ {{ range .Members}} +
+
+ + +
+ {{ $isPublic := index $.MembersIsPublicMember .ID}} + {{if $isPublic}} + {{if or (eq $.SignedUser.ID .ID) $.IsOrganizationOwner}} {{$.i18n.Tr "org.members.public_helper"}} {{end}} + {{else}} + {{if or (eq $.SignedUser.ID .ID) $.IsOrganizationOwner}} {{$.i18n.Tr "org.members.private_helper"}} {{end}} + {{end}} +
+
+ +
+
+ {{.Name}} + +
+ {{if index $.MembersIsUserOrgOwner .ID}} {{$.i18n.Tr "org.members.owner"}}{{else}}{{$.i18n.Tr "org.members.member"}}{{end}} +
+
+ +
+ {{.FullName}} +
+ +
+ {{svg "octicon-mail" 16}} + {{.Email}} +
+ +
+ +
+ + {{end}} + +
+
+ + {{template "base/paginate" .}} +
+ +
+
+{{template "base/footer" .}} + + \ No newline at end of file diff --git a/templates/org/navber_course.tmpl b/templates/org/navber_course.tmpl new file mode 100644 index 000000000..1905c2f02 --- /dev/null +++ b/templates/org/navber_course.tmpl @@ -0,0 +1,28 @@ + + \ No newline at end of file diff --git a/templates/org/repo_list.tmpl b/templates/org/repo_list.tmpl index eab03c591..c763f8976 100644 --- a/templates/org/repo_list.tmpl +++ b/templates/org/repo_list.tmpl @@ -33,7 +33,7 @@ {{end}} - {{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{.Name}} + {{if or $.PageIsExplore $.PageIsProfileStarList }}{{if .Owner}}{{.Owner.Name}} / {{end}}{{end}}{{if .Alias}}{{.Alias}}{{else}}{{.Name}}{{end}} {{if .IsArchived}}{{end}} {{if .IsPrivate}} @@ -70,4 +70,4 @@ {{$.i18n.Tr "explore.repo_no_results"}} {{end}} - + \ No newline at end of file diff --git a/templates/org/select_pro.tmpl b/templates/org/select_pro.tmpl index 647bccd57..70b2a9bb3 100755 --- a/templates/org/select_pro.tmpl +++ b/templates/org/select_pro.tmpl @@ -61,7 +61,9 @@ -webkit-line-clamp: 2; -webkit-box-orient: vertical; } - + .ui.cards>.card>.extra .tags > a{ + margin-top: 5px; + }
@@ -87,7 +89,7 @@
@@ -99,7 +101,7 @@ {{if .Topics }}
{{range .Topics}} - {{if ne . "" }}{{.}}{{end}} + {{if ne . "" }}{{.}}{{end}} {{end}}
@@ -108,7 +110,7 @@
-
+
\ No newline at end of file diff --git a/templates/repo/cloudbrain/benchmark/new.tmpl b/templates/repo/cloudbrain/benchmark/new.tmpl new file mode 100755 index 000000000..081e44e48 --- /dev/null +++ b/templates/repo/cloudbrain/benchmark/new.tmpl @@ -0,0 +1,233 @@ +{{template "base/head" .}} + + +
+
+
+
+
+
+
+
+
+
+{{template "base/footer" .}} + + \ No newline at end of file diff --git a/templates/repo/cloudbrain/benchmark/show.tmpl b/templates/repo/cloudbrain/benchmark/show.tmpl new file mode 100755 index 000000000..99fd35de2 --- /dev/null +++ b/templates/repo/cloudbrain/benchmark/show.tmpl @@ -0,0 +1,458 @@ +{{template "base/head" .}} + +
+
+
+
+
+
+
+
+
+
+{{template "repo/header" .}} +
+

+ +

+ {{range $k ,$v := .version_list_task}} +
+
+
+
+ + + +
+ {{TimeSinceUnix1 .CreatedUnix}} + + {{$.i18n.Tr "repo.modelarts.status"}}: + {{.Status}} + + {{$.i18n.Tr "repo.modelarts.train_job.dura_time"}}: + {{$.duration}} + +
+
+
+
+
+
+
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{$.i18n.Tr "repo.cloudbrain_task"}} + +
+ {{.JobName}} +
+
+ {{$.i18n.Tr "repo.modelarts.status"}} + +
+ {{.Status}} +
+
+ {{$.i18n.Tr "repo.modelarts.train_job.start_time"}} + +
+ {{TimeSinceUnix1 .CreatedUnix}} +
+
+ {{$.i18n.Tr "repo.modelarts.train_job.dura_time"}} + +
+ {{$.duration}} +
+
+ 镜像 + +
+ {{.Image}} +
+
+ 类型 + +
+ {{$.BenchmarkTypeName}} +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ 训练程序 + +
+ train.py +
+
+ 测试程序 + +
+ test.py +
+
+ {{$.i18n.Tr "repo.modelarts.train_job.description"}} + +
+ {{.Description}} +
+
+ {{$.i18n.Tr "repo.modelarts.train_job.standard"}} + +
+ {{$.resource_spec}} +
+
+ 创建者 + +
+ {{.User.Name}} +
+
+ 子类型 + +
+ {{$.BenchmarkChildTypeName}} +
+
+
+
+
+ +
+
+
+
+ +
+ + +

+                            
+ +
+ +
+ +
+
+
+ {{end}} {{template "base/paginate" .}} +
+ +
+ +
+ + +
+{{template "base/footer" .}} + + \ No newline at end of file diff --git a/templates/repo/cloudbrain/new.tmpl b/templates/repo/cloudbrain/new.tmpl index eb7805a23..0735c593b 100755 --- a/templates/repo/cloudbrain/new.tmpl +++ b/templates/repo/cloudbrain/new.tmpl @@ -147,16 +147,13 @@
- +
-
+
+ {{range .images}} @@ -225,27 +222,27 @@
- +
- +
- +
- +
- +
- +