mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-09-25 05:00:05 +00:00
parent
d5e68efc95
commit
69917c1a0d
6 changed files with 149 additions and 61 deletions
|
@ -1471,6 +1471,32 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/repair": {
|
||||||
|
"post": {
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Repositories"
|
||||||
|
],
|
||||||
|
"summary": "Repair 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
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "No Content"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{repo_id}": {
|
"/repos/{repo_id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
|
@ -2797,8 +2823,8 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"204": {
|
||||||
"description": "OK"
|
"description": "No Content"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -401,70 +401,15 @@ func DeleteRepo(c *gin.Context) {
|
||||||
// @Summary Repair a repository
|
// @Summary Repair a repository
|
||||||
// @Router /repos/{repo_id}/repair [post]
|
// @Router /repos/{repo_id}/repair [post]
|
||||||
// @Produce plain
|
// @Produce plain
|
||||||
// @Success 200
|
// @Success 204
|
||||||
// @Tags Repositories
|
// @Tags Repositories
|
||||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||||
// @Param repo_id path int true "the repository id"
|
// @Param repo_id path int true "the repository id"
|
||||||
func RepairRepo(c *gin.Context) {
|
func RepairRepo(c *gin.Context) {
|
||||||
forge := server.Config.Services.Forge
|
|
||||||
_store := store.FromContext(c)
|
|
||||||
repo := session.Repo(c)
|
repo := session.Repo(c)
|
||||||
user := session.User(c)
|
repairRepo(c, repo, true)
|
||||||
|
|
||||||
// creates the jwt token used to verify the repository
|
c.Status(http.StatusNoContent)
|
||||||
t := token.New(token.HookToken, repo.FullName)
|
|
||||||
sig, err := t.Sign(repo.Hash)
|
|
||||||
if err != nil {
|
|
||||||
c.String(http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// reconstruct the link
|
|
||||||
host := server.Config.Server.WebhookHost
|
|
||||||
link := fmt.Sprintf(
|
|
||||||
"%s/api/hook?access_token=%s",
|
|
||||||
host,
|
|
||||||
sig,
|
|
||||||
)
|
|
||||||
|
|
||||||
from, err := forge.Repo(c, user, repo.ForgeRemoteID, repo.Owner, repo.Name)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msgf("get repo '%s/%s' from forge", repo.Owner, repo.Name)
|
|
||||||
c.AbortWithStatus(http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if repo.FullName != from.FullName {
|
|
||||||
// create a redirection
|
|
||||||
err = _store.CreateRedirection(&model.Redirection{RepoID: repo.ID, FullName: repo.FullName})
|
|
||||||
if err != nil {
|
|
||||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
repo.Update(from)
|
|
||||||
if err := _store.UpdateRepo(repo); err != nil {
|
|
||||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
repo.Perm.Pull = from.Perm.Pull
|
|
||||||
repo.Perm.Push = from.Perm.Push
|
|
||||||
repo.Perm.Admin = from.Perm.Admin
|
|
||||||
if err := _store.PermUpsert(repo.Perm); err != nil {
|
|
||||||
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := forge.Deactivate(c, user, repo, host); err != nil {
|
|
||||||
log.Trace().Err(err).Msgf("deactivate repo '%s' to repair failed", repo.FullName)
|
|
||||||
}
|
|
||||||
if err := forge.Activate(c, user, repo, link); err != nil {
|
|
||||||
c.String(http.StatusInternalServerError, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Status(http.StatusOK)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MoveRepo
|
// MoveRepo
|
||||||
|
@ -575,3 +520,91 @@ func GetAllRepos(c *gin.Context) {
|
||||||
|
|
||||||
c.JSON(http.StatusOK, repos)
|
c.JSON(http.StatusOK, repos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RepairAllRepos
|
||||||
|
//
|
||||||
|
// @Summary Repair all repositories on the server. Requires admin rights.
|
||||||
|
// @Router /repos/repair [post]
|
||||||
|
// @Produce plain
|
||||||
|
// @Success 204
|
||||||
|
// @Tags Repositories
|
||||||
|
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||||
|
func RepairAllRepos(c *gin.Context) {
|
||||||
|
_store := store.FromContext(c)
|
||||||
|
|
||||||
|
repos, err := _store.RepoListAll(true, &model.ListOptions{All: true})
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusInternalServerError, "Error fetching repository list. %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, r := range repos {
|
||||||
|
repairRepo(c, r, false)
|
||||||
|
if c.Writer.Written() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func repairRepo(c *gin.Context, repo *model.Repo, withPerms bool) {
|
||||||
|
forge := server.Config.Services.Forge
|
||||||
|
_store := store.FromContext(c)
|
||||||
|
user := session.User(c)
|
||||||
|
|
||||||
|
// creates the jwt token used to verify the repository
|
||||||
|
t := token.New(token.HookToken, repo.FullName)
|
||||||
|
sig, err := t.Sign(repo.Hash)
|
||||||
|
if err != nil {
|
||||||
|
c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// reconstruct the link
|
||||||
|
host := server.Config.Server.WebhookHost
|
||||||
|
link := fmt.Sprintf(
|
||||||
|
"%s/api/hook?access_token=%s",
|
||||||
|
host,
|
||||||
|
sig,
|
||||||
|
)
|
||||||
|
|
||||||
|
from, err := forge.Repo(c, user, repo.ForgeRemoteID, repo.Owner, repo.Name)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msgf("get repo '%s/%s' from forge", repo.Owner, repo.Name)
|
||||||
|
c.AbortWithStatus(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if repo.FullName != from.FullName {
|
||||||
|
// create a redirection
|
||||||
|
err = _store.CreateRedirection(&model.Redirection{RepoID: repo.ID, FullName: repo.FullName})
|
||||||
|
if err != nil {
|
||||||
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repo.Update(from)
|
||||||
|
if err := _store.UpdateRepo(repo); err != nil {
|
||||||
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if withPerms {
|
||||||
|
repo.Perm.Pull = from.Perm.Pull
|
||||||
|
repo.Perm.Push = from.Perm.Push
|
||||||
|
repo.Perm.Admin = from.Perm.Admin
|
||||||
|
if err := _store.PermUpsert(repo.Perm); err != nil {
|
||||||
|
_ = c.AbortWithError(http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := forge.Deactivate(c, user, repo, host); err != nil {
|
||||||
|
log.Trace().Err(err).Msgf("deactivate repo '%s' to repair failed", repo.FullName)
|
||||||
|
}
|
||||||
|
if err := forge.Activate(c, user, repo, link); err != nil {
|
||||||
|
c.String(http.StatusInternalServerError, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ func apiRoutes(e *gin.RouterGroup) {
|
||||||
repo.GET("/lookup/*repo_full_name", session.SetRepo(), session.SetPerm(), session.MustPull, api.LookupRepo)
|
repo.GET("/lookup/*repo_full_name", session.SetRepo(), session.SetPerm(), session.MustPull, api.LookupRepo)
|
||||||
repo.POST("", session.MustUser(), api.PostRepo)
|
repo.POST("", session.MustUser(), api.PostRepo)
|
||||||
repo.GET("", session.MustAdmin(), api.GetAllRepos)
|
repo.GET("", session.MustAdmin(), api.GetAllRepos)
|
||||||
|
repo.POST("/repair", session.MustAdmin(), api.RepairAllRepos)
|
||||||
repoBase := repo.Group("/:repo_id")
|
repoBase := repo.Group("/:repo_id")
|
||||||
{
|
{
|
||||||
repoBase.Use(session.SetRepo())
|
repoBase.Use(session.SetRepo())
|
||||||
|
|
|
@ -440,7 +440,11 @@
|
||||||
"none": "There are no repositories yet.",
|
"none": "There are no repositories yet.",
|
||||||
"view": "View repository",
|
"view": "View repository",
|
||||||
"settings": "Repository settings",
|
"settings": "Repository settings",
|
||||||
"disabled": "Disabled"
|
"disabled": "Disabled",
|
||||||
|
"repair": {
|
||||||
|
"repair": "Repair all",
|
||||||
|
"success": "Repositories repaired"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
<template>
|
<template>
|
||||||
<Settings :title="$t('admin.settings.repos.repos')" :desc="$t('admin.settings.repos.desc')">
|
<Settings :title="$t('admin.settings.repos.repos')" :desc="$t('admin.settings.repos.desc')">
|
||||||
|
<template #titleActions>
|
||||||
|
<Button
|
||||||
|
start-icon="heal"
|
||||||
|
:is-loading="isRepairingRepos"
|
||||||
|
:text="$t('admin.settings.repos.repair.repair')"
|
||||||
|
@click="repairRepos"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
<div class="space-y-4 text-wp-text-100">
|
<div class="space-y-4 text-wp-text-100">
|
||||||
<ListItem
|
<ListItem
|
||||||
v-for="repo in repos"
|
v-for="repo in repos"
|
||||||
|
@ -30,19 +39,30 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { useI18n } from 'vue-i18n';
|
||||||
|
|
||||||
import Badge from '~/components/atomic/Badge.vue';
|
import Badge from '~/components/atomic/Badge.vue';
|
||||||
import IconButton from '~/components/atomic/IconButton.vue';
|
import IconButton from '~/components/atomic/IconButton.vue';
|
||||||
import ListItem from '~/components/atomic/ListItem.vue';
|
import ListItem from '~/components/atomic/ListItem.vue';
|
||||||
import Settings from '~/components/layout/Settings.vue';
|
import Settings from '~/components/layout/Settings.vue';
|
||||||
import useApiClient from '~/compositions/useApiClient';
|
import useApiClient from '~/compositions/useApiClient';
|
||||||
|
import { useAsyncAction } from '~/compositions/useAsyncAction';
|
||||||
|
import useNotifications from '~/compositions/useNotifications';
|
||||||
import { usePagination } from '~/compositions/usePaginate';
|
import { usePagination } from '~/compositions/usePaginate';
|
||||||
import { Repo } from '~/lib/api/types';
|
import { Repo } from '~/lib/api/types';
|
||||||
|
|
||||||
const apiClient = useApiClient();
|
const apiClient = useApiClient();
|
||||||
|
const notifications = useNotifications();
|
||||||
|
const i18n = useI18n();
|
||||||
|
|
||||||
async function loadRepos(page: number): Promise<Repo[] | null> {
|
async function loadRepos(page: number): Promise<Repo[] | null> {
|
||||||
return apiClient.getAllRepos(page);
|
return apiClient.getAllRepos(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: repos } = usePagination(loadRepos);
|
const { data: repos } = usePagination(loadRepos);
|
||||||
|
|
||||||
|
const { doSubmit: repairRepos, isLoading: isRepairingRepos } = useAsyncAction(async () => {
|
||||||
|
await apiClient.repairAllRepos();
|
||||||
|
notifications.notify({ title: i18n.t('admin.settings.repos.repair.success'), type: 'success' });
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -314,6 +314,10 @@ export default class WoodpeckerClient extends ApiClient {
|
||||||
return this._get(`/api/repos?page=${page}`) as Promise<Repo[] | null>;
|
return this._get(`/api/repos?page=${page}`) as Promise<Repo[] | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
repairAllRepos(): Promise<unknown> {
|
||||||
|
return this._post(`/api/repos/repair`) as Promise<unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line promise/prefer-await-to-callbacks
|
// eslint-disable-next-line promise/prefer-await-to-callbacks
|
||||||
on(callback: (data: { pipeline?: Pipeline; repo?: Repo }) => void): EventSource {
|
on(callback: (data: { pipeline?: Pipeline; repo?: Repo }) => void): EventSource {
|
||||||
return this._subscribe('/api/stream/events', callback, {
|
return this._subscribe('/api/stream/events', callback, {
|
||||||
|
|
Loading…
Reference in a new issue