mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-26 02:10:31 +00:00
Add api for forges (#3733)
This commit is contained in:
parent
eadead6c07
commit
fbb96ff8f5
15 changed files with 611 additions and 98 deletions
|
@ -597,6 +597,197 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/forges": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Forges"
|
||||
],
|
||||
"summary": "List forges",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header"
|
||||
},
|
||||
{
|
||||
"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/Forge"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Creates a new forge with a random token",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Forges"
|
||||
],
|
||||
"summary": "Create a new forge",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "the forge's data (only 'name' and 'no_schedule' are read)",
|
||||
"name": "forge",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Forge"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Forge"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/forges/{forgeId}": {
|
||||
"get": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Forges"
|
||||
],
|
||||
"summary": "Get a forge",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the forge's id",
|
||||
"name": "forgeId",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Forge"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"produces": [
|
||||
"text/plain"
|
||||
],
|
||||
"tags": [
|
||||
"Forges"
|
||||
],
|
||||
"summary": "Delete a forge",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the forge's id",
|
||||
"name": "forgeId",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
},
|
||||
"patch": {
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Forges"
|
||||
],
|
||||
"summary": "Update a forge",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"default": "Bearer \u003cpersonal access token\u003e",
|
||||
"description": "Insert your personal access token",
|
||||
"name": "Authorization",
|
||||
"in": "header",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "the forge's id",
|
||||
"name": "forgeId",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"description": "the forge's data",
|
||||
"name": "forgeData",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Forge"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Forge"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/healthz": {
|
||||
"get": {
|
||||
"description": "If everything is fine, just a 204 will be returned, a 500 signals server state is unhealthy.",
|
||||
|
@ -3902,6 +4093,34 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"Forge": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"additional_options": {
|
||||
"type": "object",
|
||||
"additionalProperties": {}
|
||||
},
|
||||
"client": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"oauth_host": {
|
||||
"description": "public url for oauth if different from url",
|
||||
"type": "string"
|
||||
},
|
||||
"skip_verify": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/model.ForgeType"
|
||||
},
|
||||
"url": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LogEntry": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -4524,6 +4743,27 @@ const docTemplate = `{
|
|||
"EventManual"
|
||||
]
|
||||
},
|
||||
"model.ForgeType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"github",
|
||||
"gitlab",
|
||||
"gitea",
|
||||
"forgejo",
|
||||
"bitbucket",
|
||||
"bitbucket-dc",
|
||||
"addon"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"ForgeTypeGithub",
|
||||
"ForgeTypeGitlab",
|
||||
"ForgeTypeGitea",
|
||||
"ForgeTypeForgejo",
|
||||
"ForgeTypeBitbucket",
|
||||
"ForgeTypeBitbucketDatacenter",
|
||||
"ForgeTypeAddon"
|
||||
]
|
||||
},
|
||||
"model.Workflow": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
208
server/api/forge.go
Normal file
208
server/api/forge.go
Normal file
|
@ -0,0 +1,208 @@
|
|||
// Copyright 2024 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store"
|
||||
)
|
||||
|
||||
// GetForges
|
||||
//
|
||||
// @Summary List forges
|
||||
// @Router /forges [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Forge
|
||||
// @Tags Forges
|
||||
// @Param Authorization header string false "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @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 GetForges(c *gin.Context) {
|
||||
forges, err := store.FromContext(c).ForgeList(session.Pagination(c))
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error getting forge list. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
user := session.User(c)
|
||||
if user != nil && user.Admin {
|
||||
c.JSON(http.StatusOK, forges)
|
||||
return
|
||||
}
|
||||
|
||||
// copy forges data without sensitive information
|
||||
for i, forge := range forges {
|
||||
forges[i] = forge.PublicCopy()
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, forges)
|
||||
}
|
||||
|
||||
// GetForge
|
||||
//
|
||||
// @Summary Get a forge
|
||||
// @Router /forges/{forgeId} [get]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Forge
|
||||
// @Tags Forges
|
||||
// @Param Authorization header string false "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param forgeId path int true "the forge's id"
|
||||
func GetForge(c *gin.Context) {
|
||||
forgeID, err := strconv.ParseInt(c.Param("forgeId"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
forge, err := store.FromContext(c).ForgeGet(forgeID)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
user := session.User(c)
|
||||
if user != nil && user.Admin {
|
||||
c.JSON(http.StatusOK, forge)
|
||||
} else {
|
||||
c.JSON(http.StatusOK, forge.PublicCopy())
|
||||
}
|
||||
}
|
||||
|
||||
// PatchForge
|
||||
//
|
||||
// @Summary Update a forge
|
||||
// @Router /forges/{forgeId} [patch]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Forge
|
||||
// @Tags Forges
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param forgeId path int true "the forge's id"
|
||||
// @Param forgeData body Forge true "the forge's data"
|
||||
func PatchForge(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
|
||||
// use this struct to allow updating the client secret
|
||||
type ForgeWithClientSecret struct {
|
||||
model.Forge
|
||||
ClientSecret string `json:"client_secret"`
|
||||
}
|
||||
|
||||
in := &ForgeWithClientSecret{}
|
||||
err := c.Bind(in)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
forgeID, err := strconv.ParseInt(c.Param("forgeId"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
forge, err := _store.ForgeGet(forgeID)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
forge.URL = in.URL
|
||||
forge.Type = in.Type
|
||||
forge.Client = in.Client
|
||||
forge.OAuthHost = in.OAuthHost
|
||||
forge.SkipVerify = in.SkipVerify
|
||||
forge.AdditionalOptions = in.AdditionalOptions
|
||||
if in.ClientSecret != "" {
|
||||
forge.ClientSecret = in.ClientSecret
|
||||
}
|
||||
|
||||
err = _store.ForgeUpdate(forge)
|
||||
if err != nil {
|
||||
c.AbortWithStatus(http.StatusConflict)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, forge)
|
||||
}
|
||||
|
||||
// PostForge
|
||||
//
|
||||
// @Summary Create a new forge
|
||||
// @Description Creates a new forge with a random token
|
||||
// @Router /forges [post]
|
||||
// @Produce json
|
||||
// @Success 200 {object} Forge
|
||||
// @Tags Forges
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param forge body Forge true "the forge's data (only 'name' and 'no_schedule' are read)"
|
||||
func PostForge(c *gin.Context) {
|
||||
in := &model.Forge{}
|
||||
err := c.Bind(in)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
forge := &model.Forge{
|
||||
URL: in.URL,
|
||||
Type: in.Type,
|
||||
Client: in.Client,
|
||||
ClientSecret: in.ClientSecret,
|
||||
OAuthHost: in.OAuthHost,
|
||||
SkipVerify: in.SkipVerify,
|
||||
AdditionalOptions: in.AdditionalOptions,
|
||||
}
|
||||
if err = store.FromContext(c).ForgeCreate(forge); err != nil {
|
||||
c.String(http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, forge)
|
||||
}
|
||||
|
||||
// DeleteForge
|
||||
//
|
||||
// @Summary Delete a forge
|
||||
// @Router /forges/{forgeId} [delete]
|
||||
// @Produce plain
|
||||
// @Success 200
|
||||
// @Tags Forges
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param forgeId path int true "the forge's id"
|
||||
func DeleteForge(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
|
||||
forgeID, err := strconv.ParseInt(c.Param("forgeId"), 10, 64)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
forge, err := _store.ForgeGet(forgeID)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = _store.ForgeDelete(forge); err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error deleting user. %s", err)
|
||||
return
|
||||
}
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
|
@ -28,6 +28,26 @@ import (
|
|||
"go.woodpecker-ci.org/woodpecker/v2/server/store"
|
||||
)
|
||||
|
||||
// GetOrgs
|
||||
//
|
||||
// @Summary List organizations
|
||||
// @Description Returns all registered orgs in the system. Requires admin rights.
|
||||
// @Router /orgs [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Org
|
||||
// @Tags Orgs
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @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 GetOrgs(c *gin.Context) {
|
||||
orgs, err := store.FromContext(c).OrgList(session.Pagination(c))
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error getting user list. %s", err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, orgs)
|
||||
}
|
||||
|
||||
// GetOrg
|
||||
//
|
||||
// @Summary Get an organization
|
||||
|
@ -167,3 +187,31 @@ func LookupOrg(c *gin.Context) {
|
|||
|
||||
c.JSON(http.StatusOK, org)
|
||||
}
|
||||
|
||||
// DeleteOrg
|
||||
//
|
||||
// @Summary Delete an organization
|
||||
// @Description Deletes the given org. Requires admin rights.
|
||||
// @Router /orgs/{id} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
// @Tags Orgs
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param id path string true "the org's id"
|
||||
func DeleteOrg(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = _store.OrgDelete(orgID)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
// Copyright 2023 Woodpecker Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session"
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/store"
|
||||
)
|
||||
|
||||
// GetOrgs
|
||||
//
|
||||
// @Summary List organizations
|
||||
// @Description Returns all registered orgs in the system. Requires admin rights.
|
||||
// @Router /orgs [get]
|
||||
// @Produce json
|
||||
// @Success 200 {array} Org
|
||||
// @Tags Orgs
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @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 GetOrgs(c *gin.Context) {
|
||||
orgs, err := store.FromContext(c).OrgList(session.Pagination(c))
|
||||
if err != nil {
|
||||
c.String(http.StatusInternalServerError, "Error getting user list. %s", err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, orgs)
|
||||
}
|
||||
|
||||
// DeleteOrg
|
||||
//
|
||||
// @Summary Delete an organization
|
||||
// @Description Deletes the given org. Requires admin rights.
|
||||
// @Router /orgs/{id} [delete]
|
||||
// @Produce plain
|
||||
// @Success 204
|
||||
// @Tags Orgs
|
||||
// @Param Authorization header string true "Insert your personal access token" default(Bearer <personal access token>)
|
||||
// @Param id path string true "the org's id"
|
||||
func DeleteOrg(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
|
||||
orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64)
|
||||
if err != nil {
|
||||
c.String(http.StatusBadRequest, "Error parsing org id. %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = _store.OrgDelete(orgID)
|
||||
if err != nil {
|
||||
handleDBError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Status(http.StatusNoContent)
|
||||
}
|
|
@ -27,12 +27,23 @@ const (
|
|||
)
|
||||
|
||||
type Forge struct {
|
||||
ID int64 `xorm:"pk autoincr 'id'"`
|
||||
Type ForgeType `xorm:"VARCHAR(250)"`
|
||||
URL string `xorm:"VARCHAR(500) 'url'"`
|
||||
Client string `xorm:"VARCHAR(250)"`
|
||||
ClientSecret string `xorm:"VARCHAR(250)"`
|
||||
SkipVerify bool `xorm:"bool"`
|
||||
OAuthHost string `xorm:"VARCHAR(250) 'oauth_host'"` // public url for oauth if different from url
|
||||
AdditionalOptions map[string]any `xorm:"json"`
|
||||
ID int64 `json:"id" xorm:"pk autoincr 'id'"`
|
||||
Type ForgeType `json:"type" xorm:"VARCHAR(250)"`
|
||||
URL string `json:"url" xorm:"VARCHAR(500) 'url'"`
|
||||
Client string `json:"client,omitempty" xorm:"VARCHAR(250)"`
|
||||
ClientSecret string `json:"-" xorm:"VARCHAR(250)"` // do not expose client secret
|
||||
SkipVerify bool `json:"skip_verify,omitempty" xorm:"bool"`
|
||||
OAuthHost string `json:"oauth_host,omitempty" xorm:"VARCHAR(250) 'oauth_host'"` // public url for oauth if different from url
|
||||
AdditionalOptions map[string]any `json:"additional_options,omitempty" xorm:"json"`
|
||||
} // @name Forge
|
||||
|
||||
// PublicCopy returns a copy of the forge without sensitive information and technical details.
|
||||
func (f *Forge) PublicCopy() *Forge {
|
||||
forge := &Forge{
|
||||
ID: f.ID,
|
||||
Type: f.Type,
|
||||
URL: f.URL,
|
||||
}
|
||||
|
||||
return forge
|
||||
}
|
||||
|
|
|
@ -202,6 +202,16 @@ func apiRoutes(e *gin.RouterGroup) {
|
|||
agentBase.DELETE("/:agent", api.DeleteAgent)
|
||||
}
|
||||
|
||||
apiBase.GET("/forges", api.GetForges)
|
||||
apiBase.GET("/forges/:forgeId", api.GetForge)
|
||||
forgeBase := apiBase.Group("/forges")
|
||||
{
|
||||
forgeBase.Use(session.MustAdmin())
|
||||
forgeBase.POST("", api.PostForge)
|
||||
forgeBase.PATCH("/:forgeId", api.PatchForge)
|
||||
forgeBase.DELETE("/:forgeId", api.DeleteForge)
|
||||
}
|
||||
|
||||
apiBase.GET("/signature/public-key", session.MustUser(), api.GetSignaturePublicKey)
|
||||
|
||||
apiBase.POST("/hook", api.PostHook)
|
||||
|
|
|
@ -39,20 +39,11 @@ func Config(c *gin.Context) {
|
|||
csrf, _ = t.Sign(user.Hash)
|
||||
}
|
||||
|
||||
// TODO: remove this and use the forge type from the corresponding repo
|
||||
mainForge, err := server.Config.Services.Manager.ForgeMain()
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("could not get main forge")
|
||||
c.AbortWithStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
configData := map[string]any{
|
||||
"user": user,
|
||||
"csrf": csrf,
|
||||
"version": version.String(),
|
||||
"skip_version_check": server.Config.WebUI.SkipVersionCheck,
|
||||
"forge": mainForge.Name(),
|
||||
"root_path": server.Config.Server.RootPath,
|
||||
"enable_swagger": server.Config.WebUI.EnableSwagger,
|
||||
}
|
||||
|
@ -85,7 +76,6 @@ const configTemplate = `
|
|||
window.WOODPECKER_USER = {{ json .user }};
|
||||
window.WOODPECKER_CSRF = "{{ .csrf }}";
|
||||
window.WOODPECKER_VERSION = "{{ .version }}";
|
||||
window.WOODPECKER_FORGE = "{{ .forge }}";
|
||||
window.WOODPECKER_ROOT_PATH = "{{ .root_path }}";
|
||||
window.WOODPECKER_ENABLE_SWAGGER = {{ .enable_swagger }};
|
||||
window.WOODPECKER_SKIP_VERSION_CHECK = {{ .skip_version_check }}
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<i-simple-icons-gitea v-else-if="name === 'gitea'" class="h-8 w-8" />
|
||||
<i-simple-icons-forgejo v-else-if="name === 'forgejo'" class="h-8 w-8" />
|
||||
<i-ph-gitlab-logo-simple-fill v-else-if="name === 'gitlab'" class="h-8 w-8" />
|
||||
<i-mdi-bitbucket v-else-if="name === 'bitbucket' || name === 'bitbucket_dc'" class="h-8 w-8" />
|
||||
<i-mdi-bitbucket v-else-if="name === 'bitbucket' || name === 'bitbucket-dc'" class="h-8 w-8" />
|
||||
<i-vaadin-question-circle-o v-else-if="name === 'question'" class="h-6 w-6" />
|
||||
<i-ic-twotone-add v-else-if="name === 'plus'" class="h-6 w-6" />
|
||||
<i-mdi-format-list-bulleted v-else-if="name === 'list'" class="h-6 w-6" />
|
||||
|
@ -86,7 +86,7 @@ export type IconNames =
|
|||
| 'gitea'
|
||||
| 'gitlab'
|
||||
| 'bitbucket'
|
||||
| 'bitbucket_dc'
|
||||
| 'bitbucket-dc'
|
||||
| 'forgejo'
|
||||
| 'question'
|
||||
| 'list'
|
||||
|
|
|
@ -6,7 +6,6 @@ declare global {
|
|||
WOODPECKER_VERSION: string | undefined;
|
||||
WOODPECKER_SKIP_VERSION_CHECK: boolean | undefined;
|
||||
WOODPECKER_CSRF: string | undefined;
|
||||
WOODPECKER_FORGE: 'github' | 'gitlab' | 'gitea' | 'forgejo' | 'bitbucket' | 'bitbucket_dc' | undefined;
|
||||
WOODPECKER_ROOT_PATH: string | undefined;
|
||||
WOODPECKER_ENABLE_SWAGGER: boolean | undefined;
|
||||
}
|
||||
|
@ -17,7 +16,6 @@ export default () => ({
|
|||
version: window.WOODPECKER_VERSION,
|
||||
skipVersionCheck: window.WOODPECKER_SKIP_VERSION_CHECK === true || false,
|
||||
csrf: window.WOODPECKER_CSRF ?? null,
|
||||
forge: window.WOODPECKER_FORGE ?? null,
|
||||
rootPath: window.WOODPECKER_ROOT_PATH ?? '',
|
||||
enableSwagger: window.WOODPECKER_ENABLE_SWAGGER === true || false,
|
||||
});
|
||||
|
|
30
web/src/compositions/useForgeStore.ts
Normal file
30
web/src/compositions/useForgeStore.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import { computed, reactive, type Ref } from 'vue';
|
||||
|
||||
import useApiClient from '~/compositions/useApiClient';
|
||||
import type { Forge } from '~/lib/api/types';
|
||||
|
||||
export const useForgeStore = defineStore('forges', () => {
|
||||
const apiClient = useApiClient();
|
||||
|
||||
const forges = reactive<Map<number, Forge>>(new Map());
|
||||
|
||||
async function loadForge(forgeId: number): Promise<Forge> {
|
||||
const forge = await apiClient.getForge(forgeId);
|
||||
forges.set(forge.id, forge);
|
||||
return forge;
|
||||
}
|
||||
|
||||
async function getForge(forgeId: number): Promise<Ref<Forge | undefined>> {
|
||||
if (!forges.has(forgeId)) {
|
||||
await loadForge(forgeId);
|
||||
}
|
||||
|
||||
return computed(() => forges.get(forgeId));
|
||||
}
|
||||
|
||||
return {
|
||||
getForge,
|
||||
loadForge,
|
||||
};
|
||||
});
|
|
@ -2,6 +2,7 @@ import ApiClient, { encodeQueryString } from './client';
|
|||
import type {
|
||||
Agent,
|
||||
Cron,
|
||||
Forge,
|
||||
Org,
|
||||
OrgPermissions,
|
||||
Pipeline,
|
||||
|
@ -284,6 +285,27 @@ export default class WoodpeckerClient extends ApiClient {
|
|||
return this._delete(`/api/agents/${agent.id}`);
|
||||
}
|
||||
|
||||
getForges(opts?: PaginationOptions): Promise<Forge[] | null> {
|
||||
const query = encodeQueryString(opts);
|
||||
return this._get(`/api/forges?${query}`) as Promise<Forge[] | null>;
|
||||
}
|
||||
|
||||
getForge(forgeId: Forge['id']): Promise<Forge> {
|
||||
return this._get(`/api/forges/${forgeId}`) as Promise<Forge>;
|
||||
}
|
||||
|
||||
createForge(forge: Partial<Forge>): Promise<Forge> {
|
||||
return this._post('/api/forges', forge) as Promise<Forge>;
|
||||
}
|
||||
|
||||
updateForge(forge: Partial<Forge>): Promise<unknown> {
|
||||
return this._patch(`/api/forges/${forge.id}`, forge);
|
||||
}
|
||||
|
||||
deleteForge(forge: Forge): Promise<unknown> {
|
||||
return this._delete(`/api/forges/${forge.id}`);
|
||||
}
|
||||
|
||||
getQueueInfo(): Promise<QueueInfo> {
|
||||
return this._get('/api/queue/info') as Promise<QueueInfo>;
|
||||
}
|
||||
|
|
12
web/src/lib/api/types/forge.ts
Normal file
12
web/src/lib/api/types/forge.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
export type ForgeType = 'github' | 'gitlab' | 'gitea' | 'bitbucket' | 'bitbucket-dc' | 'addon';
|
||||
|
||||
export interface Forge {
|
||||
id: number;
|
||||
type: ForgeType;
|
||||
url: string;
|
||||
client?: string;
|
||||
client_secret?: string;
|
||||
skip_verify?: boolean;
|
||||
oauth_host?: string;
|
||||
additional_options?: Record<string, unknown>;
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
export * from './agent';
|
||||
export * from './cron';
|
||||
export * from './forge';
|
||||
export * from './org';
|
||||
export * from './pipeline';
|
||||
export * from './pipelineConfig';
|
||||
|
|
|
@ -9,6 +9,9 @@ export interface Repo {
|
|||
// The id of the repository on the source control management system.
|
||||
forge_remote_id: string;
|
||||
|
||||
// The id of the forge that the repository is on.
|
||||
forge_id: number;
|
||||
|
||||
// The source control management being used.
|
||||
// Currently, this is either 'git' or 'hg' (Mercurial).
|
||||
scm: string;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<a v-if="badgeUrl" :href="badgeUrl" target="_blank">
|
||||
<img :src="badgeUrl" />
|
||||
</a>
|
||||
<IconButton :href="repo.forge_url" :title="$t('repo.open_in_forge')" :icon="forge ?? 'repo'" class="forge" />
|
||||
<IconButton :href="repo.forge_url" :title="$t('repo.open_in_forge')" :icon="forgeIcon" class="forge" />
|
||||
<IconButton
|
||||
v-if="repoPermissions.admin"
|
||||
:to="{ name: 'repo-settings' }"
|
||||
|
@ -49,6 +49,7 @@ import { computed, onMounted, provide, ref, toRef, watch } from 'vue';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import type { IconNames } from '~/components/atomic/Icon.vue';
|
||||
import IconButton from '~/components/atomic/IconButton.vue';
|
||||
import ManualPipelinePopup from '~/components/layout/popups/ManualPipelinePopup.vue';
|
||||
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
||||
|
@ -56,8 +57,9 @@ import Tab from '~/components/layout/scaffold/Tab.vue';
|
|||
import useApiClient from '~/compositions/useApiClient';
|
||||
import useAuthentication from '~/compositions/useAuthentication';
|
||||
import useConfig from '~/compositions/useConfig';
|
||||
import { useForgeStore } from '~/compositions/useForgeStore';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
||||
import type { RepoPermissions } from '~/lib/api/types';
|
||||
import type { Forge, RepoPermissions } from '~/lib/api/types';
|
||||
import { usePipelineStore } from '~/store/pipelines';
|
||||
import { useRepoStore } from '~/store/repos';
|
||||
|
||||
|
@ -76,14 +78,21 @@ const route = useRoute();
|
|||
const router = useRouter();
|
||||
const i18n = useI18n();
|
||||
const config = useConfig();
|
||||
const forgeStore = useForgeStore();
|
||||
|
||||
const { forge } = useConfig(); // TODO: remove this and use the forge type from the corresponding repo
|
||||
const repo = repoStore.getRepo(repositoryId);
|
||||
const repoPermissions = ref<RepoPermissions>();
|
||||
const pipelines = pipelineStore.getRepoPipelines(repositoryId);
|
||||
provide('repo', repo);
|
||||
provide('repo-permissions', repoPermissions);
|
||||
provide('pipelines', pipelines);
|
||||
const forge = ref<Forge>();
|
||||
const forgeIcon = computed<IconNames>(() => {
|
||||
if (forge.value && forge.value.type !== 'addon') {
|
||||
return forge.value.type;
|
||||
}
|
||||
return 'repo';
|
||||
});
|
||||
|
||||
const showManualPipelinePopup = ref(false);
|
||||
|
||||
|
@ -102,6 +111,10 @@ async function loadRepo() {
|
|||
|
||||
await repoStore.loadRepo(repositoryId.value);
|
||||
await pipelineStore.loadRepoPipelines(repositoryId.value);
|
||||
|
||||
if (repo.value) {
|
||||
forge.value = (await forgeStore.getForge(repo.value?.forge_id)).value;
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
|
|
Loading…
Reference in a new issue