Browse Source

Merge pull request 'V202108合并到develop分支' (#338) from V202108 into develop

Reviewed-on: https://git.openi.org.cn/OpenI/aiforge/pulls/338
tags/v1.21.8
lewis 4 years ago
parent
commit
0c358e8936
40 changed files with 964 additions and 124 deletions
  1. +39
    -7
      models/cloudbrain.go
  2. +11
    -1
      models/file_chunk.go
  3. +10
    -0
      models/user_mail.go
  4. +1
    -0
      modules/auth/cloudbrain.go
  5. +22
    -5
      modules/cloudbrain/cloudbrain.go
  6. +41
    -38
      modules/cloudbrain/resty.go
  7. +14
    -12
      modules/modelarts/modelarts.go
  8. +11
    -1
      modules/setting/setting.go
  9. +0
    -0
      modules/timeutil/since.go
  10. +2
    -0
      options/locale/locale_en-US.ini
  11. +3
    -1
      options/locale/locale_zh-CN.ini
  12. BIN
      public/img/apple-touch-icon.png
  13. BIN
      public/img/avatar_default.png
  14. BIN
      public/img/favicon.png
  15. BIN
      public/img/gitea-192.png
  16. BIN
      public/img/gitea-512.png
  17. BIN
      public/img/gitea-lg.png
  18. BIN
      public/img/gitea-sm.png
  19. +17
    -1
      public/img/openi-safari.svg
  20. +6
    -1
      routers/home.go
  21. +13
    -4
      routers/repo/attachment.go
  22. +122
    -16
      routers/repo/cloudbrain.go
  23. +6
    -2
      routers/repo/modelarts.go
  24. +3
    -0
      routers/repo/view.go
  25. +5
    -1
      routers/routes/routes.go
  26. +6
    -0
      routers/user/setting/account.go
  27. +1
    -12
      routers/user/setting/profile.go
  28. +2
    -2
      services/mailer/mail.go
  29. +2
    -0
      templates/base/head_navbar.tmpl
  30. +2
    -0
      templates/base/head_navbar_home.tmpl
  31. +7
    -0
      templates/explore/images.tmpl
  32. +2
    -2
      templates/pwa/manifest_json.tmpl
  33. +51
    -8
      templates/repo/cloudbrain/new.tmpl
  34. +10
    -1
      templates/repo/home.tmpl
  35. +6
    -1
      templates/repo/modelarts/new.tmpl
  36. +2
    -1
      templates/user/settings/profile.tmpl
  37. +508
    -0
      web_src/js/components/Images.vue
  38. +21
    -4
      web_src/js/components/MinioUploader.vue
  39. +18
    -1
      web_src/js/index.js
  40. +0
    -2
      web_src/less/_base.less

+ 39
- 7
models/cloudbrain.go View File

@@ -72,11 +72,11 @@ type CloudBrainLoginResult struct {

type TaskRole struct {
Name string `json:"name"`
TaskNumber int8 `json:"taskNumber"`
MinSucceededTaskCount int8 `json:"minSucceededTaskCount"`
MinFailedTaskCount int8 `json:"minFailedTaskCount"`
CPUNumber int8 `json:"cpuNumber"`
GPUNumber int8 `json:"gpuNumber"`
TaskNumber int `json:"taskNumber"`
MinSucceededTaskCount int `json:"minSucceededTaskCount"`
MinFailedTaskCount int `json:"minFailedTaskCount"`
CPUNumber int `json:"cpuNumber"`
GPUNumber int `json:"gpuNumber"`
MemoryMB int `json:"memoryMB"`
ShmMB int `json:"shmMB"`
Command string `json:"command"`
@@ -123,8 +123,9 @@ type GetImagesResult struct {
}

type GetImagesPayload struct {
Count int `json:"count"`
ImageInfo []*ImageInfo `json:"rows"`
Count int `json:"count"`
TotalPages int `json:"totalPages,omitempty"`
ImageInfo []*ImageInfo `json:"rows"`
}

type CloudbrainsOptions struct {
@@ -286,6 +287,37 @@ type GpuInfo struct {
Queue string `json:"queue"`
}

type ResourceSpecs struct {
ResourceSpec []*ResourceSpec `json:"resorce_specs"`
}

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

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

type FlavorInfo struct {
Id int `json:"id"`
Value string `json:"value"`
}

type PoolInfos struct {
PoolInfo []*PoolInfo `json:"pool_info"`
}

type PoolInfo struct {
PoolId string `json:"pool_id"`
PoolName string `json:"pool_name"`
PoolType string `json:"pool_type"`
}

type CommitImageParams struct {
Ip string `json:"ip"`
TaskContainerId string `json:"taskContainerId"`


+ 11
- 1
models/file_chunk.go View File

@@ -87,7 +87,7 @@ func InsertFileChunk(fileChunk *FileChunk) (_ *FileChunk, err error) {
return fileChunk, nil
}

// UpdateAttachment updates the given attachment in database
// UpdateFileChunk updates the given file_chunk in database
func UpdateFileChunk(fileChunk *FileChunk) error {
return updateFileChunk(x, fileChunk)
}
@@ -98,3 +98,13 @@ func updateFileChunk(e Engine, fileChunk *FileChunk) error {
_, err := sess.Cols("is_uploaded").Update(fileChunk)
return err
}

// DeleteFileChunk delete the given file_chunk in database
func DeleteFileChunk(fileChunk *FileChunk) error {
return deleteFileChunk(x, fileChunk)
}

func deleteFileChunk(e Engine, fileChunk *FileChunk) error {
_, err := e.ID(fileChunk.ID).Delete(fileChunk)
return err
}

+ 10
- 0
models/user_mail.go View File

@@ -293,6 +293,16 @@ func MakeEmailPrimary(email *EmailAddress) error {
}

user.Email = email.Email

has, err = sess.Where("id!=?", user.ID).
And("type=?", user.Type).
And("email=?", strings.ToLower(user.Email)).
Get(new(User))
if err != nil {
return err
} else if has {
return ErrEmailAlreadyUsed{user.Email}
}
if _, err = sess.ID(user.ID).Cols("email").Update(user); err != nil {
return err
}


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

@@ -13,6 +13,7 @@ type CreateCloudBrainForm struct {
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"`
}

type CommitImageCloudBrainForm struct {


+ 22
- 5
modules/cloudbrain/cloudbrain.go View File

@@ -23,12 +23,29 @@ const (
Success = "S000"
)

func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath, modelPath, benchmarkPath, snn4imagenetPath, jobType, gpuQueue string) error {
var (
ResourceSpecs *models.ResourceSpecs
)

func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath, modelPath, benchmarkPath, snn4imagenetPath, jobType, gpuQueue string, resourceSpecId int) error {
dataActualPath := setting.Attachment.Minio.RealPath +
setting.Attachment.Minio.Bucket + "/" +
setting.Attachment.Minio.BasePath +
models.AttachmentRelativePath(uuid) +
uuid

var resourceSpec *models.ResourceSpec
for _, spec := range ResourceSpecs.ResourceSpec {
if resourceSpecId == spec.Id {
resourceSpec = spec
}
}

if resourceSpec == nil {
log.Error("no such resourceSpecId(%d)", resourceSpecId, ctx.Data["MsgID"])
return errors.New("no such resourceSpec")
}
jobResult, err := CreateJob(jobName, models.CreateJobParams{
JobName: jobName,
RetryCount: 1,
@@ -40,10 +57,10 @@ func GenerateTask(ctx *context.Context, jobName, image, command, uuid, codePath,
TaskNumber: 1,
MinSucceededTaskCount: 1,
MinFailedTaskCount: 1,
CPUNumber: 2,
GPUNumber: 1,
MemoryMB: 16384,
ShmMB: 8192,
CPUNumber: resourceSpec.CpuNum,
GPUNumber: resourceSpec.GpuNum,
MemoryMB: resourceSpec.MemMiB,
ShmMB: resourceSpec.ShareMemMiB,
Command: command,
NeedIBDevice: false,
IsMainRole: false,


+ 41
- 38
modules/cloudbrain/resty.go View File

@@ -1,9 +1,11 @@
package cloudbrain

import (
"code.gitea.io/gitea/modules/log"
"encoding/json"
"fmt"
"strings"

"code.gitea.io/gitea/modules/log"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/setting"
@@ -11,13 +13,16 @@ import (
)

var (
restyClient *resty.Client
HOST string
TOKEN string
restyClient *resty.Client
HOST string
TOKEN string
ImagesUrlMap = map[string]string{Public: "/rest-server/api/v1/image/public/list/", Custom: "/rest-server/api/v1/image/list/"}
)

const (
JobHasBeenStopped = "S410"
Public = "public"
Custom = "custom"
)

func getRestyClient() *resty.Client {
@@ -77,6 +82,12 @@ sendjob:
Post(HOST + "/rest-server/api/v1/jobs/")

if err != nil {
if res != nil {
var response models.CloudBrainResult
json.Unmarshal(res.Body(), &response)
log.Error("code(%s), msg(%s)", response.Code, response.Msg)
return nil, fmt.Errorf(response.Msg)
}
return nil, fmt.Errorf("resty create job: %s", err)
}

@@ -126,6 +137,16 @@ sendjob:
}

func GetImages() (*models.GetImagesResult, error) {

return GetImagesPageable(1, 100, Custom, "")

}

func GetPublicImages() (*models.GetImagesResult, error) {
return GetImagesPageable(1, 100, Public, "")
}

func GetImagesPageable(page int, size int, imageType string, name string) (*models.GetImagesResult, error) {
checkSetting()
client := getRestyClient()
var getImagesResult models.GetImagesResult
@@ -136,9 +157,9 @@ sendjob:
res, err := client.R().
SetHeader("Content-Type", "application/json").
SetAuthToken(TOKEN).
SetQueryString("pageIndex=1&pageSize=100").
SetQueryString(getQueryString(page, size, name)).
SetResult(&getImagesResult).
Get(HOST + "/rest-server/api/v1/image/list/")
Get(HOST + ImagesUrlMap[imageType])

if err != nil {
return nil, fmt.Errorf("resty GetImages: %v", err)
@@ -157,48 +178,30 @@ sendjob:
goto sendjob
}

if len(response.Code) != 0 {
log.Error("getImagesResult failed(%s): %s", response.Code, response.Msg)
return &getImagesResult, fmt.Errorf("getImagesResult failed(%s): %s", response.Code, response.Msg)
}

if getImagesResult.Code != Success {
return &getImagesResult, fmt.Errorf("getImagesResult err: %s", res.String())
}

getImagesResult.Payload.TotalPages = getTotalPages(getImagesResult, size)
return &getImagesResult, nil
}

func GetPublicImages() (*models.GetImagesResult, error) {
checkSetting()
client := getRestyClient()
var getImagesResult models.GetImagesResult

retry := 0

sendjob:
res, err := client.R().
SetHeader("Content-Type", "application/json").
SetAuthToken(TOKEN).
SetQueryString("pageIndex=1&pageSize=100").
SetResult(&getImagesResult).
Get(HOST + "/rest-server/api/v1/image/public/list/")

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

if getImagesResult.Code == "S401" && retry < 1 {
retry++
_ = loginCloudbrain()
goto sendjob
func getTotalPages(getImagesResult models.GetImagesResult, size int) int {
totalCount := getImagesResult.Payload.Count
var totalPages int
if totalCount%size != 0 {
totalPages = totalCount/size + 1
} else {
totalPages = totalCount / size
}
return totalPages
}

if getImagesResult.Code != Success {
return &getImagesResult, fmt.Errorf("getImgesResult err: %s", res.String())
func getQueryString(page int, size int, name string) string {
if strings.TrimSpace(name) == "" {
return fmt.Sprintf("pageIndex=%d&pageSize=%d", page, size)
}

return &getImagesResult, nil
return fmt.Sprintf("pageIndex=%d&pageSize=%d&name=%s", page, size, name)
}

func CommitImage(jobID string, params models.CommitImageParams) error {


+ 14
- 12
modules/modelarts/modelarts.go View File

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

import (
"code.gitea.io/gitea/modules/setting"
"encoding/json"
"path"

"code.gitea.io/gitea/models"
@@ -12,30 +13,31 @@ import (
const (
storageTypeOBS = "obs"
autoStopDuration = 4 * 60 * 60
flavor = "modelarts.kat1.xlarge"
//profileID = "Python3-ascend910-arm"
profileID = "efa847c0-7359-11eb-b34f-0255ac100057"
poolID = "pool1328035d"
poolName = "train-private-1"
poolType = "USER_DEFINED"

DataSetMountPath = "/home/ma-user/work"
NotebookEnv = "Python3"
NotebookType = "Ascend"
FlavorInfo = "Ascend: 1*Ascend 910 CPU: 24 核 96GiB (modelarts.kat1.xlarge)"
)

var (
poolInfos *models.PoolInfos
FlavorInfos *models.FlavorInfos
)

func GenerateTask(ctx *context.Context, jobName, uuid, description string) error {
dataActualPath := setting.Bucket + "/" + setting.BasePath + path.Join(uuid[0:1], uuid[1:2]) + "/" + uuid + "/"
if poolInfos == nil {
json.Unmarshal([]byte(setting.PoolInfos), &poolInfos)
}
jobResult, err := CreateJob(models.CreateNotebookParams{
JobName: jobName,
Description: description,
ProfileID: profileID,
Flavor: flavor,
ProfileID: setting.ProfileID,
Flavor: setting.Flavor,
Pool: models.Pool{
ID: poolID,
Name: poolName,
Type: poolType,
ID: poolInfos.PoolInfo[0].PoolId,
Name: poolInfos.PoolInfo[0].PoolName,
Type: poolInfos.PoolInfo[0].PoolType,
},
Spec: models.Spec{
Storage: models.Storage{


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

@@ -439,6 +439,7 @@ var (
JobType string
GpuTypes string
DebugServerHost string
ResourceSpecs string

//benchmark config
IsBenchmarkEnabled bool
@@ -472,6 +473,10 @@ var (
ModelArtsUsername string
ModelArtsPassword string
ModelArtsDomain string
ProfileID string
PoolInfos string
Flavor string
FlavorInfos string
)

// DateLang transforms standard language locale name to corresponding value in datetime plugin.
@@ -1147,7 +1152,8 @@ func NewContext() {
JobPath = sec.Key("JOB_PATH").MustString("/datasets/minio/data/opendata/jobs/")
DebugServerHost = sec.Key("DEBUG_SERVER_HOST").MustString("http://192.168.202.73")
JobType = sec.Key("GPU_TYPE_DEFAULT").MustString("openidebug")
GpuTypes = sec.Key("GPU_TYPES").MustString("openidebug,openidgx")
GpuTypes = sec.Key("GPU_TYPES").MustString("")
ResourceSpecs = sec.Key("RESOURCE_SPECS").MustString("")

sec = Cfg.Section("benchmark")
IsBenchmarkEnabled = sec.Key("ENABLED").MustBool(false)
@@ -1180,6 +1186,10 @@ func NewContext() {
ModelArtsUsername = sec.Key("USERNAME").MustString("")
ModelArtsPassword = sec.Key("PASSWORD").MustString("")
ModelArtsDomain = sec.Key("DOMAIN").MustString("cn-south-222")
ProfileID = sec.Key("PROFILE_ID").MustString("")
PoolInfos = sec.Key("POOL_INFOS").MustString("")
Flavor = sec.Key("FLAVOR").MustString("")
FlavorInfos = sec.Key("FLAVOR_INFOS").MustString("")
}

func loadInternalToken(sec *ini.Section) string {


+ 0
- 0
modules/timeutil/since.go View File


+ 2
- 0
options/locale/locale_en-US.ini View File

@@ -223,6 +223,7 @@ issues.in_your_repos = In your repositories
repos = Repositories
users = Users
organizations = Organizations
images = CloudImages
search = Search
code = Code
repo_no_results = No matching repositories found.
@@ -767,6 +768,7 @@ cloudbrain2 = cloudbrain2
cloudbrain_selection = select cloudbrain
cloudbrain_platform_selection = Select the cloudbrain platform you want to use:
confirm_choice = confirm
cloudbran1_tips = Only data in zip format can create cloudbrain tasks

template.items = Template Items
template.git_content = Git Content (Default Branch)


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

@@ -224,6 +224,7 @@ issues.in_your_repos=属于该用户项目的
repos=项目
users=用户
organizations=组织
images = 云脑镜像
search=搜索
code=代码
repo_no_results=未找到匹配的项目。
@@ -478,7 +479,7 @@ add_new_email=添加新的邮箱地址
add_new_openid=添加新的 OpenID URI
add_email=增加电子邮件地址
add_openid=添加 OpenID URI
add_email_confirmation_sent=一封新的确认邮件已经被发送至 <b>%s</b>,请检查您的收件箱并在 %s 内完成确认注册操作。
add_email_confirmation_sent=一封新的确认邮件已经被发送至 <b>%s</b>,请检查您的收件箱并在 %s 内完成确认操作。
add_email_success=新的电子邮件地址已添加。
email_preference_set_success=电子邮件首选项已成功设置。
add_openid_success=新的 OpenID 地址已添加。
@@ -769,6 +770,7 @@ cloudbrain2=云脑2
cloudbrain_selection=云脑选择
cloudbrain_platform_selection=选择您准备使用的云脑平台:
confirm_choice=确定
cloudbran1_tips=只有zip格式的数据集才能发起云脑任务

template.items=模板选项
template.git_content=Git数据(默认分支)


BIN
public/img/apple-touch-icon.png View File

Before After
Width: 180  |  Height: 180  |  Size: 2.0 kB Width: 180  |  Height: 180  |  Size: 5.0 kB

BIN
public/img/avatar_default.png View File

Before After
Width: 200  |  Height: 200  |  Size: 2.5 kB Width: 200  |  Height: 200  |  Size: 7.9 kB

BIN
public/img/favicon.png View File

Before After
Width: 180  |  Height: 180  |  Size: 2.2 kB Width: 180  |  Height: 180  |  Size: 7.2 kB

BIN
public/img/gitea-192.png View File

Before After
Width: 192  |  Height: 192  |  Size: 2.4 kB Width: 192  |  Height: 192  |  Size: 7.6 kB

BIN
public/img/gitea-512.png View File

Before After
Width: 512  |  Height: 512  |  Size: 5.3 kB Width: 512  |  Height: 512  |  Size: 21 kB

BIN
public/img/gitea-lg.png View File

Before After
Width: 880  |  Height: 880  |  Size: 9.1 kB Width: 880  |  Height: 880  |  Size: 38 kB

BIN
public/img/gitea-sm.png View File

Before After
Width: 120  |  Height: 120  |  Size: 1.6 kB Width: 120  |  Height: 120  |  Size: 4.9 kB

+ 17
- 1
public/img/openi-safari.svg View File

@@ -1 +1,17 @@
<svg clip-rule="evenodd" fill-rule="evenodd" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="m83.046 35.842c10.395-.091 22.52-.636 42.972-1.719 12.367.282-4.316 74.37-26.594 76.038h-37.152c-8.48-.911-19.934-18.203-21.002-23.216-9.735-.215-34.086-2.695-35.192-29.423-.707-17.106 12.085-23.932 21.632-23.904 3.736.01 7.213.478 9.708.505 19.935 1.104 30.433 1.729 42.366 1.733l.021 21.503 3.241 1.61zm18.936 34.047-31.267-15.193-15.192 31.267 31.267 15.192zm-71.781-26.813c-12.443-.333-15.784 7.423-14.833 14.834 1.782 13.885 11.469 17.605 22.851 19.11-4.462-12.24-6.666-20.251-8.018-33.944z" transform="matrix(.128847 0 0 .128847 -.779534 -1.26275)"/><g transform="matrix(.128847 0 0 .128847 -.779534 -1.26275)"><g transform="matrix(.940904 -.338674 .338674 .940904 0 0)"><circle cx="49.065" cy="90.078" r="3.475"/><circle cx="36.81" cy="102.105" r="3.475"/><circle cx="46.484" cy="111.439" r="3.475"/></g><path d="m97.333 18.062h2.673v27.261h-2.673z" transform="matrix(.898609 .43875 -.447723 .916987 .406685 -.832937)"/><path d="m76.558 68.116c12.976 6.396 13.013 4.102 4.891 20.908" fill="none" stroke="#000" stroke-width="2.68"/></g></svg>
<svg clip-rule="evenodd" fill-rule="evenodd" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg">
<path class="st0" d="M54.7,15.9L34.8,4.5c-1.7-1-3.9-1-5.6,0L9.3,15.9c-1.8,1-2.8,2.9-2.8,4.9v22.8c0,2,1.1,3.9,2.8,4.9l4.9,2.8
c-0.1,0.2-0.1,0.5-0.1,0.7c0,2.1,1.6,3.8,3.7,3.8s3.8-1.6,3.8-3.7c0-2.1-1.6-3.8-3.7-3.8c-0.9,0-1.8,0.3-2.5,0.9l-4.9-2.8
c-1-0.6-1.6-1.7-1.6-2.9V20.9c0-1.2,0.6-2.3,1.6-2.9L30.3,6.5c1-0.6,2.3-0.6,3.3,0l19.8,11.4c1,0.6,1.7,1.7,1.6,2.9v22.8
c0,1.2-0.6,2.3-1.6,2.9L33.7,58c-0.2,0.1-0.4,0.2-0.6,0.3v-7.1c0-0.1,0-0.2,0-0.4v-4.1h0.1l11.4-7.6c0.3-0.2,0.5-0.6,0.5-1V25.9
c2-0.6,3.1-2.8,2.5-4.8c-0.6-2-2.8-3.1-4.8-2.5c-2,0.6-3.1,2.8-2.5,4.8c0.4,1.2,1.3,2.1,2.5,2.5v11.4L33,43.9V20.8
c2-0.6,3.1-2.8,2.5-4.8c-0.6-2-2.8-3.1-4.8-2.5c-2,0.6-3.1,2.8-2.5,4.8c0.4,1.2,1.3,2.1,2.5,2.5V36l-6.4-3.7v-6.1
c2-0.6,3.1-2.8,2.5-4.8c-0.6-2-2.8-3.1-4.8-2.5s-3.1,2.8-2.5,4.8c0.4,1.2,1.3,2.1,2.5,2.5v6.7c0,0.4,0.2,0.8,0.6,1l8.2,4.8V49
l-14.1-8.2v-10c2-0.6,3-2.7,2.4-4.7s-2.7-3-4.7-2.4c-2,0.6-3,2.7-2.4,4.7c0.4,1.1,1.3,2,2.4,2.4v10.7c0,0.4,0.2,0.8,0.6,1l15.8,9.1
v8c0,0,0,0.1,0,0.1v0.1V60c0,0,0,0,0,0.1l0.1,0.1l0,0l0.1,0.1l0,0c0,0,0.1,0.1,0.1,0.1l0,0l0.2,0.1l0,0h0.2l0,0c0.1,0,0.1,0,0.2,0
h0.2c1,0,2-0.3,2.8-0.7l19.8-11.4c1.8-0.9,2.9-2.7,3.1-4.7V20.9C57.5,18.9,56.4,17,54.7,15.9 M17.9,50.8c0.8,0,1.4,0.6,1.4,1.4
s-0.6,1.4-1.4,1.4s-1.4-0.6-1.4-1.4l0,0C16.5,51.4,17.1,50.8,17.9,50.8 M44,21c0.8,0,1.4,0.6,1.4,1.4c0,0.8-0.6,1.4-1.4,1.4
c-0.8,0-1.4-0.6-1.4-1.4l0,0C42.6,21.7,43.2,21,44,21 M23.1,21.3c0.8,0,1.4,0.6,1.4,1.4s-0.6,1.4-1.4,1.4c-0.8,0-1.4-0.6-1.4-1.4
S22.4,21.3,23.1,21.3L23.1,21.3 M15.5,25.9c0.8,0,1.4,0.6,1.4,1.4s-0.6,1.4-1.4,1.4c-0.8,0-1.4-0.6-1.4-1.4S14.7,25.9,15.5,25.9
L15.5,25.9 M31.9,15.8c0.8,0,1.4,0.6,1.4,1.4s-0.6,1.4-1.4,1.4c-0.8,0-1.4-0.6-1.4-1.4C30.4,16.4,31.1,15.8,31.9,15.8
C31.9,15.8,31.9,15.8,31.9,15.8"/>
</svg>

+ 6
- 1
routers/home.go View File

@@ -32,7 +32,8 @@ const (
// tplExploreOrganizations explore organizations page template
tplExploreOrganizations base.TplName = "explore/organizations"
// tplExploreCode explore code page template
tplExploreCode base.TplName = "explore/code"
tplExploreCode base.TplName = "explore/code"
tplExploreImages base.TplName = "explore/images"
)

// Home render home page
@@ -475,6 +476,10 @@ func ExploreCode(ctx *context.Context) {
ctx.HTML(200, tplExploreCode)
}

func ExploreImages(ctx *context.Context) {
ctx.HTML(200, tplExploreImages)
}

// NotFound render 404 page
func NotFound(ctx *context.Context) {
ctx.Data["Title"] = "Page Not Found"


+ 13
- 4
routers/repo/attachment.go View File

@@ -483,16 +483,25 @@ func GetSuccessChunks(ctx *context.Context) {
if typeCloudBrain == models.TypeCloudBrainOne {
chunks, err = storage.GetPartInfos(fileChunk.UUID, fileChunk.UploadID)
if err != nil {
ctx.ServerError("GetPartInfos failed", err)
return
log.Error("GetPartInfos failed:%v", err.Error())
}
} else {
chunks, err = storage.GetObsPartInfos(fileChunk.UUID, fileChunk.UploadID)
if err != nil {
ctx.ServerError("GetObsPartInfos failed", err)
return
log.Error("GetObsPartInfos failed:%v", err.Error())
}
}

if err != nil {
models.DeleteFileChunk(fileChunk)
ctx.JSON(200, map[string]string{
"uuid": "",
"uploaded": "0",
"uploadID": "",
"chunks": "",
})
return
}
}

var attachID int64


+ 122
- 16
routers/repo/cloudbrain.go View File

@@ -1,17 +1,21 @@
package repo

import (
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/storage"
"bufio"
"encoding/json"
"errors"
"io"
"net/http"
"os"
"os/exec"
"regexp"
"strconv"
"strings"
"time"

"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/storage"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/base"
@@ -22,10 +26,10 @@ 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"
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"
)

var (
@@ -86,17 +90,27 @@ func cutString(str string, lens int) string {
return str[:lens]
}

func CloudBrainNew(ctx *context.Context) {
ctx.Data["PageIsCloudBrain"] = true
func jobNamePrefixValid(s string) string {
lowStr := strings.ToLower(s)
re := regexp.MustCompile(`[^a-z0-9_\\-]+`)
removeSpecial := re.ReplaceAllString(lowStr, "")
re = regexp.MustCompile(`^[_\\-]+`)
return re.ReplaceAllString(removeSpecial, "")

}

func cloudBrainNewDataPrepare(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:]
var jobName = jobNamePrefixValid(cutString(ctx.User.Name, 5)) + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:]
ctx.Data["job_name"] = jobName

result, err := cloudbrain.GetImages()
if err != nil {
ctx.Data["error"] = err.Error()
log.Error("cloudbrain.GetImages failed:", err.Error(), ctx.Data["msgID"])
log.Error("cloudbrain.GetImages failed:", err.Error(), ctx.Data["MsgID"])
}

for i, payload := range result.Payload.ImageInfo {
@@ -112,7 +126,7 @@ func CloudBrainNew(ctx *context.Context) {
resultPublic, err := cloudbrain.GetPublicImages()
if err != nil {
ctx.Data["error"] = err.Error()
log.Error("cloudbrain.GetPublicImages failed:", err.Error(), ctx.Data["msgID"])
log.Error("cloudbrain.GetPublicImages failed:", err.Error(), ctx.Data["MsgID"])
}

for i, payload := range resultPublic.Payload.ImageInfo {
@@ -127,8 +141,8 @@ func CloudBrainNew(ctx *context.Context) {

attachs, err := models.GetAllUserAttachments(ctx.User.ID)
if err != nil {
ctx.ServerError("GetAllUserAttachments failed:", err)
return
log.Error("GetAllUserAttachments failed: %v", err, ctx.Data["MsgID"])
return err
}

ctx.Data["attachments"] = attachs
@@ -148,8 +162,23 @@ func CloudBrainNew(ctx *context.Context) {
json.Unmarshal([]byte(setting.GpuTypes), &gpuInfos)
}
ctx.Data["gpu_types"] = gpuInfos.GpuInfo

if cloudbrain.ResourceSpecs == nil {
json.Unmarshal([]byte(setting.ResourceSpecs), &cloudbrain.ResourceSpecs)
}
ctx.Data["resource_specs"] = cloudbrain.ResourceSpecs.ResourceSpec
ctx.Data["snn4imagenet_path"] = cloudbrain.Snn4imagenetMountPath
ctx.Data["is_snn4imagenet_enabled"] = setting.IsSnn4imagenetEnabled

return nil
}

func CloudBrainNew(ctx *context.Context) {
err := cloudBrainNewDataPrepare(ctx)
if err != nil {
ctx.ServerError("get new cloudbrain info failed", err)
return
}
ctx.HTML(200, tplCloudBrainNew)
}

@@ -162,9 +191,11 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {
jobType := form.JobType
gpuQueue := setting.JobType
codePath := setting.JobPath + jobName + cloudbrain.CodeMountPath
resourceSpecId := form.ResourceSpecId

if jobType != string(models.JobTypeBenchmark) && jobType != string(models.JobTypeDebug) && jobType != string(models.JobTypeSnn4imagenet) {
log.Error("jobtype error:", jobType, ctx.Data["msgID"])
log.Error("jobtype error:", jobType, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr("jobtype error", tplCloudBrainNew, &form)
return
}
@@ -172,11 +203,13 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {
_, 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", tplCloudBrainNew, &form)
return
} else {
if !models.IsErrJobNotExist(err) {
log.Error("system error, %v", err, ctx.Data["MsgID"])
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr("system error", tplCloudBrainNew, &form)
return
}
@@ -187,6 +220,7 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {
modelPath := setting.JobPath + jobName + cloudbrain.ModelMountPath
err = os.MkdirAll(modelPath, os.ModePerm)
if err != nil {
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(err.Error(), tplCloudBrainNew, &form)
return
}
@@ -208,8 +242,9 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) {
downloadRateCode(repo, jobName, setting.Snn4imagenetCode, snn4imagenetPath, "", "")
}

err = cloudbrain.GenerateTask(ctx, jobName, image, command, uuid, codePath, modelPath, benchmarkPath, snn4imagenetPath, jobType, gpuQueue)
err = cloudbrain.GenerateTask(ctx, jobName, image, command, uuid, codePath, modelPath, benchmarkPath, snn4imagenetPath, jobType, gpuQueue, resourceSpecId)
if err != nil {
cloudBrainNewDataPrepare(ctx)
ctx.RenderWithErr(err.Error(), tplCloudBrainNew, &form)
return
}
@@ -384,6 +419,38 @@ func CloudBrainShowModels(ctx *context.Context) {
ctx.HTML(200, tplCloudBrainShowModels)
}

func GetPublicImages(ctx *context.Context) {

getImages(ctx, cloudbrain.Public)

}

func GetCustomImages(ctx *context.Context) {

getImages(ctx, cloudbrain.Custom)

}

func getImages(ctx *context.Context, imageType string) {
log.Info("Get images begin")

page := ctx.QueryInt("page")
size := ctx.QueryInt("size")
name := ctx.Query("name")
getImagesResult, err := cloudbrain.GetImagesPageable(page, size, imageType, name)
if err != nil {
log.Error("Can not get images:%v", err)
ctx.JSON(http.StatusOK, models.GetImagesPayload{
Count: 0,
TotalPages: 0,
ImageInfo: []*models.ImageInfo{},
})
} else {
ctx.JSON(http.StatusOK, getImagesResult.Payload)
}
log.Info("Get images end")
}

func getModelDirs(jobName string, parentDir string) (string, error) {
var req string
modelActualPath := setting.JobPath + jobName + "/model/"
@@ -400,7 +467,7 @@ func CloudBrainDownloadModel(ctx *context.Context) {
parentDir := ctx.Query("parentDir")
fileName := ctx.Query("fileName")
jobName := ctx.Query("jobName")
filePath := "jobs/" +jobName + "/model/" + parentDir
filePath := "jobs/" + jobName + "/model/" + parentDir
url, err := storage.Attachments.PresignedGetURL(filePath, fileName)
if err != nil {
log.Error("PresignedGetURL failed: %v", err.Error(), ctx.Data["msgID"])
@@ -434,6 +501,45 @@ func downloadCode(repo *models.Repository, codePath string) error {
return err
}

configFile, err := os.OpenFile(codePath + "/.git/config", os.O_RDWR, 0666)
if err != nil {
log.Error("open file(%s) failed:%v", codePath + "/,git/config", err)
return err
}

defer configFile.Close()

pos := int64(0)
reader := bufio.NewReader(configFile)
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
log.Error("not find the remote-url")
return nil
} else {
log.Error("read error: %v", err)
return err
}
}

if strings.Contains(line, "url") && strings.Contains(line, ".git"){
originUrl := "\turl = " + repo.CloneLink().HTTPS + "\n"
if len(line) > len(originUrl) {
originUrl += strings.Repeat( " ", len(line) - len(originUrl))
}
bytes := []byte(originUrl)
_, err := configFile.WriteAt(bytes, pos)
if err != nil {
log.Error("WriteAt failed:%v", err)
return err
}
break
}

pos += int64(len(line))
}

return nil
}



+ 6
- 2
routers/repo/modelarts.go View File

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

import (
"code.gitea.io/gitea/modules/modelarts"
"encoding/json"
"errors"
"github.com/unknwon/com"
"strconv"
@@ -71,7 +72,7 @@ func ModelArtsNew(ctx *context.Context) {
ctx.Data["PageIsCloudBrain"] = true

t := time.Now()
var jobName = cutString(ctx.User.Name, 5) + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:]
var jobName = jobNamePrefixValid(cutString(ctx.User.Name, 5)) + t.Format("2006010215") + strconv.Itoa(int(t.Unix()))[5:]
ctx.Data["job_name"] = jobName

attachs, err := models.GetModelArtsUserAttachments(ctx.User.ID)
@@ -84,7 +85,10 @@ func ModelArtsNew(ctx *context.Context) {
ctx.Data["dataset_path"] = modelarts.DataSetMountPath
ctx.Data["env"] = modelarts.NotebookEnv
ctx.Data["notebook_type"] = modelarts.NotebookType
ctx.Data["flavor"] = modelarts.FlavorInfo
if modelarts.FlavorInfos == nil {
json.Unmarshal([]byte(setting.FlavorInfos), &modelarts.FlavorInfos)
}
ctx.Data["flavors"] = modelarts.FlavorInfos.FlavorInfo
ctx.HTML(200, tplModelArtsNew)
}



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

@@ -590,6 +590,9 @@ func Home(ctx *context.Context) {
if err == nil && contributors != nil {
var contributorInfos []*ContributorInfo
for _, c := range contributors {
if strings.Compare(c.Email,"") == 0 {
continue
}
// get user info from committer email
user, err := models.GetUserByEmail(c.Email)
if err == nil {


+ 5
- 1
routers/routes/routes.go View File

@@ -6,13 +6,14 @@ package routes

import (
"bytes"
"code.gitea.io/gitea/routers/secure"
"encoding/gob"
"net/http"
"path"
"text/template"
"time"

"code.gitea.io/gitea/routers/secure"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
"code.gitea.io/gitea/modules/context"
@@ -313,11 +314,14 @@ func RegisterRoutes(m *macaron.Macaron) {
m.Get("", func(ctx *context.Context) {
ctx.Redirect(setting.AppSubURL + "/explore/repos")
})
m.Get("/images/public", repo.GetPublicImages)
m.Get("/images/custom", repo.GetCustomImages)
m.Get("/repos", routers.ExploreRepos)
m.Get("/datasets", routers.ExploreDatasets)
m.Get("/users", routers.ExploreUsers)
m.Get("/organizations", routers.ExploreOrganizations)
m.Get("/code", routers.ExploreCode)
m.Get("/images", routers.ExploreImages)
}, ignSignIn)
m.Combo("/install", routers.InstallInit).Get(routers.Install).
Post(bindIgnErr(auth.InstallForm{}), routers.InstallPost)


+ 6
- 0
routers/user/setting/account.go View File

@@ -80,6 +80,12 @@ func EmailPost(ctx *context.Context, form auth.AddEmailForm) {
// Make emailaddress primary.
if ctx.Query("_method") == "PRIMARY" {
if err := models.MakeEmailPrimary(&models.EmailAddress{ID: ctx.QueryInt64("id")}); err != nil {
if _, ok := err.(models.ErrEmailAlreadyUsed); ok {
ctx.Flash.Error(ctx.Tr("form.email_been_used"))
ctx.Redirect(setting.AppSubURL + "/user/settings/account")
return
}

ctx.ServerError("MakeEmailPrimary", err)
return
}


+ 1
- 12
routers/user/setting/profile.go View File

@@ -90,23 +90,12 @@ func ProfilePost(ctx *context.Context, form auth.UpdateProfileForm) {
}

ctx.User.FullName = form.FullName
ctx.User.Email = form.Email
ctx.User.KeepEmailPrivate = form.KeepEmailPrivate
ctx.User.Website = form.Website
ctx.User.Location = form.Location
ctx.User.Language = form.Language
ctx.User.Description = form.Description
isUsed, err := models.IsEmailUsed(form.Email)
if err != nil {
ctx.ServerError("IsEmailUsed", err)
return
}

if isUsed {
ctx.Flash.Error(ctx.Tr("form.email_been_used"))
ctx.Redirect(setting.AppSubURL + "/user/settings")
return
}

if err := models.UpdateUserSetting(ctx.User); err != nil {
if _, ok := err.(models.ErrEmailAlreadyUsed); ok {


+ 2
- 2
services/mailer/mail.go View File

@@ -59,7 +59,7 @@ func SendTestMail(email string) error {
func SendUserMail(language string, u *models.User, tpl base.TplName, code, subject, info string) {
data := map[string]interface{}{
"DisplayName": u.DisplayName(),
"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, language),
"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, "en-US"),
"ResetPwdCodeLives": timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, language),
"Code": code,
}
@@ -97,7 +97,7 @@ func SendResetPasswordMail(locale Locale, u *models.User) {
func SendActivateEmailMail(locale Locale, u *models.User, email *models.EmailAddress) {
data := map[string]interface{}{
"DisplayName": u.DisplayName(),
"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale.Language()),
"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, "en-US"),
"Code": u.GenerateEmailActivateCode(email.Email),
"Email": email.Email,
}


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

@@ -28,6 +28,7 @@
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
</div>
</div>
{{else if .IsLandingPageHome}}
@@ -42,6 +43,7 @@
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "datasets"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
</div>
</div>
{{else if .IsLandingPageExplore}}


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

@@ -28,6 +28,7 @@
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "custom.head.dataset"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
</div>
</div>
{{else if .IsLandingPageHome}}
@@ -42,6 +43,7 @@
<a class="item" href="{{AppSubUrl}}/explore/datasets">{{.i18n.Tr "datasets"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/users">{{.i18n.Tr "explore.users"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/organizations">{{.i18n.Tr "explore.organizations"}}</a>
<a class="item" href="{{AppSubUrl}}/explore/images">{{.i18n.Tr "explore.images"}}</a>
</div>
</div>
{{else if .IsLandingPageExplore}}


+ 7
- 0
templates/explore/images.tmpl View File

@@ -0,0 +1,7 @@
{{template "base/head" .}}
<div id="images">

</div>

{{template "base/footer" .}}

+ 2
- 2
templates/pwa/manifest_json.tmpl View File

@@ -1,6 +1,6 @@
{
"short_name": "Gitea",
"name": "Gitea - Git with a cup of tea",
"short_name": "OpenI",
"name": "OpenI - 启智AI开发协作平台",
"icons": [
{
"src": "{{StaticUrlPrefix}}/img/gitea-lg.png",


+ 51
- 8
templates/repo/cloudbrain/new.tmpl View File

@@ -110,12 +110,16 @@
<div class="rect5"></div>
</div>
</div>

<div class="repository">
{{template "repo/header" .}}
<div class="repository new repo ui middle very relaxed page grid">
<div class="column">
{{template "base/alert" .}}
<form class="ui form" action="{{.Link}}" method="post">
<div class="ui positive message" id="messageInfo">
<p></p>
</div>
<form id="form_id" class="ui form" action="{{.Link}}" method="post">
{{.CsrfTokenHtml}}
<h3 class="ui top attached header">
{{.i18n.Tr "repo.cloudbrain.new"}}
@@ -161,7 +165,7 @@

<div class="inline required field">
<label>镜像</label>
<input type="text" list="cloudbrain_image" placeholder="选择镜像" name="image">
<input type="text" list="cloudbrain_image" placeholder="选择镜像" name="image" required autofocus maxlength="255">
<datalist class="ui search" id="cloudbrain_image" style='width:385px;' name="image">
{{range .images}}
<option name="image" value="{{.Place}}">{{.PlaceView}}</option>
@@ -181,17 +185,26 @@
</select>
</div>

<div class="inline required field">
<label>资源规格</label>
<select id="cloudbrain_resource_spec" class="ui search dropdown" placeholder="选择资源规格" style='width:385px' name="resource_spec_id">
{{range .resource_specs}}
<option name="resource_spec_id" value="{{.Id}}">GPU数:{{.GpuNum}},CPU数:{{.CpuNum}},内存(MB):{{.MemMiB}},共享内存(MB):{{.ShareMemMiB}}</option>
{{end}}
</select>
</div>

<div class="inline required field">
<label>数据集存放路径</label>
<input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" autofocus required maxlength="255" readonly="readonly">
<input name="dataset_path" id="cloudbrain_dataset_path" value="{{.dataset_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div>
<div class="inline required field">
<label>模型存放路径</label>
<input name="model_path" id="cloudbrain_model_path" value="{{.model_path}}" tabindex="3" autofocus required maxlength="255" readonly="readonly">
<input name="model_path" id="cloudbrain_model_path" value="{{.model_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div>
<div class="inline required field">
<label>代码存放路径</label>
<input name="code_path" id="cloudbrain_code_path" value="{{.code_path}}" tabindex="3" autofocus required maxlength="255" readonly="readonly">
<input name="code_path" id="cloudbrain_code_path" value="{{.code_path}}" tabindex="3" disabled autofocus required maxlength="255" readonly="readonly">
</div>
<div class="inline required field cloudbrain_benchmark">
<label>benchmark脚本存放路径</label>
@@ -208,13 +221,15 @@

<div class="inline field">
<label></label>
<button class="ui green button" onclick="showmask()">
<button class="ui green button" >
{{.i18n.Tr "repo.cloudbrain.new"}}
</button>
<a class="ui button" href="/">{{.i18n.Tr "repo.cloudbrain.cancel"}}</a>
</div>
</div>
</form>

</div>
</div>
</div>
@@ -222,9 +237,37 @@
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<script>
// 点击按钮后遮罩层显示
function showmask() {

let url_href = window.location.pathname.split('create')[0]
$(".ui.button").attr('href',url_href)
let form = document.getElementById('form_id');
let value_image = $("input[name='image']").val()
console.log("value_image",$("input[name='image']").val())
$('#messageInfo').css('display','none')

form.onsubmit = function(e){
let value_task = $("input[name='job_name']").val()
let value_image = $("input[name='image']").val()
let re = /^[a-z0-9][a-z0-9-_]{1,36}$/
let flag = re.test(value_task)
if(!flag){
$('#messageInfo').css('display','block')
let str = '只能以小写字母或数字开头且只包含小写字母、数字、_和-、最长36个字符。'
$('#messageInfo p').text(str)
return false
}
if(!value_image){
return false
}
let min_value_task = value_task.toLowerCase()
console.log(min_value_task)
$("input[name='job_name']").attr("value",min_value_task)
document.getElementById("mask").style.display = "block"
}

// 页面加载完毕后遮罩层隐藏


+ 10
- 1
templates/repo/home.tmpl View File

@@ -12,6 +12,15 @@
float: left;
margin: .25em;
}
.edit-link{
vertical-align: top;
display: inline-block;
overflow: hidden;
word-break: keep-all;
white-space: nowrap;
text-overflow: ellipsis;
width: 16.5em;
}
#contributorInfo > a.circular{
height: 2.0em;
padding: 0;
@@ -218,7 +227,7 @@
{{if .Repository.Website}}
<p class="ui">
<i class="gray linkify icon"></i>
<a class="link" target="_blank" href="{{.Repository.Website}}">{{.Repository.Website}}</a>
<a class="link edit-link" target="_blank" title="{{.Repository.Website}}" href="{{.Repository.Website}}">{{.Repository.Website}}</a>
</p>
{{end}}


+ 6
- 1
templates/repo/modelarts/new.tmpl View File

@@ -132,7 +132,12 @@
</div>
<div class="inline required field">
<label>规格</label>
<input name="flavor" id="cloudbrain_flavor" value="{{.flavor}}" tabindex="3" autofocus required maxlength="255" readonly="readonly">
<select id="cloudbrain_flavor" class="ui search dropdown" placeholder="选择规格" style='width:385px' name="flavor">
{{range .flavors}}
<option name="flavor" value="{{.Value}}">{{.Value}}</option>

{{end}}
</select>
</div>
<div class="inline required field">
<label>数据集存放路径</label>


+ 2
- 1
templates/user/settings/profile.tmpl View File

@@ -23,7 +23,8 @@
</div>
<div class="required field {{if .Err_Email}}error{{end}}">
<label for="email">{{.i18n.Tr "email"}}</label>
<input id="email" name="email" value="{{.SignedUser.Email}}">
<input type="hidden" id="email" name="email" value="{{.SignedUser.Email}}" >
<input disabled value="{{.SignedUser.Email}}">
</div>
<div class="inline field">
<div class="ui checkbox" id="keep-email-private">


+ 508
- 0
web_src/js/components/Images.vue View File

@@ -0,0 +1,508 @@
<template>
<div>
<div class="header-wrapper">
<div class="ui container">
<el-row class="image_text">
<h1>云脑镜像</h1>
</el-row>
</div>
</div>
<div class="ui container" id="header">
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="公共镜像(云脑1)" name="first" v-loading="loading">
<div class="ui sixteen wide column">
<div class="ui two column stackable grid">
<div class="column">
<el-input placeholder="请输入镜像名称关健词" v-model="search" class="input-with-select" @keyup.enter.native="searchName()">
<el-button id="success" slot="append" icon="el-icon-search" @click="searchName()">搜索</el-button>
</el-input>
</div>

<!-- <div class="column right aligned">
<el-dropdown>
<span class="el-dropdown-link">
排序<i class="el-icon-caret-bottom"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>最早创建</el-dropdown-item>
<el-dropdown-item>最新创建</el-dropdown-item>
<el-dropdown-item divided>按镜像字母顺序排序</el-dropdown-item>
<el-dropdown-item>按镜像字母逆序排序</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div> -->
</div>
</div>

<el-row style="margin-top:15px;">

<el-table
:data="tableData"
style="width: 100%"
:header-cell-style="tableHeaderStyle"
:default-sort="{prop:'createtime',order:'descending'}">
<el-table-column
label="镜像名称"
width="350"
align="left"
prop="name"
sortable
>
<template slot-scope="scope">
<a class="text-over" style="cursor:default;color:#426290" :title="scope.row.name">{{ scope.row.name }}</a>
</template>
</el-table-column>
<el-table-column
label="文件路径/镜像描述"
width="450"
align="left"
>
<template slot-scope="scope">
<el-tooltip class="item" effect="dark" content="点击复制文件路径" placement="top">
<a class="text-over" style="display:block;" @click="copyUrl(scope.row.place)">{{ scope.row.place }}</a>
</el-tooltip>
<span class="text-over" :title="scope.row.description | clearP">{{ scope.row.description | clearP}}</span>
</template>
</el-table-column>
<el-table-column
prop="provider"
label="提供者"
width="120"
align="left"
sortable>
</el-table-column>
<el-table-column
prop="createtime"
label="创建时间"
align="center"
sortable>
<template slot-scope="scope">
{{scope.row.createtime | transformTimestamp}}
</template>
</el-table-column>
</el-table>
</el-row>
<div class="ui container" style="margin-top:50px;text-align:center">
<el-pagination
background
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="currentPage"
:page-size="pageSize"
:page-sizes="[5,10,20]"
layout="total, sizes, prev, pager, next, jumper"
:total="totalNum">
</el-pagination>
</div>
</el-tab-pane>
<el-tab-pane label="自定义镜像(云脑1)" name="second" v-loading="loading1">
<div class="ui sixteen wide column">
<div class="ui two column stackable grid">
<div class="column">
<el-input placeholder="请输入镜像名称关健词" v-model="search" class="input-with-select" @keyup.enter.native="searchName()">
<el-button slot="append" id="success" icon="el-icon-search" @click="searchName()">搜索</el-button>
</el-input>
</div>

<!-- <div class="column right aligned">
<el-dropdown>
<span class="el-dropdown-link">
排序<i class="el-icon-caret-bottom"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>最早创建</el-dropdown-item>
<el-dropdown-item>最新创建</el-dropdown-item>
<el-dropdown-item divided>按镜像字母顺序排序</el-dropdown-item>
<el-dropdown-item>按镜像字母逆序排序</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div> -->
</div>
</div>

<el-row style="margin-top:15px;">

<el-table
:data="tableData1"
style="width: 100%"
:header-cell-style="tableHeaderStyle"
:default-sort="{prop:'createtime',order:'descending'}">
<el-table-column
label="镜像名称"
width="350"
align="left"
prop="name"
sortable
>
<template slot-scope="scope">
<a class="text-over" :title="scope.row.name" style="cursor:default;color:#426290">{{ scope.row.name }}</a>
</template>
</el-table-column>
<el-table-column
label="文件路径/镜像描述"
width="450"
align="left"
>
<template slot-scope="scope">
<el-tooltip class="item" effect="dark" content="点击复制文件路径" placement="top">
<a class="text-over" style="display:block;" @click="copyUrl(scope.row.place)">{{ scope.row.place }}</a>
</el-tooltip>
<span class="text-over" :title="scope.row.description | clearP">{{ scope.row.description | clearP }}</span>
</template>
</el-table-column>
<el-table-column
prop="provider"
label="提供者"
width="120"
align="left"
sortable>
</el-table-column>
<el-table-column
prop="createtime"
label="创建时间"
align="center"
sortable>
<template slot-scope="scope">
{{scope.row.createtime | transformTimestamp}}
</template>
</el-table-column>
</el-table>
</el-row>
<div class="ui container" style="margin-top:50px;text-align:center">
<el-pagination
background
@size-change="handleSizeChange1"
@current-change="handleCurrentChange1"
:current-page="currentPage1"
:page-size="pageSize1"
:page-sizes="[5,10,20]"
layout="total, sizes, prev, pager, next, jumper"
:total="totalNum1">
</el-pagination>
</div>

</el-tab-pane>
<el-tab-pane label="公共镜像(云脑2)" name="third">
<div class="ui sixteen wide column">
<div class="ui two column stackable grid">
<div class="column">
<el-input placeholder="请输入镜像名称关健词" v-model="search" class="input-with-select">
<el-button slot="append" id="success" icon="el-icon-search">搜索</el-button>
</el-input>
</div>

<!-- <div class="column right aligned">
<el-dropdown>
<span class="el-dropdown-link">
排序<i class="el-icon-caret-bottom"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>最早创建</el-dropdown-item>
<el-dropdown-item>最新创建</el-dropdown-item>
<el-dropdown-item divided>按镜像字母顺序排序</el-dropdown-item>
<el-dropdown-item>按镜像字母逆序排序</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div> -->
</div>
</div>

<el-empty :image-size="200"></el-empty>
</el-tab-pane>
<el-tab-pane label="自定义镜像(云脑2)" name="fourth">
<div class="ui sixteen wide column">
<div class="ui two column stackable grid">
<div class="column">
<el-input placeholder="请输入镜像名称关健词" v-model="search" class="input-with-select">
<el-button slot="append" id="success" icon="el-icon-search">搜索</el-button>
</el-input>
</div>

<!-- <div class="column right aligned">
<el-dropdown>
<span class="el-dropdown-link">
排序<i class="el-icon-caret-bottom"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item>最早创建</el-dropdown-item>
<el-dropdown-item>最新创建</el-dropdown-item>
<el-dropdown-item divided>按镜像字母顺序排序</el-dropdown-item>
<el-dropdown-item>按镜像字母逆序排序</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div> -->
</div>
</div>

<el-empty :image-size="200"></el-empty>

</el-tab-pane>
</el-tabs>
</div>
</div>

</template>

<script>

const {_AppSubUrl, _StaticUrlPrefix, csrf} = window.config;




export default {
components: {
},
data() {
return {
activeName: 'first',
search:'',
currentPage:1,
pageSize:10,
totalNum:0,
params:{page:1,size:10,name:''},
tableData: [],
loading:false,

currentPage1:1,
pageSize1:10,
totalNum1:0,
params1:{page:1,size:10,name:''},
tableData1: [],
loading1:false
};
},
methods: {
handleClick(tab, event) {
if(tab.name=="first"){
this.getImageList()
}
if(tab.name=="second"){
this.getImageList1()
}
},
tableHeaderStyle({row,column,rowIndex,columnIndex}){
if(rowIndex===0){
return 'background:#f5f5f6;color:#606266'
}
},
handleSizeChange(val){
this.params.size = val
this.getImageList()


},
handleCurrentChange(val){
console.log(val)
this.params.page = val
this.getImageList()

},
handleSizeChange1(val){
this.params1.size = val
this.getImageList1()


},
handleCurrentChange1(val){
this.params1.page = val
this.getImageList1()

},
getImageList(){
this.loading = true
this.$axios.get('/explore/images/public',{
params:this.params
}).then((res)=>{
this.totalNum = res.data.count
this.tableData = res.data.rows
this.loading = false
})
},

getImageList1(){
this.loading1 = true
this.$axios.get('/explore/images/custom',{
params:this.params1
}).then((res)=>{
this.totalNum1 = res.data.count
this.tableData1 = res.data.rows
this.loading1 = false
})
},
copyUrl(url){
console.log(url)
const cInput = document.createElement('input')
cInput.value = url
document.body.appendChild(cInput)
cInput.select()
document.execCommand('Copy')
cInput.remove()

},
searchName(){
if(this.activeName=='first'){
this.params.name = this.search
this.params.page = 1
this.getImageList()
}
if(this.activeName=='second'){
this.params1.name = this.search
this.params1.page = 1
this.getImageList1()
}
}

},
filters:{



clearP(value){
console.log("sorce value",value)
if(!value) return ''
const reg = /\<\/?p\>/g;
value = value.replace(reg,'')
console.log("repalace:",value)
return value

},
transformTimestamp(timestamp){
console.log("timestamp",timestamp)
let a = new Date(timestamp).getTime();
const date = new Date(a);
const Y = date.getFullYear() + '-';
const M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-';
const D = (date.getDate() < 10 ? '0'+date.getDate() : date.getDate()) + ' ';
const h = (date.getHours() < 10 ? '0'+date.getHours() : date.getHours()) + ':';
const m = (date.getMinutes() <10 ? '0'+date.getMinutes() : date.getMinutes()) + ':' ;
const s = (date.getSeconds() <10 ? '0'+date.getSeconds() : date.getSeconds()) ; // 秒
const dateString = Y + M + D + h + m + s;
// console.log('dateString', dateString); // > dateString 2021-07-06 14:23
return dateString;
},
},
watch:{
search(val){
if(!val && this.activeName=='first'){
this.params.name = val
this.getImageList()
}
if(!val && this.activeName=='second'){
this.params1.name = val
this.getImageList1()
}
}

},
mounted() {
this.getImageList()
},
created() {
}

};
</script>

<style scoped>
.header-wrapper {
background-color: #f5f5f6;
padding-top: 15px;
}
.image_text{
padding:25px 0 55px 0 ;
}
#header{
position: relative;
top:-40px;
}
.el-dropdown-menu__item--divided{
border-top: 1px solid blue;
}
.el-table thead{
background-color: #f5f5f6;
}
/deep/ .el-tabs__item:hover{
color: #000;
font-weight: 500;
}
/deep/ .el-tabs__item.is-active {
color: #000;
font-weight: 500;
}
/deep/ .el-tabs__active-bar{
background-color:#000
}

/deep/ .el-pagination.is-background .el-pager li:not(.disabled).active {
background-color: #5bb973;
color: #FFF;
}
/deep/ .el-pagination.is-background .el-pager li.active {
color: #fff;
cursor: default;
}
/deep/ .el-pagination.is-background .el-pager li:hover {
color: #5bb973;
}
/deep/ .el-pagination.is-background .el-pager li:not(.disabled):hover {
color: #5bb973;
}
/deep/ .el-pagination.is-background .el-pager li:not(.disabled).active:hover {
background-color: #5bb973;
color: #FFF;
}

/deep/ .el-pager li.active {
color: #08C0B9;
cursor: default;
}
/deep/ .el-pagination .el-pager li:hover {
color: #08C0B9;
}
/deep/ .el-pagination .el-pager li:not(.disabled):hover {
color: #08C0B9;
}
/* /deep/ .el-pagination.is-background .el-pager li:not(.disabled).active{
background-color: #5bb973;
color: #000;
} */
/* /deep/ .el-pager li:hover{
color: #000;
} */
#success{
background-color: #5bb973;
color: white;
}
.text-over{
overflow: hidden;
text-overflow: ellipsis;
vertical-align: middle;
white-space: nowrap;
}
</style>

+ 21
- 4
web_src/js/components/MinioUploader.vue View File

@@ -1,5 +1,8 @@
<template>
<div class="dropzone-wrapper dataset-files">
<div class="ui pointing below red basic label">
<i class="icon info circle"></i>只有zip格式的数据集才能发起云脑任务
</div>
<div
id="dataset"
class="dropzone"
@@ -335,8 +338,19 @@ export default {

async function uploadMinio(url, e) {
const res = await axios.put(url, e.target.result);
delete e.target.result
delete e.target.result
etags[currentChunk] = res.headers.etag;
}
async function uploadMinioNewMethod(url,e){
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, false);
xhr.setRequestHeader('Content-Type', 'text/plain')
xhr.send(e.target.result);
var etagValue = xhr.getResponseHeader('etag');
//console.log(etagValue);
etags[currentChunk] = etagValue;
}

async function updateChunk(currentChunk) {
@@ -359,8 +373,10 @@ export default {
// 获取分片上传url
await getUploadChunkUrl(currentChunk, partSize);
if (urls[currentChunk] != '') {
// 上传到minio
await uploadMinio(urls[currentChunk], e);
// 上传到minio
//await uploadMinio(urls[currentChunk], e);
await uploadMinioNewMethod(urls[currentChunk], e);
if (etags[currentChunk] != '') {
// 更新数据库:分片上传结果
//await updateChunk(currentChunk);
@@ -372,8 +388,9 @@ export default {
}
}
} catch (error) {
this.emitDropzoneFailed(file);
console.log(error);
//this.emitDropzoneFailed(file);
//console.log(error);
}
}



+ 18
- 1
web_src/js/index.js View File

@@ -35,6 +35,7 @@ import {createCodeEditor} from './features/codeeditor.js';
import MinioUploader from './components/MinioUploader.vue';
import ObsUploader from './components/ObsUploader.vue';
import EditAboutInfo from './components/EditAboutInfo.vue';
import Images from './components/Images.vue'

Vue.use(ElementUI);
Vue.prototype.$axios = axios;
@@ -2966,6 +2967,7 @@ $(document).ready(async () => {
initVueUploader();
initObsUploader();
initVueEditAbout();
initVueImages();
initTeamSettings();
initCtrlEnterSubmit();
initNavbarContentToggle();
@@ -3653,7 +3655,7 @@ function initVueUploader() {

function initVueEditAbout() {
const el = document.getElementById('about-desc');
console.log(el)
if (!el) {
return;
}
@@ -3664,6 +3666,21 @@ function initVueEditAbout() {
});
}


function initVueImages() {
const el = document.getElementById('images');
console.log("el",el)
if (!el) {
return;
}

new Vue({
el: '#images',
render: h => h(Images)
});
}

// 新增
function initObsUploader() {
const el = document.getElementById('obsUploader');


+ 0
- 2
web_src/less/_base.less View File

@@ -890,8 +890,6 @@ footer {

.ui.menu.new-menu {
justify-content: center !important;
padding-top: 15px !important;
margin-top: -15px !important;
margin-bottom: 15px !important;
background-color: #fafafa !important;
border-width: 1px !important;


Loading…
Cancel
Save