| @@ -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 { | |||
| @@ -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 { | |||
| @@ -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. | |||
| @@ -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 地址已添加。 | |||
| @@ -2436,7 +2437,7 @@ future=将来 | |||
| 1y=1年 | |||
| seconds=%d 秒 | |||
| minutes=%d 分钟 | |||
| hours=%d hours | |||
| hours=%d 小时 | |||
| days=%d 天 | |||
| weeks=%d 周 | |||
| months=%d 个月 | |||
| @@ -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" | |||
| @@ -1,8 +1,6 @@ | |||
| package repo | |||
| import ( | |||
| "code.gitea.io/gitea/modules/git" | |||
| "code.gitea.io/gitea/modules/storage" | |||
| "encoding/json" | |||
| "errors" | |||
| "net/http" | |||
| @@ -13,6 +11,9 @@ import ( | |||
| "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" | |||
| @@ -23,10 +24,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 ( | |||
| @@ -89,13 +90,17 @@ func cutString(str string, lens int) string { | |||
| func jobNamePrefixValid(s string) string { | |||
| lowStr := strings.ToLower(s) | |||
| re := regexp.MustCompile(`[^a-z0-9\\.\\-]+`) | |||
| return re.ReplaceAllString(lowStr, "") | |||
| re := regexp.MustCompile(`[^a-z0-9_\\-]+`) | |||
| //去掉非法字符 | |||
| removeSpecial := re.ReplaceAllString(lowStr, "") | |||
| //任务名以数字字母开头,去掉非法前缀 | |||
| re = regexp.MustCompile(`^[_\\-]+`) | |||
| return re.ReplaceAllString(removeSpecial, "") | |||
| } | |||
| func CloudBrainNew(ctx *context.Context) { | |||
| func cloudBrainNewDataPrepare(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 | |||
| @@ -103,7 +108,7 @@ func CloudBrainNew(ctx *context.Context) { | |||
| 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 { | |||
| @@ -119,7 +124,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 { | |||
| @@ -134,8 +139,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 | |||
| @@ -162,6 +167,16 @@ func CloudBrainNew(ctx *context.Context) { | |||
| 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) | |||
| } | |||
| @@ -177,7 +192,8 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { | |||
| 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 | |||
| } | |||
| @@ -185,11 +201,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 | |||
| } | |||
| @@ -200,6 +218,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 | |||
| } | |||
| @@ -223,6 +242,7 @@ func CloudBrainCreate(ctx *context.Context, form auth.CreateCloudBrainForm) { | |||
| 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 | |||
| } | |||
| @@ -397,6 +417,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/" | |||
| @@ -413,7 +465,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"]) | |||
| @@ -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) | |||
| @@ -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, | |||
| } | |||
| @@ -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}} | |||
| @@ -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}} | |||
| @@ -0,0 +1,7 @@ | |||
| {{template "base/head" .}} | |||
| <div id="images"> | |||
| </div> | |||
| {{template "base/footer" .}} | |||
| @@ -0,0 +1,420 @@ | |||
| <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"> | |||
| <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="center" | |||
| 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="center" | |||
| > | |||
| <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">{{ scope.row.description }}</span> | |||
| </template> | |||
| </el-table-column> | |||
| <el-table-column | |||
| prop="provider" | |||
| label="提供者" | |||
| width="120" | |||
| align="center" | |||
| sortable> | |||
| </el-table-column> | |||
| <el-table-column | |||
| prop="createtime" | |||
| label="创建时间" | |||
| align="center" | |||
| sortable> | |||
| </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"> | |||
| <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="center" | |||
| 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="center" | |||
| > | |||
| <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">{{ scope.row.description }}</span> | |||
| </template> | |||
| </el-table-column> | |||
| <el-table-column | |||
| prop="provider" | |||
| label="提供者" | |||
| width="120" | |||
| align="center" | |||
| sortable> | |||
| </el-table-column> | |||
| <el-table-column | |||
| prop="createtime" | |||
| label="创建时间" | |||
| align="center" | |||
| sortable> | |||
| </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(){ | |||
| 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.getImageList() | |||
| } | |||
| if(this.activeName=='second'){ | |||
| this.params1.name = this.search | |||
| this.getImageList1() | |||
| } | |||
| } | |||
| }, | |||
| 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; | |||
| } | |||
| #success{ | |||
| background-color: #4093ff; | |||
| color: white; | |||
| } | |||
| .text-over{ | |||
| overflow: hidden; | |||
| text-overflow: ellipsis; | |||
| vertical-align: middle; | |||
| white-space: nowrap; | |||
| } | |||
| </style> | |||
| @@ -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'); | |||