diff --git a/README_ZH.md b/README_ZH.md index 442347d077..13dcf0ff4c 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -46,6 +46,10 @@ CHARSET_COLLATION = utf8mb4_bin [cors] CONTENT_SECURITY_POLICY = default-src 'self' data: 'unsafe-inline' https://mp.weixin.qq.com; img-src * data: + +[ui.admin] +;; Dev Container 分页参数(每页展示 DevContainer 个数),若未指定,默认值 50 +DEV_CONTAINERS_PAGING_NUM = 50 ``` 正式部署单机版 diff --git a/models/devstar_devcontainer/devstar_devcontainer.go b/models/devstar_devcontainer/devstar_devcontainer.go index 4d0a86aa84..2bd0810122 100644 --- a/models/devstar_devcontainer/devstar_devcontainer.go +++ b/models/devstar_devcontainer/devstar_devcontainer.go @@ -4,7 +4,7 @@ import ( "code.gitea.io/gitea/models/db" ) -// DevstarDevcontainer devContainer 与 代码仓库一对一关联表 +// DevstarDevcontainer devContainer 关联 代码仓库 和 用户 // // 遵循gonic规则映射数据库表 `devstar_devcontainer`,各字段注解见 https://xorm.io/docs/chapter-02/4.columns/ type DevstarDevcontainer struct { @@ -15,7 +15,8 @@ type DevstarDevcontainer struct { DevcontainerUsername string `xorm:"VARCHAR(32) charset=utf8mb4 collate=utf8mb4_bin NOT NULL 'devcontainer_username' comment('SSH Username')"` DevcontainerPassword string `xorm:"VARCHAR(32) charset=utf8mb4 collate=utf8mb4_bin NOT NULL 'devcontainer_password' comment('SSH Password')"` DevcontainerWorkDir string `xorm:"VARCHAR(256) charset=utf8mb4 collate=utf8mb4_bin NOT NULL 'devcontainer_work_dir' comment('SSH 工作路径,典型值 ~/${project_name},256字节以内')"` - RepoId int64 `xorm:"BIGINT UNIQUE NOT NULL 'repo_id' comment('repository表主键')"` + RepoId int64 `xorm:"BIGINT NOT NULL 'repo_id' comment('repository表主键')"` + UserId int64 `xorm:"BIGINT NOT NULL 'user_id' comment('user表主键')"` } func init() { diff --git a/models/devstar_devcontainer/devstar_devcontainer_user.go b/models/devstar_devcontainer/devstar_devcontainer_user.go deleted file mode 100644 index 62ae938011..0000000000 --- a/models/devstar_devcontainer/devstar_devcontainer_user.go +++ /dev/null @@ -1,18 +0,0 @@ -package devstar_devcontainer - -import ( - "code.gitea.io/gitea/models/db" -) - -// DevstarDevcontainerUser devContainer 与 用户 多对多关联表 -// -// 遵循gonic规则映射数据库表 `devstar_devcontainer_user`,各字段注解见 https://xorm.io/docs/chapter-02/4.columns/ -type DevstarDevcontainerUser struct { - Id int64 `xorm:"BIGINT pk NOT NULL autoincr 'id' comment('主键')"` - DevcontainerId int64 `xorm:"BIGINT NOT NULL 'devcontainer_id' comment('devstar_devcontainer表主键')"` - UserId int64 `xorm:"BIGINT NOT NULL 'user_id' comment('user表主键')"` -} - -func init() { - db.RegisterModel(new(DevstarDevcontainerUser)) -} diff --git a/models/migrations/devstar_v1_0/dv2.go b/models/migrations/devstar_v1_0/dv2.go index 560aabd8ca..aa0544e440 100644 --- a/models/migrations/devstar_v1_0/dv2.go +++ b/models/migrations/devstar_v1_0/dv2.go @@ -13,16 +13,11 @@ func InitializeDevContainerDbTables(x *xorm.Engine) error { var err error - // 1. 初始化 devContainer 与 Repository 一对一关系表 + // 1. 初始化 devContainer 表 if err = addDBDevStarDevContainer(x); err != nil { return err } - // 2. 初始化 devContainer 与 user 多对多关系表 - if err = addDBDevStarDevContainerUser(x); err != nil { - return err - } - return nil } @@ -39,17 +34,3 @@ func addDBDevStarDevContainer(x *xorm.Engine) error { return nil } - -// addDBDevStarDevContainerUser 2. 初始化 devContainer 与 user 多对多关系表 -func addDBDevStarDevContainerUser(x *xorm.Engine) error { - - err := x.Sync(new(devcontainer_models.DevstarDevcontainerUser)) - if err != nil { - return ErrMigrateDevstarDatabase{ - Step: "create table 'devstar_devcontainer_user'", - Message: err.Error(), - } - } - - return nil -} diff --git a/modules/setting/ui.go b/modules/setting/ui.go index a8dc11d097..5caa16013f 100644 --- a/modules/setting/ui.go +++ b/modules/setting/ui.go @@ -60,6 +60,8 @@ var UI = struct { RepoPagingNum int NoticePagingNum int OrgPagingNum int + // DevContainersPagingNum Dev Container 分页参数,每页展示 Dev Container 数量 + DevContainersPagingNum int } `ini:"ui.admin"` User struct { RepoPagingNum int @@ -118,11 +120,15 @@ var UI = struct { RepoPagingNum int NoticePagingNum int OrgPagingNum int + // DevContainersPagingNum Dev Container 分页参数,每页展示 Dev Container 数量 + DevContainersPagingNum int }{ UserPagingNum: 50, RepoPagingNum: 50, NoticePagingNum: 25, OrgPagingNum: 50, + // DevContainersPagingNum Dev Container 分页参数,每页展示 Dev Container 数量 + DevContainersPagingNum: 50, }, User: struct { RepoPagingNum int diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 12efa1a43d..7149a0be2d 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -691,6 +691,8 @@ social = Social Accounts applications = Applications orgs = Manage Organizations repos = Repositories +dev_containers_list = Dev Containers +dev_containers_none = You do not own any dev containers. delete = Delete Account twofa = Two-Factor Authentication (TOTP) account_link = Linked Accounts @@ -1010,6 +1012,7 @@ visibility.private = Private visibility.private_tooltip = Visible only to members of organizations you have joined [repo] +dev_container = Dev Container new_repo_helper = A repository contains all project files, including revision history. Already hosting one elsewhere? Migrate repository. owner = Owner owner_helper = Some organizations may not show up in the dropdown due to a maximum repository count limit. diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 8236120e1b..ece9e1397a 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -690,6 +690,8 @@ social=社交帐号绑定 applications=应用 orgs=管理组织 repos=仓库列表 +dev_containers_list = 开发容器列表 +dev_containers_none = 你没有任何开发容器。 delete=删除帐户 twofa=两步验证 account_link=已绑定帐户 @@ -1009,6 +1011,7 @@ visibility.private=私有 visibility.private_tooltip=仅对您已加入的组织的成员可见。 [repo] +dev_container = 开发容器 new_repo_helper=代码仓库包含了所有的项目文件,包括版本历史记录。已经在其他地方托管了?迁移仓库。 owner=拥有者 owner_helper=由于最大仓库数量限制,一些组织可能不会显示在下拉列表中。 diff --git a/routers/web/devcontainer/list_user_devcontainers.go b/routers/web/devcontainer/list_user_devcontainers.go index f51b5cf33c..9987672e24 100644 --- a/routers/web/devcontainer/list_user_devcontainers.go +++ b/routers/web/devcontainer/list_user_devcontainers.go @@ -1,6 +1,8 @@ package devcontainer import ( + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/setting" Result "code.gitea.io/gitea/routers/entity" DevcontainersVO "code.gitea.io/gitea/routers/web/devcontainer/vo" "code.gitea.io/gitea/services/context" @@ -9,8 +11,10 @@ import ( // ListUserDevcontainers 枚举已登录用户所有的 devContainers,但目前测试阶段只返回 mock数据 // -// GET /api/devcontainer/user -// 请求输入参数:无(已在ctx隐含) +// GET /api/devcontainer/user +// 请求输入参数: +// - page: 当前第几页(默认第1页),从1开始计数 +// - pageSize: 每页记录数(默认值 setting.UI.Admin.DevContainersPagingNum) func ListUserDevcontainers(ctx *context.Context) { // 1. 检查用户登录状态,若未登录则返回未授权错误 @@ -20,17 +24,28 @@ func ListUserDevcontainers(ctx *context.Context) { } // 2. 查询数据库 当前登录用户拥有写入权限的仓库 - userId := ctx.Doer.ID - userDevcontainersItemVOList, _ := devstar_devcontainer_service.GetUserDevcontainerListByUserId(ctx, userId) + userPage := ctx.FormInt("page") + if userPage <= 0 { + userPage = 1 + } + userPageSize := ctx.FormInt("page_size") + if userPageSize <= 0 || userPageSize > setting.UI.Admin.DevContainersPagingNum { + userPageSize = setting.UI.Admin.DevContainersPagingNum + } + opts := &DevcontainersVO.SearchDevcontainerItemVoOptions{ + Actor: ctx.Doer, + PaginationOptions: db.ListOptions{ + Page: userPage, + PageSize: userPageSize, + }, + } + userDevcontainersVO, _ := devstar_devcontainer_service.GetUserDevcontainersList(ctx, opts) // 3. 封装VO resultListUserDevcontainersVO := Result.ResultType{ Code: Result.RespSuccess.Code, Msg: Result.RespSuccess.Msg, - Data: DevcontainersVO.ListUserDevcontainersVO{ - UserID: userId, - DevContainers: userDevcontainersItemVOList, - }, + Data: userDevcontainersVO, } // 4. JSON序列化,写入输出流 diff --git a/routers/web/devcontainer/vo/ListUserDevcontainersVO.go b/routers/web/devcontainer/vo/ListUserDevcontainersVO.go index 902471bfff..c050fda6b4 100644 --- a/routers/web/devcontainer/vo/ListUserDevcontainersVO.go +++ b/routers/web/devcontainer/vo/ListUserDevcontainersVO.go @@ -1,21 +1,38 @@ package vo +import ( + "code.gitea.io/gitea/models/db" + user_model "code.gitea.io/gitea/models/user" +) + // ListUserDevcontainersVO 封装用户所有 devContainer 信息列表 type ListUserDevcontainersVO struct { UserID int64 `json:"userId"` + Username string `json:"username"` DevContainers []DevContainerItemVO `json:"devContainers"` + Page int `json:"page"` + PageSize int `json:"pageSize"` + PageTotalNum int `json:"pageTotalNum"` + ItemTotalNum int64 `json:"itemTotalNum"` } // DevContainerItemVO 描述每一项 devContainer type DevContainerItemVO struct { - RepoId int64 `json:"repoId" xorm:"RepoId"` - RepoName string `json:"repoName" xorm:"RepoName"` - RepoDescription string `json:"repoDescription" xorm:"RepoDescription"` - DevContainerId int64 `json:"devContainerId" xorm:"DevContainerId"` - DevContainerName string `json:"devContainerName" xorm:"DevContainerName"` - DevContainerHost string `json:"devContainerHost" xorm:"DevContainerHost"` - DevContainerPort uint16 `json:"devContainerPort" xorm:"DevContainerPort"` - DevContainerUsername string `json:"devContainerUsername" xorm:"DevContainerUsername"` - DevContainerPassword string `json:"devContainerPassword" xorm:"DevContainerPassword"` - DevContainerWorkDir string `json:"devContainerWorkDir" xorm:"DevContainerWorkDir"` + DevContainerId int64 `json:"devContainerId" xorm:"devcontainer_id"` + DevContainerName string `json:"devContainerName" xorm:"devcontainer_name"` + DevContainerHost string `json:"devContainerHost" xorm:"devcontainer_host"` + DevContainerPort uint16 `json:"devContainerPort" xorm:"devcontainer_port"` + DevContainerUsername string `json:"devContainerUsername" xorm:"devcontainer_username"` + DevContainerPassword string `json:"devContainerPassword" xorm:"devcontainer_password"` + DevContainerWorkDir string `json:"devContainerWorkDir" xorm:"devcontainer_work_dir"` + RepoId int64 `json:"repoId" xorm:"repo_id"` + RepoName string `json:"repoName" xorm:"repo_name"` + RepoDescription string `json:"repoDescription" xorm:"repo_description"` +} + +// SearchDevcontainerItemVoOptions 查询条件 +type SearchDevcontainerItemVoOptions struct { + PaginationOptions db.ListOptions // XORM 分页查询条件 + Actor *user_model.User // Dev Container 所属用户 + OrderBy db.SearchOrderBy // 排序 } diff --git a/routers/web/user/setting/dev_containers_list.go b/routers/web/user/setting/dev_containers_list.go new file mode 100644 index 0000000000..9c5de38194 --- /dev/null +++ b/routers/web/user/setting/dev_containers_list.go @@ -0,0 +1,59 @@ +package setting + +import ( + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/setting" + DevcontainersVO "code.gitea.io/gitea/routers/web/devcontainer/vo" + gitea_context "code.gitea.io/gitea/services/context" + devstar_devcontainer_service "code.gitea.io/gitea/services/devstar_devcontainer" + "net/http" +) + +const ( + tplSettingsDevContainers base.TplName = "user/settings/dev_containers_list" +) + +// ListDevContainers 展示用户所有开发容器 +func ListDevContainers(ctx *gitea_context.Context) { + + // 1. 检查页面是否登录,若未登录则重定向到登录页 + ctxUser := ctx.Doer + if ctxUser == nil { + ctx.Redirect(setting.AppSubURL + string(setting.LandingPageLogin)) + return + } + + // 2. 设置前端展示元数据数据 + ctx.Data["Title"] = ctx.Tr("settings.dev_containers_list") + ctx.Data["PageIsSettingsDevContainersList"] = true + + // 3. 分页查询 + paginationOptions := db.ListOptions{ + PageSize: setting.UI.Admin.DevContainersPagingNum, // 参考 app.ini 配置文件 ui.admin.DEV_CONTAINERS_PAGING_NUM,默认50 + Page: ctx.FormInt("page"), // URL参数起始页码 + } + if paginationOptions.Page <= 0 { + paginationOptions.Page = 1 + } + opts := &DevcontainersVO.SearchDevcontainerItemVoOptions{ + PaginationOptions: paginationOptions, + Actor: ctxUser, + } + userDevcontainersListVO, err := devstar_devcontainer_service.GetUserDevcontainersList(ctx, opts) + if err != nil { + ctx.ServerError("ListDevContainers", err) + return + } + ctx.Data["DevContainers"] = userDevcontainersListVO.DevContainers + ctx.Data["ContextUser"] = ctxUser + itemTotalNum := userDevcontainersListVO.ItemTotalNum + pageTotalNum := userDevcontainersListVO.PageTotalNum + pageSize := userDevcontainersListVO.PageSize + currentPage := userDevcontainersListVO.Page + pager := gitea_context.NewPagination(int(itemTotalNum), pageSize, currentPage, pageTotalNum) + pager.SetDefaultParams(ctx) + ctx.Data["Page"] = pager + ctx.HTML(http.StatusOK, tplSettingsDevContainers) + return +} diff --git a/routers/web/web.go b/routers/web/web.go index 563cac768e..63be7b3e9c 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -651,6 +651,13 @@ func registerRoutes(m *web.Router) { m.Get("/repos", user_setting.Repos) m.Post("/repos/unadopted", user_setting.AdoptOrDeleteRepository) + // ***** START: /user/settings/dev-containers ***** + // 管理用户级别开发容器列表,现只做查看,如需控制/修改,需要跳转到对应仓库开发容器页面 + m.Group("/dev-containers-list", func() { + m.Get("", user_setting.ListDevContainers) + }) + // ***** END: /user/settings/dev-containers ***** + m.Group("/hooks", func() { m.Get("", user_setting.Webhooks) m.Post("/delete", user_setting.DeleteWebhook) diff --git a/services/devstar_devcontainer/UserDevcontainerService.go b/services/devstar_devcontainer/UserDevcontainerService.go index 6ffc0db9fe..1d9e2e6261 100644 --- a/services/devstar_devcontainer/UserDevcontainerService.go +++ b/services/devstar_devcontainer/UserDevcontainerService.go @@ -3,52 +3,131 @@ package devstar_devcontainer import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/devstar_devcontainer" + "code.gitea.io/gitea/modules/setting" DevcontainersVO "code.gitea.io/gitea/routers/web/devcontainer/vo" "context" + "fmt" + "xorm.io/builder" ) -// GetUserDevcontainerListByUserId 根据 userId 查询名下 devContainer 列表 -func GetUserDevcontainerListByUserId(ctx context.Context, userId int64) ([]DevcontainersVO.DevContainerItemVO, error) { +// GetUserDevcontainersList 根据 userId 查询名下 devContainer 列表 +func GetUserDevcontainersList(ctx context.Context, opts *DevcontainersVO.SearchDevcontainerItemVoOptions) (DevcontainersVO.ListUserDevcontainersVO, error) { - var devcontainerItemVOList []DevcontainersVO.DevContainerItemVO - /* - SELECT - devstar_devcontainer.repo_id AS RepoId, - repository.name AS RepoName, - repository.description AS RepoDescription, - devstar_devcontainer.id AS DevContainerId, - devstar_devcontainer.name AS DevContainerName, - devstar_devcontainer.devcontainer_host AS DevContainerHost, - devstar_devcontainer.devcontainer_port AS DevContainerPort, - devstar_devcontainer.devcontainer_username AS DevContainerUsername, - devstar_devcontainer.devcontainer_password AS DevContainerPassword, - devstar_devcontainer.devcontainer_work_dir AS DevContainerWorkDir - FROM devstar_devcontainer - LEFT JOIN repository ON devstar_devcontainer.repo_id = repository.id - WHERE devstar_devcontainer.id IN ( - SELECT devcontainer_id - FROM devstar_devcontainer_user - WHERE user_id = ? - ); - */ - err := db.GetEngine(ctx). - Table("devstar_devcontainer"). - Select("devstar_devcontainer.repo_id AS RepoId, repository.name AS RepoName, repository.description AS RepoDescription, devstar_devcontainer.id AS DevContainerId, devstar_devcontainer.name AS DevContainerName, devstar_devcontainer.devcontainer_host AS DevContainerHost, devstar_devcontainer.devcontainer_port AS DevContainerPort, devstar_devcontainer.devcontainer_username AS DevContainerUsername,devstar_devcontainer.devcontainer_password AS DevContainerPassword, devstar_devcontainer.devcontainer_work_dir AS DevContainerWorkDir"). - Join("LEFT", "repository", "devstar_devcontainer.repo_id = repository.id"). - Where("devstar_devcontainer.id IN (SELECT devcontainer_id FROM devstar_devcontainer_user WHERE user_id = ?)", userId). - Find(&devcontainerItemVOList) - - if devcontainerItemVOList == nil { - devcontainerItemVOList = []DevcontainersVO.DevContainerItemVO{} + // 0. 构造异常返回时的空数据 + var resultDevContainerListVO = DevcontainersVO.ListUserDevcontainersVO{ + Page: 0, + PageSize: setting.UI.Admin.DevContainersPagingNum, + PageTotalNum: 0, + ItemTotalNum: 0, + DevContainers: []DevcontainersVO.DevContainerItemVO{}, } - if err != nil { - return devcontainerItemVOList, + // 1. 查询参数预处理 + if opts == nil || opts.Actor == nil { + return resultDevContainerListVO, devstar_devcontainer.ErrFailedToOperateDevstarDevcontainerDB{ + Action: "construct query condition for devContainer user list", + Message: "invalid search condition", + } + } + resultDevContainerListVO.UserID = opts.Actor.ID + resultDevContainerListVO.Username = opts.Actor.Name + if len(opts.OrderBy) == 0 { + opts.OrderBy = "devcontainer_id DESC" + } + opts.PaginationOptions.ListAll = false // 强制使用分页查询,禁止一次性列举所有 devContainers + if opts.PaginationOptions.Page <= 0 { // 未指定页码/无效页码:查询第 1 页 + opts.PaginationOptions.Page = 1 + } + if opts.PaginationOptions.PageSize <= 0 || opts.PaginationOptions.PageSize > setting.UI.Admin.DevContainersPagingNum { + opts.PaginationOptions.PageSize = setting.UI.Admin.DevContainersPagingNum // /无效页面大小/超过每页最大限制:自动调整到系统最大开发容器页面大小 + } + resultDevContainerListVO.Page = opts.PaginationOptions.Page + resultDevContainerListVO.PageSize = opts.PaginationOptions.PageSize + + // 2. SQL 条件构建 + sqlCondition := builder.NewCond() + sqlCondition = sqlCondition.And(builder.Eq{"user_id": opts.Actor.ID}) + + // 3. 开启数据库事务,进行分页查询,同时封装 VO + errDbTransaction := db.WithTx(ctx, func(ctx context.Context) error { + sess := db.GetEngine(ctx) + var err error + + // 2.1 查询符合条件的记录总数 + /* + SELECT COUNT(*) + FROM devstar_devcontainer + LEFT JOIN repository ON devstar_devcontainer.repo_id = repository.id + WHERE devstar_devcontainer.user_id = #{opts.Actor.ID} + */ + resultDevContainerListVO.ItemTotalNum, err = sess. + Table("devstar_devcontainer"). + Join("LEFT", "repository", "devstar_devcontainer.repo_id = repository.id"). + Where(sqlCondition). + Count() + if err != nil { + return devstar_devcontainer.ErrFailedToOperateDevstarDevcontainerDB{ + Action: "count devContainer item numbers", + Message: err.Error(), + } + } + if resultDevContainerListVO.ItemTotalNum == 0 { + return nil // 无符合记录,返回 nil 提交/结束 事务 + } + + // 2.3 计算分页参数 + totalRecords := resultDevContainerListVO.ItemTotalNum + pageSize := int64(resultDevContainerListVO.PageSize) + resultDevContainerListVO.PageTotalNum += int(totalRecords/pageSize) + 1 + + // 2.3 数据库带条件分页查询 + resultDevContainerListVO.DevContainers = make([]DevcontainersVO.DevContainerItemVO, 0, opts.PaginationOptions.PageSize) + /* + SELECT + devstar_devcontainer.id AS devcontainer_id, + devstar_devcontainer.name AS devcontainer_name, + devstar_devcontainer.devcontainer_host AS devcontainer_host, + devstar_devcontainer.devcontainer_port AS devcontainer_port, + devstar_devcontainer.devcontainer_username AS devcontainer_username, + devstar_devcontainer.devcontainer_password AS devcontainer_password, + devstar_devcontainer.devcontainer_work_dir AS devcontainer_work_dir, + repository.id AS repo_id, + repository.name AS repo_name, + repository.description AS repo_description + FROM devstar_devcontainer + LEFT JOIN repository ON devstar_devcontainer.repo_id = repository.id + WHERE devstar_devcontainer.user_id = #{opts.Actor.ID} + ORDER BY #{opts.OrderBy.String()} + LIMIT #{opts.PaginationOptions.PageSize} + OFFSET ( (#{opts.PaginationOptions.Page} - 1) * #{opts.PaginationOptions.PageSize}); + */ + sess = sess. + Table("devstar_devcontainer"). + Select("devstar_devcontainer.repo_id AS repo_id, repository.name AS repo_name, repository.description AS repo_description, devstar_devcontainer.id AS devcontainer_id, devstar_devcontainer.name AS devcontainer_name, devstar_devcontainer.devcontainer_host AS devcontainer_host, devstar_devcontainer.devcontainer_port AS devcontainer_port, devstar_devcontainer.devcontainer_username AS devcontainer_username, devstar_devcontainer.devcontainer_password AS devcontainer_password, devstar_devcontainer.devcontainer_work_dir AS devcontainer_work_dir"). + Join("LEFT", "repository", "devstar_devcontainer.repo_id = repository.id"). + Where(sqlCondition). + OrderBy(opts.OrderBy.String()) + err = db.SetSessionPagination(sess, &opts.PaginationOptions). + Find(&resultDevContainerListVO.DevContainers) + if err != nil { + // 查询出错,返回错误信息,结束事务 + return devstar_devcontainer.ErrFailedToOperateDevstarDevcontainerDB{ + Action: fmt.Sprintf("list devContainer for user '%v' at page %v", opts.Actor.Name, opts.PaginationOptions.Page), + Message: err.Error(), + } + } + + // 2.x 查询完成,返回 nil 自动提交事务 + return nil + }) + + if errDbTransaction != nil { + return resultDevContainerListVO, devstar_devcontainer.ErrFailedToOperateDevstarDevcontainerDB{ Action: "query user DevContainer List", - Message: err.Error(), + Message: errDbTransaction.Error(), } } - return devcontainerItemVOList, nil + return resultDevContainerListVO, nil } diff --git a/templates/repo/header.tmpl b/templates/repo/header.tmpl index d52891b02a..fefb5fe4b4 100644 --- a/templates/repo/header.tmpl +++ b/templates/repo/header.tmpl @@ -138,6 +138,13 @@ {{end}} + + {{if .Permission.CanWrite ctx.Consts.RepoUnitTypeCode}} + + {{svg "octicon-container"}} {{ctx.Locale.Tr "repo.dev_container"}} + + {{end}} + {{if .Permission.CanRead ctx.Consts.RepoUnitTypeIssues}} {{svg "octicon-issue-opened"}} {{ctx.Locale.Tr "repo.issues"}} @@ -234,3 +241,10 @@
+ + + diff --git a/templates/user/settings/dev_containers_list.tmpl b/templates/user/settings/dev_containers_list.tmpl new file mode 100644 index 0000000000..b0304c300f --- /dev/null +++ b/templates/user/settings/dev_containers_list.tmpl @@ -0,0 +1,38 @@ +{{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings repos")}} +
+

+ {{ctx.Locale.Tr "settings.dev_containers_list"}} +

+
+ {{if .DevContainers}} +
+ {{range .DevContainers}} +
+ + + {{svg "octicon-repo"}} {{ctx.Locale.Tr "repo.repo_name"}}: + + {{.RepoName}} + + +
+ {{end}} +
+ {{template "base/paginate" .}} + {{else}} +
+ {{ctx.Locale.Tr "settings.dev_containers_none"}} +
+ {{end}} +
+
+{{template "user/settings/layout_footer" .}} diff --git a/templates/user/settings/navbar.tmpl b/templates/user/settings/navbar.tmpl index c360944814..0d166b3bdf 100644 --- a/templates/user/settings/navbar.tmpl +++ b/templates/user/settings/navbar.tmpl @@ -54,5 +54,8 @@ {{ctx.Locale.Tr "settings.repos"}} + + {{ctx.Locale.Tr "settings.dev_containers_list"}} +