Skip to content

Commit

Permalink
Add repos list for admins (#2347)
Browse files Browse the repository at this point in the history
  • Loading branch information
qwerty287 committed Sep 8, 2023
1 parent d7000e0 commit d04bb72
Show file tree
Hide file tree
Showing 11 changed files with 242 additions and 59 deletions.
50 changes: 50 additions & 0 deletions cmd/server/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,56 @@ const docTemplate = `{
}
},
"/repos": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Repositories"
],
"summary": "List all repositories on the server. Requires admin rights.",
"parameters": [
{
"type": "string",
"default": "Bearer \u003cpersonal access token\u003e",
"description": "Insert your personal access token",
"name": "Authorization",
"in": "header",
"required": true
},
{
"type": "boolean",
"description": "only list active repos",
"name": "active",
"in": "query"
},
{
"type": "integer",
"default": 1,
"description": "for response pagination, page offset number",
"name": "page",
"in": "query"
},
{
"type": "integer",
"default": 50,
"description": "for response pagination, max items per page",
"name": "perPage",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Repo"
}
}
}
}
},
"post": {
"produces": [
"application/json"
Expand Down
25 changes: 25 additions & 0 deletions server/api/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -550,3 +550,28 @@ func MoveRepo(c *gin.Context) {
}
c.Status(http.StatusOK)
}

// GetAllRepos
//
// @Summary List all repositories on the server. Requires admin rights.
// @Router /repos [get]
// @Produce json
// @Success 200 {array} Repo
// @Tags Repositories
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
// @Param active query bool false "only list active repos"
// @Param page query int false "for response pagination, page offset number" default(1)
// @Param perPage query int false "for response pagination, max items per page" default(50)
func GetAllRepos(c *gin.Context) {
_store := store.FromContext(c)

active, _ := strconv.ParseBool(c.Query("active"))

repos, err := _store.RepoListAll(active, session.Pagination(c))
if err != nil {
c.String(http.StatusInternalServerError, "Error fetching repository list. %s", err)
return
}

c.JSON(http.StatusOK, repos)
}
122 changes: 63 additions & 59 deletions server/router/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,68 +68,72 @@ func apiRoutes(e *gin.RouterGroup) {
}
}

apiBase.GET("/repos/lookup/*repo_full_name", session.SetRepo(), session.SetPerm(), session.MustPull, api.LookupRepo)
apiBase.POST("/repos", session.MustUser(), api.PostRepo)
repoBase := apiBase.Group("/repos/:repo_id")
repo := apiBase.Group("/repos")
{
repoBase.Use(session.SetRepo())
repoBase.Use(session.SetPerm())
repo.GET("/lookup/*repo_full_name", session.SetRepo(), session.SetPerm(), session.MustPull, api.LookupRepo)
repo.POST("", session.MustUser(), api.PostRepo)
repo.GET("", session.MustAdmin(), api.GetAllRepos)
repoBase := repo.Group("/:repo_id")
{
repoBase.Use(session.SetRepo())
repoBase.Use(session.SetPerm())

repoBase.GET("/permissions", api.GetRepoPermissions)
repoBase.GET("/permissions", api.GetRepoPermissions)

repo := repoBase.Group("")
{
repo.Use(session.MustPull)

repo.GET("", api.GetRepo)

repo.GET("/branches", api.GetRepoBranches)
repo.GET("/pull_requests", api.GetRepoPullRequests)

repo.GET("/pipelines", api.GetPipelines)
repo.POST("/pipelines", session.MustPush, api.CreatePipeline)
repo.GET("/pipelines/:number", api.GetPipeline)
repo.GET("/pipelines/:number/config", api.GetPipelineConfig)

// requires push permissions
repo.POST("/pipelines/:number", session.MustPush, api.PostPipeline)
repo.POST("/pipelines/:number/cancel", session.MustPush, api.CancelPipeline)
repo.POST("/pipelines/:number/approve", session.MustPush, api.PostApproval)
repo.POST("/pipelines/:number/decline", session.MustPush, api.PostDecline)

repo.GET("/logs/:number/:stepId", api.GetStepLogs)

// requires push permissions
repo.DELETE("/logs/:number", session.MustPush, api.DeletePipelineLogs)

// requires push permissions
repo.GET("/secrets", session.MustPush, api.GetSecretList)
repo.POST("/secrets", session.MustPush, api.PostSecret)
repo.GET("/secrets/:secret", session.MustPush, api.GetSecret)
repo.PATCH("/secrets/:secret", session.MustPush, api.PatchSecret)
repo.DELETE("/secrets/:secret", session.MustPush, api.DeleteSecret)

// requires push permissions
repo.GET("/registry", session.MustPush, api.GetRegistryList)
repo.POST("/registry", session.MustPush, api.PostRegistry)
repo.GET("/registry/:registry", session.MustPush, api.GetRegistry)
repo.PATCH("/registry/:registry", session.MustPush, api.PatchRegistry)
repo.DELETE("/registry/:registry", session.MustPush, api.DeleteRegistry)

// requires push permissions
repo.GET("/cron", session.MustPush, api.GetCronList)
repo.POST("/cron", session.MustPush, api.PostCron)
repo.GET("/cron/:cron", session.MustPush, api.GetCron)
repo.POST("/cron/:cron", session.MustPush, api.RunCron)
repo.PATCH("/cron/:cron", session.MustPush, api.PatchCron)
repo.DELETE("/cron/:cron", session.MustPush, api.DeleteCron)

// requires admin permissions
repo.PATCH("", session.MustRepoAdmin(), api.PatchRepo)
repo.DELETE("", session.MustRepoAdmin(), api.DeleteRepo)
repo.POST("/chown", session.MustRepoAdmin(), api.ChownRepo)
repo.POST("/repair", session.MustRepoAdmin(), api.RepairRepo)
repo.POST("/move", session.MustRepoAdmin(), api.MoveRepo)
repo := repoBase.Group("")
{
repo.Use(session.MustPull)

repo.GET("", api.GetRepo)

repo.GET("/branches", api.GetRepoBranches)
repo.GET("/pull_requests", api.GetRepoPullRequests)

repo.GET("/pipelines", api.GetPipelines)
repo.POST("/pipelines", session.MustPush, api.CreatePipeline)
repo.GET("/pipelines/:number", api.GetPipeline)
repo.GET("/pipelines/:number/config", api.GetPipelineConfig)

// requires push permissions
repo.POST("/pipelines/:number", session.MustPush, api.PostPipeline)
repo.POST("/pipelines/:number/cancel", session.MustPush, api.CancelPipeline)
repo.POST("/pipelines/:number/approve", session.MustPush, api.PostApproval)
repo.POST("/pipelines/:number/decline", session.MustPush, api.PostDecline)

repo.GET("/logs/:number/:stepId", api.GetStepLogs)

// requires push permissions
repo.DELETE("/logs/:number", session.MustPush, api.DeletePipelineLogs)

// requires push permissions
repo.GET("/secrets", session.MustPush, api.GetSecretList)
repo.POST("/secrets", session.MustPush, api.PostSecret)
repo.GET("/secrets/:secret", session.MustPush, api.GetSecret)
repo.PATCH("/secrets/:secret", session.MustPush, api.PatchSecret)
repo.DELETE("/secrets/:secret", session.MustPush, api.DeleteSecret)

// requires push permissions
repo.GET("/registry", session.MustPush, api.GetRegistryList)
repo.POST("/registry", session.MustPush, api.PostRegistry)
repo.GET("/registry/:registry", session.MustPush, api.GetRegistry)
repo.PATCH("/registry/:registry", session.MustPush, api.PatchRegistry)
repo.DELETE("/registry/:registry", session.MustPush, api.DeleteRegistry)

// requires push permissions
repo.GET("/cron", session.MustPush, api.GetCronList)
repo.POST("/cron", session.MustPush, api.PostCron)
repo.GET("/cron/:cron", session.MustPush, api.GetCron)
repo.POST("/cron/:cron", session.MustPush, api.RunCron)
repo.PATCH("/cron/:cron", session.MustPush, api.PatchCron)
repo.DELETE("/cron/:cron", session.MustPush, api.DeleteCron)

// requires admin permissions
repo.PATCH("", session.MustRepoAdmin(), api.PatchRepo)
repo.DELETE("", session.MustRepoAdmin(), api.DeleteRepo)
repo.POST("/chown", session.MustRepoAdmin(), api.ChownRepo)
repo.POST("/repair", session.MustRepoAdmin(), api.RepairRepo)
repo.POST("/move", session.MustRepoAdmin(), api.MoveRepo)
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions server/store/datastore/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,15 @@ func (s storage) RepoList(user *model.User, owned, active bool) ([]*model.Repo,
Asc("repo_full_name").
Find(&repos)
}

// RepoListAll list all repos
func (s storage) RepoListAll(active bool, p *model.ListOptions) ([]*model.Repo, error) {
repos := make([]*model.Repo, 0)
sess := s.paginate(p).Table("repos")
if active {
sess = sess.And(builder.Eq{"repos.repo_active": true})
}
return repos, sess.
Asc("repo_full_name").
Find(&repos)
}
26 changes: 26 additions & 0 deletions server/store/mocks/store.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions server/store/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ type Store interface {
// Repositories
RepoList(user *model.User, owned, active bool) ([]*model.Repo, error)
RepoListLatest(*model.User) ([]*model.Feed, error)
RepoListAll(active bool, p *model.ListOptions) ([]*model.Repo, error)

// Permissions
PermFind(user *model.User, repo *model.Repo) (*model.Perm, error)
Expand Down
1 change: 1 addition & 0 deletions web/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ declare module '@vue/runtime-core' {
AdminOrgsTab: typeof import('./src/components/admin/settings/AdminOrgsTab.vue')['default']
AdminQueueStats: typeof import('./src/components/admin/settings/queue/AdminQueueStats.vue')['default']
AdminQueueTab: typeof import('./src/components/admin/settings/AdminQueueTab.vue')['default']
AdminReposTab: typeof import('./src/components/admin/settings/AdminReposTab.vue')['default']
AdminSecretsTab: typeof import('./src/components/admin/settings/AdminSecretsTab.vue')['default']
AdminUsersTab: typeof import('./src/components/admin/settings/AdminUsersTab.vue')['default']
Badge: typeof import('./src/components/atomic/Badge.vue')['default']
Expand Down
8 changes: 8 additions & 0 deletions web/src/assets/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,14 @@
"deleted": "Organization deleted",
"delete_confirm": "Do you really want to delete this organization? This will also delete all repositories owned by this organization.",
"view": "View organization"
},
"repos": {
"repos": "Repositories",
"desc": "Repositories that are or were enabled on this server",
"none": "There are no repositories yet.",
"view": "View repository",
"settings": "Repository settings",
"disabled": "Disabled"
}
}
},
Expand Down
48 changes: 48 additions & 0 deletions web/src/components/admin/settings/AdminReposTab.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<template>
<Settings :title="$t('admin.settings.repos.repos')" :desc="$t('admin.settings.repos.desc')">
<div class="space-y-4 text-wp-text-100">
<ListItem
v-for="repo in repos"
:key="repo.id"
class="items-center gap-2 !bg-wp-background-200 !dark:bg-wp-background-100"
>
<span>{{ repo.full_name }}</span>
<div class="ml-auto flex items-center">
<Badge v-if="!repo.active" class="<md:hidden mr-2" :label="$t('admin.settings.repos.disabled')" />
<IconButton
icon="chevron-right"
:title="$t('admin.settings.repos.view')"
class="w-8 h-8"
:to="{ name: 'repo', params: { repoId: repo.id } }"
/>
<IconButton
icon="settings"
:title="$t('admin.settings.repos.settings')"
class="w-8 h-8"
:to="{ name: 'repo-settings', params: { repoId: repo.id } }"
/>
</div>
</ListItem>

<div v-if="repos?.length === 0" class="ml-2">{{ $t('admin.settings.orgs.none') }}</div>
</div>
</Settings>
</template>
<script lang="ts" setup>
import Badge from '~/components/atomic/Badge.vue';
import IconButton from '~/components/atomic/IconButton.vue';
import ListItem from '~/components/atomic/ListItem.vue';
import Settings from '~/components/layout/Settings.vue';
import useApiClient from '~/compositions/useApiClient';
import { usePagination } from '~/compositions/usePaginate';
import { Repo } from '~/lib/api/types';
const apiClient = useApiClient();
async function loadRepos(page: number): Promise<Repo[] | null> {
return apiClient.getAllRepos(page);
}
const { data: repos } = usePagination(loadRepos);
</script>
4 changes: 4 additions & 0 deletions web/src/lib/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,10 @@ export default class WoodpeckerClient extends ApiClient {
return this._delete(`/api/orgs/${org.id}`);
}

getAllRepos(page: number): Promise<Repo[] | null> {
return this._get(`/api/repos?page=${page}`) as Promise<Repo[] | null>;
}

// eslint-disable-next-line promise/prefer-await-to-callbacks
on(callback: (data: { pipeline?: Pipeline; repo?: Repo; step?: PipelineWorkflow }) => void): EventSource {
return this._subscribe('/api/stream/events', callback, {
Expand Down
4 changes: 4 additions & 0 deletions web/src/views/admin/AdminSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
<Tab id="secrets" :title="$t('admin.settings.secrets.secrets')">
<AdminSecretsTab />
</Tab>
<Tab id="repos" :title="$t('admin.settings.repos.repos')">
<AdminReposTab />
</Tab>
<Tab id="users" :title="$t('admin.settings.users.users')">
<AdminUsersTab />
</Tab>
Expand All @@ -29,6 +32,7 @@ import { useRouter } from 'vue-router';
import AdminAgentsTab from '~/components/admin/settings/AdminAgentsTab.vue';
import AdminOrgsTab from '~/components/admin/settings/AdminOrgsTab.vue';
import AdminQueueTab from '~/components/admin/settings/AdminQueueTab.vue';
import AdminReposTab from '~/components/admin/settings/AdminReposTab.vue';
import AdminSecretsTab from '~/components/admin/settings/AdminSecretsTab.vue';
import AdminUsersTab from '~/components/admin/settings/AdminUsersTab.vue';
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
Expand Down

0 comments on commit d04bb72

Please sign in to comment.