mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-20 23:46:49 +00:00
Add ability to trigger manual builds (#1156)
closes #83 closes #240 Co-authored-by: Anbraten <anton@ju60.de> Co-authored-by: qwerty287 <80460567+qwerty287@users.noreply.github.com> Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
parent
86bc751b95
commit
b4d89a1cce
33 changed files with 1096 additions and 806 deletions
|
@ -23,5 +23,6 @@ var Command = &cli.Command{
|
|||
buildQueueCmd,
|
||||
buildKillCmd,
|
||||
buildPsCmd,
|
||||
buildCreateCmd,
|
||||
},
|
||||
}
|
||||
|
|
78
cli/build/build_create.go
Normal file
78
cli/build/build_create.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
package build
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/woodpecker-ci/woodpecker/woodpecker-go/woodpecker"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
|
||||
"github.com/woodpecker-ci/woodpecker/cli/common"
|
||||
"github.com/woodpecker-ci/woodpecker/cli/internal"
|
||||
)
|
||||
|
||||
var buildCreateCmd = &cli.Command{
|
||||
Name: "create",
|
||||
Usage: "create new build",
|
||||
ArgsUsage: "<repo/name>",
|
||||
Action: buildCreate,
|
||||
Flags: append(common.GlobalFlags,
|
||||
common.FormatFlag(tmplBuildList),
|
||||
&cli.StringFlag{
|
||||
Name: "branch",
|
||||
Usage: "branch to create build from",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "var",
|
||||
Usage: "key=value",
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
func buildCreate(c *cli.Context) error {
|
||||
repo := c.Args().First()
|
||||
|
||||
owner, name, err := internal.ParseRepo(repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client, err := internal.NewClient(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
branch := c.String("branch")
|
||||
variables := make(map[string]string)
|
||||
|
||||
for _, vaz := range c.StringSlice("var") {
|
||||
sp := strings.SplitN(vaz, "=", 2)
|
||||
if len(sp) == 2 {
|
||||
variables[sp[0]] = sp[1]
|
||||
}
|
||||
}
|
||||
|
||||
options := &woodpecker.BuildOptions{
|
||||
Branch: branch,
|
||||
Variables: variables,
|
||||
}
|
||||
|
||||
build, err := client.BuildCreate(owner, name, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tmpl, err := template.New("_").Parse(c.String("format") + "\n")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := tmpl.Execute(os.Stdout, build); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -273,10 +273,10 @@ when:
|
|||
:::info
|
||||
**By default steps are filtered by following event types:**
|
||||
|
||||
`push`, `pull_request`, `tag`, `deployment`.
|
||||
`push`, `pull_request`, `tag`, `deployment`, `manual`.
|
||||
:::
|
||||
|
||||
Available events: `push`, `pull_request`, `tag`, `deployment`, `cron`
|
||||
Available events: `push`, `pull_request`, `tag`, `deployment`, `cron`, `manual`
|
||||
|
||||
Execute a step if the build event is a `tag`:
|
||||
|
||||
|
|
|
@ -198,6 +198,30 @@ State: {{ .State }}
|
|||
|
||||
**--token, -t**="": server auth token
|
||||
|
||||
### create
|
||||
|
||||
create new build
|
||||
|
||||
**--branch**="": branch to create build from
|
||||
|
||||
**--format**="": format output (default: [33mBuild #{{ .Number }} [0m
|
||||
Status: {{ .Status }}
|
||||
Event: {{ .Event }}
|
||||
Commit: {{ .Commit }}
|
||||
Branch: {{ .Branch }}
|
||||
Ref: {{ .Ref }}
|
||||
Author: {{ .Author }} {{ if .Email }}<{{.Email}}>{{ end }}
|
||||
Message: {{ .Message }}
|
||||
)
|
||||
|
||||
**--log-level**="": set logging level (default: info)
|
||||
|
||||
**--server, -s**="": server address
|
||||
|
||||
**--token, -t**="": server auth token
|
||||
|
||||
**--var**="": key=value
|
||||
|
||||
## log
|
||||
|
||||
manage logs
|
||||
|
|
2
go.mod
2
go.mod
|
@ -3,7 +3,7 @@ module github.com/woodpecker-ci/woodpecker
|
|||
go 1.18
|
||||
|
||||
require (
|
||||
code.gitea.io/sdk/gitea v0.15.1-0.20220720025709-de34275bb64e
|
||||
code.gitea.io/sdk/gitea v0.15.1-0.20220831004139-a0127ed0e7fe
|
||||
codeberg.org/6543/go-yaml2json v0.2.1
|
||||
github.com/bmatcuk/doublestar/v4 v4.2.0
|
||||
github.com/caddyserver/certmagic v0.17.1-0.20220901172127-2e22c6fa8c47
|
||||
|
|
4
go.sum
4
go.sum
|
@ -54,8 +54,8 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
|
|||
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
|
||||
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
|
||||
cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y=
|
||||
code.gitea.io/sdk/gitea v0.15.1-0.20220720025709-de34275bb64e h1:xayGBU2DwsrA5ZyqKNpXB91w3BfnkNcLDWZ7Ynn/w+g=
|
||||
code.gitea.io/sdk/gitea v0.15.1-0.20220720025709-de34275bb64e/go.mod h1:aRmrQC3CAHdJAU1LQt0C9zqzqI8tUB/5oQtNE746aYE=
|
||||
code.gitea.io/sdk/gitea v0.15.1-0.20220831004139-a0127ed0e7fe h1:PeLyxnUZE85QuJtBZ4P8qCQcgWG5Ked67mlNgr0WkCQ=
|
||||
code.gitea.io/sdk/gitea v0.15.1-0.20220831004139-a0127ed0e7fe/go.mod h1:aRmrQC3CAHdJAU1LQt0C9zqzqI8tUB/5oQtNE746aYE=
|
||||
codeberg.org/6543/go-yaml2json v0.2.1 h1:S0dxlzRRpYnSLODxpbqaUfmJYZZg0Wcpf8bI9YzyOXo=
|
||||
codeberg.org/6543/go-yaml2json v0.2.1/go.mod h1:mz61q14LWF4ZABrgMEDMmk3t9dPi6zgR1uBh2VKV2RQ=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
|
|
|
@ -29,6 +29,7 @@ const (
|
|||
EventTag = "tag"
|
||||
EventDeploy = "deployment"
|
||||
EventCron = "cron"
|
||||
EventManual = "manual"
|
||||
)
|
||||
|
||||
type (
|
||||
|
|
|
@ -166,6 +166,7 @@ func (c *Constraint) SetDefaultEventFilter() {
|
|||
frontend.EventPull,
|
||||
frontend.EventTag,
|
||||
frontend.EventDeploy,
|
||||
frontend.EventManual,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,15 @@ package api
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/woodpecker-ci/woodpecker/server"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
|
@ -34,6 +37,53 @@ import (
|
|||
"github.com/woodpecker-ci/woodpecker/server/store"
|
||||
)
|
||||
|
||||
func CreateBuild(c *gin.Context) {
|
||||
_store := store.FromContext(c)
|
||||
repo := session.Repo(c)
|
||||
|
||||
var p model.BuildOptions
|
||||
|
||||
err := json.NewDecoder(c.Request.Body).Decode(&p)
|
||||
if err != nil {
|
||||
_ = c.AbortWithError(http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
user := session.User(c)
|
||||
|
||||
lastCommit, _ := server.Config.Services.Remote.BranchHead(c, user, repo, p.Branch)
|
||||
|
||||
tmpBuild := createTmpBuild(model.EventManual, lastCommit, repo, user, &p)
|
||||
|
||||
build, err := pipeline.Create(c, _store, repo, tmpBuild)
|
||||
if err != nil {
|
||||
handlePipelineErr(c, err)
|
||||
} else {
|
||||
c.JSON(http.StatusOK, build)
|
||||
}
|
||||
}
|
||||
|
||||
func createTmpBuild(event model.WebhookEvent, commitSHA string, repo *model.Repo, user *model.User, opts *model.BuildOptions) *model.Build {
|
||||
return &model.Build{
|
||||
Event: event,
|
||||
Commit: commitSHA,
|
||||
Branch: opts.Branch,
|
||||
Timestamp: time.Now().UTC().Unix(),
|
||||
|
||||
Avatar: user.Avatar,
|
||||
Message: "MANUAL BUILD @ " + opts.Branch,
|
||||
|
||||
Ref: opts.Branch,
|
||||
AdditionalVariables: opts.Variables,
|
||||
|
||||
Author: user.Login,
|
||||
Email: user.Email,
|
||||
|
||||
// TODO: Generate proper link to commit
|
||||
Link: repo.Link,
|
||||
}
|
||||
}
|
||||
|
||||
func GetBuilds(c *gin.Context) {
|
||||
repo := session.Repo(c)
|
||||
page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
|
|
|
@ -17,40 +17,41 @@ package model
|
|||
|
||||
// swagger:model build
|
||||
type Build struct {
|
||||
ID int64 `json:"id" xorm:"pk autoincr 'build_id'"`
|
||||
RepoID int64 `json:"-" xorm:"UNIQUE(s) INDEX 'build_repo_id'"`
|
||||
Number int64 `json:"number" xorm:"UNIQUE(s) 'build_number'"`
|
||||
Author string `json:"author" xorm:"INDEX 'build_author'"`
|
||||
ConfigID int64 `json:"-" xorm:"build_config_id"`
|
||||
Parent int64 `json:"parent" xorm:"build_parent"`
|
||||
Event WebhookEvent `json:"event" xorm:"build_event"`
|
||||
Status StatusValue `json:"status" xorm:"INDEX 'build_status'"`
|
||||
Error string `json:"error" xorm:"build_error"`
|
||||
Enqueued int64 `json:"enqueued_at" xorm:"build_enqueued"`
|
||||
Created int64 `json:"created_at" xorm:"build_created"`
|
||||
Updated int64 `json:"updated_at" xorm:"updated NOT NULL DEFAULT 0 'updated'"`
|
||||
Started int64 `json:"started_at" xorm:"build_started"`
|
||||
Finished int64 `json:"finished_at" xorm:"build_finished"`
|
||||
Deploy string `json:"deploy_to" xorm:"build_deploy"`
|
||||
Commit string `json:"commit" xorm:"build_commit"`
|
||||
Branch string `json:"branch" xorm:"build_branch"`
|
||||
Ref string `json:"ref" xorm:"build_ref"`
|
||||
Refspec string `json:"refspec" xorm:"build_refspec"`
|
||||
Remote string `json:"remote" xorm:"build_remote"`
|
||||
Title string `json:"title" xorm:"build_title"`
|
||||
Message string `json:"message" xorm:"build_message"`
|
||||
Timestamp int64 `json:"timestamp" xorm:"build_timestamp"`
|
||||
Sender string `json:"sender" xorm:"build_sender"` // uses reported user for webhooks and name of cron for cron pipelines
|
||||
Avatar string `json:"author_avatar" xorm:"build_avatar"`
|
||||
Email string `json:"author_email" xorm:"build_email"`
|
||||
Link string `json:"link_url" xorm:"build_link"`
|
||||
Signed bool `json:"signed" xorm:"build_signed"` // deprecate
|
||||
Verified bool `json:"verified" xorm:"build_verified"` // deprecate
|
||||
Reviewer string `json:"reviewed_by" xorm:"build_reviewer"`
|
||||
Reviewed int64 `json:"reviewed_at" xorm:"build_reviewed"`
|
||||
Procs []*Proc `json:"procs,omitempty" xorm:"-"`
|
||||
Files []*File `json:"files,omitempty" xorm:"-"`
|
||||
ChangedFiles []string `json:"changed_files,omitempty" xorm:"json 'changed_files'"`
|
||||
ID int64 `json:"id" xorm:"pk autoincr 'build_id'"`
|
||||
RepoID int64 `json:"-" xorm:"UNIQUE(s) INDEX 'build_repo_id'"`
|
||||
Number int64 `json:"number" xorm:"UNIQUE(s) 'build_number'"`
|
||||
Author string `json:"author" xorm:"INDEX 'build_author'"`
|
||||
ConfigID int64 `json:"-" xorm:"build_config_id"`
|
||||
Parent int64 `json:"parent" xorm:"build_parent"`
|
||||
Event WebhookEvent `json:"event" xorm:"build_event"`
|
||||
Status StatusValue `json:"status" xorm:"INDEX 'build_status'"`
|
||||
Error string `json:"error" xorm:"build_error"`
|
||||
Enqueued int64 `json:"enqueued_at" xorm:"build_enqueued"`
|
||||
Created int64 `json:"created_at" xorm:"build_created"`
|
||||
Updated int64 `json:"updated_at" xorm:"updated NOT NULL DEFAULT 0 'updated'"`
|
||||
Started int64 `json:"started_at" xorm:"build_started"`
|
||||
Finished int64 `json:"finished_at" xorm:"build_finished"`
|
||||
Deploy string `json:"deploy_to" xorm:"build_deploy"`
|
||||
Commit string `json:"commit" xorm:"build_commit"`
|
||||
Branch string `json:"branch" xorm:"build_branch"`
|
||||
Ref string `json:"ref" xorm:"build_ref"`
|
||||
Refspec string `json:"refspec" xorm:"build_refspec"`
|
||||
Remote string `json:"remote" xorm:"build_remote"`
|
||||
Title string `json:"title" xorm:"build_title"`
|
||||
Message string `json:"message" xorm:"build_message"`
|
||||
Timestamp int64 `json:"timestamp" xorm:"build_timestamp"`
|
||||
Sender string `json:"sender" xorm:"build_sender"` // uses reported user for webhooks and name of cron for cron pipelines
|
||||
Avatar string `json:"author_avatar" xorm:"build_avatar"`
|
||||
Email string `json:"author_email" xorm:"build_email"`
|
||||
Link string `json:"link_url" xorm:"build_link"`
|
||||
Signed bool `json:"signed" xorm:"build_signed"` // deprecate
|
||||
Verified bool `json:"verified" xorm:"build_verified"` // deprecate
|
||||
Reviewer string `json:"reviewed_by" xorm:"build_reviewer"`
|
||||
Reviewed int64 `json:"reviewed_at" xorm:"build_reviewed"`
|
||||
Procs []*Proc `json:"procs,omitempty" xorm:"-"`
|
||||
Files []*File `json:"files,omitempty" xorm:"-"`
|
||||
ChangedFiles []string `json:"changed_files,omitempty" xorm:"json 'changed_files'"`
|
||||
AdditionalVariables map[string]string `json:"variables,omitempty" xorm:"json 'additional_variables'"`
|
||||
}
|
||||
|
||||
// TableName return database table name for xorm
|
||||
|
@ -61,3 +62,8 @@ func (Build) TableName() string {
|
|||
type UpdateBuildStore interface {
|
||||
UpdateBuild(*Build) error
|
||||
}
|
||||
|
||||
type BuildOptions struct {
|
||||
Branch string `json:"branch"`
|
||||
Variables map[string]string `json:"variables"`
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ const (
|
|||
EventTag WebhookEvent = "tag"
|
||||
EventDeploy WebhookEvent = "deployment"
|
||||
EventCron WebhookEvent = "cron"
|
||||
EventManual WebhookEvent = "manual"
|
||||
)
|
||||
|
||||
func ValidateWebhookEvent(s WebhookEvent) bool {
|
||||
|
|
|
@ -60,6 +60,10 @@ func createBuildItems(ctx context.Context, store store.Store, build *model.Build
|
|||
}
|
||||
}
|
||||
|
||||
for k, v := range build.AdditionalVariables {
|
||||
envs[k] = v
|
||||
}
|
||||
|
||||
b := shared.ProcBuilder{
|
||||
Repo: repo,
|
||||
Curr: build,
|
||||
|
|
|
@ -76,6 +76,7 @@ func apiRoutes(e *gin.Engine) {
|
|||
repo.GET("/branches", api.GetRepoBranches)
|
||||
|
||||
repo.GET("/builds", api.GetBuilds)
|
||||
repo.POST("/builds", session.MustPush, api.CreateBuild)
|
||||
repo.GET("/builds/:number", api.GetBuild)
|
||||
repo.GET("/builds/:number/config", api.GetBuildConfig)
|
||||
|
||||
|
|
4
web/components.d.ts
vendored
4
web/components.d.ts
vendored
|
@ -46,10 +46,12 @@ declare module '@vue/runtime-core' {
|
|||
IIcRoundLightMode: typeof import('~icons/ic/round-light-mode')['default']
|
||||
IIcSharpTimelapse: typeof import('~icons/ic/sharp-timelapse')['default']
|
||||
IIcTwotoneAdd: typeof import('~icons/ic/twotone-add')['default']
|
||||
ILaTimes: typeof import('~icons/la/times')['default']
|
||||
IMdiBitbucket: typeof import('~icons/mdi/bitbucket')['default']
|
||||
IMdiChevronRight: typeof import('~icons/mdi/chevron-right')['default']
|
||||
IMdiClockTimeEightOutline: typeof import('~icons/mdi/clock-time-eight-outline')['default']
|
||||
IMdiFormatListBulleted: typeof import('~icons/mdi/format-list-bulleted')['default']
|
||||
IMdiGestureTap: typeof import('~icons/mdi/gesture-tap')['default']
|
||||
IMdiGithub: typeof import('~icons/mdi/github')['default']
|
||||
IMdiLoading: typeof import('~icons/mdi/loading')['default']
|
||||
IMdiSourceBranch: typeof import('~icons/mdi/source-branch')['default']
|
||||
|
@ -70,10 +72,12 @@ declare module '@vue/runtime-core' {
|
|||
ITeenyiconsGitSolid: typeof import('~icons/teenyicons/git-solid')['default']
|
||||
IVaadinQuestionCircleO: typeof import('~icons/vaadin/question-circle-o')['default']
|
||||
ListItem: typeof import('./src/components/atomic/ListItem.vue')['default']
|
||||
ManualPipelinePopup: typeof import('./src/components/layout/popups/ManualPipelinePopup.vue')['default']
|
||||
Navbar: typeof import('./src/components/layout/header/Navbar.vue')['default']
|
||||
NumberField: typeof import('./src/components/form/NumberField.vue')['default']
|
||||
OrgSecretsTab: typeof import('./src/components/org/settings/OrgSecretsTab.vue')['default']
|
||||
Panel: typeof import('./src/components/layout/Panel.vue')['default']
|
||||
Popup: typeof import('./src/components/layout/Popup.vue')['default']
|
||||
RadioField: typeof import('./src/components/form/RadioField.vue')['default']
|
||||
RegistriesTab: typeof import('./src/components/repo/settings/RegistriesTab.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
|
|
|
@ -17,56 +17,56 @@
|
|||
"test": "echo 'No tests configured' && exit 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@intlify/vite-plugin-vue-i18n": "^6.0.0",
|
||||
"@kyvg/vue3-notification": "^2.3.6",
|
||||
"@intlify/vite-plugin-vue-i18n": "^6.0.1",
|
||||
"@kyvg/vue3-notification": "^2.4.1",
|
||||
"@meforma/vue-toaster": "^1.3.0",
|
||||
"@vueuse/core": "^9.1.1",
|
||||
"@vueuse/core": "^9.2.0",
|
||||
"ansi_up": "^5.1.0",
|
||||
"dayjs": "^1.11.4",
|
||||
"floating-vue": "^2.0.0-beta.19",
|
||||
"dayjs": "^1.11.5",
|
||||
"floating-vue": "^2.0.0-beta.20",
|
||||
"fuse.js": "^6.6.2",
|
||||
"humanize-duration": "^3.27.2",
|
||||
"humanize-duration": "^3.27.3",
|
||||
"javascript-time-ago": "^2.5.7",
|
||||
"lodash": "^4.17.21",
|
||||
"node-emoji": "^1.11.0",
|
||||
"pinia": "^2.0.17",
|
||||
"prismjs": "^1.28.0",
|
||||
"vue": "^3.2.37",
|
||||
"pinia": "^2.0.22",
|
||||
"prismjs": "^1.29.0",
|
||||
"vue": "^3.2.39",
|
||||
"vue-i18n": "^9.2.2",
|
||||
"vue-router": "^4.1.3"
|
||||
"vue-router": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify/json": "^2.1.88",
|
||||
"@iconify/json": "^2.1.106",
|
||||
"@types/humanize-duration": "^3.27.1",
|
||||
"@types/javascript-time-ago": "^2.0.3",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/lodash": "^4.14.185",
|
||||
"@types/node": "^18.7.17",
|
||||
"@types/node-emoji": "^1.8.1",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||
"@typescript-eslint/parser": "^5.33.0",
|
||||
"@vitejs/plugin-vue": "^3.0.1",
|
||||
"@vue/compiler-sfc": "^3.2.37",
|
||||
"eslint": "^8.21.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.37.0",
|
||||
"@typescript-eslint/parser": "^5.37.0",
|
||||
"@vitejs/plugin-vue": "^3.1.0",
|
||||
"@vue/compiler-sfc": "^3.2.39",
|
||||
"eslint": "^8.23.1",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-prettier": "^4.2.1",
|
||||
"eslint-plugin-promise": "^6.0.0",
|
||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||
"eslint-plugin-vue": "^9.3.0",
|
||||
"eslint-plugin-promise": "^6.0.1",
|
||||
"eslint-plugin-simple-import-sort": "^8.0.0",
|
||||
"eslint-plugin-vue": "^9.4.0",
|
||||
"eslint-plugin-vue-scoped-css": "^2.2.0",
|
||||
"prettier": "^2.7.1",
|
||||
"typescript": "4.4.4",
|
||||
"unplugin-icons": "^0.14.8",
|
||||
"unplugin-vue-components": "^0.22.3",
|
||||
"vite": "^3.0.4",
|
||||
"typescript": "4.8.3",
|
||||
"unplugin-icons": "^0.14.9",
|
||||
"unplugin-vue-components": "^0.22.7",
|
||||
"vite": "^3.1.0",
|
||||
"vite-plugin-prismjs": "^0.0.8",
|
||||
"vite-plugin-windicss": "^1.8.7",
|
||||
"vite-svg-loader": "^3.4.0",
|
||||
"vue-eslint-parser": "^9.0.3",
|
||||
"vue-tsc": "^0.39.5",
|
||||
"vite-plugin-windicss": "^1.8.8",
|
||||
"vite-svg-loader": "^3.6.0",
|
||||
"vue-eslint-parser": "^9.1.0",
|
||||
"vue-tsc": "^0.40.13",
|
||||
"windicss": "^3.5.6"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,18 @@
|
|||
"not_started": "not started yet"
|
||||
},
|
||||
"repo": {
|
||||
"manual_pipeline": {
|
||||
"title": "Trigger a manual pipeline run",
|
||||
"trigger": "Run pipeline",
|
||||
"select_branch": "Select branch",
|
||||
"variables": {
|
||||
"add": "Add variable",
|
||||
"title": "Additional pipeline variables",
|
||||
"desc": "Specify additional variables to use in your pipeline. Variables with the same name will be overwritten.",
|
||||
"name": "Variable name",
|
||||
"value": "Variable value"
|
||||
}
|
||||
},
|
||||
"activity": "Activity",
|
||||
"branches": "Branches",
|
||||
"add": "Add repository",
|
||||
|
@ -207,7 +219,8 @@
|
|||
"tag": "Tag",
|
||||
"pr": "Pull Request",
|
||||
"deploy": "Deploy",
|
||||
"cron": "Cron"
|
||||
"cron": "Cron",
|
||||
"manual": "Manual"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<i-mdi-clock-time-eight-outline v-else-if="name === 'since'" class="h-6 w-6" />
|
||||
<i-mdi-source-branch v-else-if="name === 'push'" class="h-6 w-6" />
|
||||
<i-mdi-source-pull v-else-if="name === 'pull_request'" class="h-6 w-6" />
|
||||
<i-mdi-gesture-tap v-else-if="name === 'manual-pipeline'" class="h-6 w-6" />
|
||||
<i-mdi-tag-outline v-else-if="name === 'tag'" class="h-6 w-6" />
|
||||
<i-clarity-deploy-line v-else-if="name === 'deployment'" class="h-6 w-6" />
|
||||
<i-mdisource-commit v-else-if="name === 'commit'" class="h-6 w-6" />
|
||||
|
@ -51,6 +52,7 @@ export type IconNames =
|
|||
| 'since'
|
||||
| 'push'
|
||||
| 'pull_request'
|
||||
| 'manual-pipeline'
|
||||
| 'tag'
|
||||
| 'deployment'
|
||||
| 'commit'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
class="w-full border border-gray-200 py-1 px-2 rounded-md bg-white hover:border-gray-300 dark:bg-dark-gray-700 dark:border-dark-400 dark:hover:border-dark-800"
|
||||
:class="{ 'bg-gray-200 dark:bg-gray-600': disabled }"
|
||||
>
|
||||
<input
|
||||
v-if="lines === 1"
|
||||
|
|
24
web/src/components/layout/Popup.vue
Normal file
24
web/src/components/layout/Popup.vue
Normal file
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<!-- overlay -->
|
||||
<div
|
||||
v-if="open"
|
||||
class="fixed bg-gray-900 opacity-80 left-0 top-0 right-0 bottom-0 z-500 print:hidden"
|
||||
@click="$emit('close')"
|
||||
/>
|
||||
<!-- overlay end -->
|
||||
<transition class="print:hidden fixed flex top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
||||
<div v-if="open" class="m-auto flex flex-col shadow-all z-1000 max-w-3/5 max-h-3/5 h-auto">
|
||||
<slot />
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
defineProps<{
|
||||
open: boolean;
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
(event: 'close'): void;
|
||||
}>();
|
||||
</script>
|
120
web/src/components/layout/popups/ManualPipelinePopup.vue
Normal file
120
web/src/components/layout/popups/ManualPipelinePopup.vue
Normal file
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<Popup :open="open" @close="$emit('close')">
|
||||
<Panel v-if="!loading">
|
||||
<span class="text-xl text-color">{{ $t('repo.manual_pipeline.title') }}</span>
|
||||
<InputField :label="$t('repo.manual_pipeline.select_branch')">
|
||||
<SelectField
|
||||
v-model="payload.branch"
|
||||
:options="branches"
|
||||
required
|
||||
class="dark:bg-dark-gray-700 bg-transparent text-color border-gray-200 dark:border-dark-400"
|
||||
/>
|
||||
</InputField>
|
||||
<InputField :label="$t('repo.manual_pipeline.variables.title')">
|
||||
<span class="text-sm text-color-alt mb-2">{{ $t('repo.manual_pipeline.variables.desc') }}</span>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div v-for="(value, name) in payload.variables" :key="name" class="flex gap-4">
|
||||
<TextField :model-value="name" disabled />
|
||||
<TextField :model-value="value" disabled />
|
||||
<div class="w-34 flex-shrink-0">
|
||||
<Button type="submit" class="ml-auto" @click="deleteVar(name)">
|
||||
<i-la-times />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<form class="flex gap-4" @submit.prevent="addPipelineVariable">
|
||||
<TextField
|
||||
v-model="newPipelineVariable.name"
|
||||
:placeholder="$t('repo.manual_pipeline.variables.name')"
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
v-model="newPipelineVariable.value"
|
||||
:placeholder="$t('repo.manual_pipeline.variables.value')"
|
||||
required
|
||||
/>
|
||||
<Button
|
||||
class="w-34 flex-shrink-0"
|
||||
start-icon="plus"
|
||||
type="submit"
|
||||
:text="$t('repo.manual_pipeline.variables.add')"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</InputField>
|
||||
<Button type="submit" :text="$t('repo.manual_pipeline.trigger')" @click="triggerManualPipeline" />
|
||||
</Panel>
|
||||
</Popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import InputField from '~/components/form/InputField.vue';
|
||||
import SelectField from '~/components/form/SelectField.vue';
|
||||
import TextField from '~/components/form/TextField.vue';
|
||||
import Panel from '~/components/layout/Panel.vue';
|
||||
import Popup from '~/components/layout/Popup.vue';
|
||||
import useApiClient from '~/compositions/useApiClient';
|
||||
import { inject } from '~/compositions/useInjectProvide';
|
||||
|
||||
defineProps<{
|
||||
open: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'close'): void;
|
||||
}>();
|
||||
|
||||
const apiClient = useApiClient();
|
||||
|
||||
const repo = inject('repo');
|
||||
|
||||
const router = useRouter();
|
||||
const branches = ref<{ text: string; value: string }[]>([]);
|
||||
const payload = ref<{ branch: string; variables: Record<string, string> }>({
|
||||
branch: 'main',
|
||||
variables: {},
|
||||
});
|
||||
const newPipelineVariable = ref<{ name: string; value: string }>({ name: '', value: '' });
|
||||
|
||||
const loading = ref(true);
|
||||
onMounted(async () => {
|
||||
const data = await apiClient.getRepoBranches(repo.value.owner, repo.value.name);
|
||||
branches.value = data.map((e) => ({
|
||||
text: e,
|
||||
value: e,
|
||||
}));
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
function addPipelineVariable() {
|
||||
if (!newPipelineVariable.value.name || !newPipelineVariable.value.value) {
|
||||
return;
|
||||
}
|
||||
payload.value.variables[newPipelineVariable.value.name] = newPipelineVariable.value.value;
|
||||
newPipelineVariable.value.name = '';
|
||||
newPipelineVariable.value.value = '';
|
||||
}
|
||||
|
||||
function deleteVar(key: string) {
|
||||
delete payload.value.variables[key];
|
||||
}
|
||||
|
||||
async function triggerManualPipeline() {
|
||||
loading.value = true;
|
||||
const build = await apiClient.createBuild(repo.value.owner, repo.value.name, payload.value);
|
||||
|
||||
emit('close');
|
||||
|
||||
await router.push({
|
||||
name: 'repo-build',
|
||||
params: {
|
||||
buildId: build.number,
|
||||
},
|
||||
});
|
||||
|
||||
loading.value = false;
|
||||
}
|
||||
</script>
|
|
@ -35,6 +35,7 @@
|
|||
<Icon v-else-if="build.event === 'deployment'" name="deployment" />
|
||||
<Icon v-else-if="build.event === 'tag'" name="tag" />
|
||||
<Icon v-else-if="build.event === 'cron'" name="push" />
|
||||
<Icon v-else-if="build.event === 'manual'" name="manual-pipeline" />
|
||||
<Icon v-else name="push" />
|
||||
<span class="truncate">{{ prettyRef }}</span>
|
||||
</div>
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
<span>{{ build.author }}</span>
|
||||
</div>
|
||||
<div class="flex space-x-1 items-center min-w-0">
|
||||
<Icon v-if="build.event === 'manual'" name="manual-pipeline" />
|
||||
<Icon v-if="build.event === 'push'" name="push" />
|
||||
<Icon v-if="build.event === 'deployment'" name="deployment" />
|
||||
<Icon v-else-if="build.event === 'tag'" name="tag" />
|
||||
|
@ -45,7 +46,7 @@
|
|||
<div class="md:absolute top-0 left-0 w-full">
|
||||
<div v-for="proc in build.procs" :key="proc.id">
|
||||
<div class="p-4 pb-1 flex flex-wrap items-center justify-between">
|
||||
<div v-if="build.procs.length > 1" class="flex items-center">
|
||||
<div v-if="build.procs && build.procs.length > 1" class="flex items-center">
|
||||
<span class="ml-2">{{ proc.name }}</span>
|
||||
</div>
|
||||
<div v-if="proc.environ" class="text-xs">
|
||||
|
|
|
@ -31,8 +31,8 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { computed, defineComponent, PropType, toRef } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { computed, toRef } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
|
@ -42,92 +42,58 @@ import InputField from '~/components/form/InputField.vue';
|
|||
import TextField from '~/components/form/TextField.vue';
|
||||
import { Secret, WebhookEvents } from '~/lib/api/types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SecretEdit',
|
||||
const props = defineProps<{
|
||||
modelValue: Partial<Secret>;
|
||||
isSaving: boolean;
|
||||
i18nPrefix: string;
|
||||
}>();
|
||||
|
||||
components: {
|
||||
Button,
|
||||
InputField,
|
||||
TextField,
|
||||
CheckboxesField,
|
||||
},
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:modelValue', value: Partial<Secret> | undefined): void;
|
||||
(event: 'save', value: Partial<Secret>): void;
|
||||
}>();
|
||||
|
||||
props: {
|
||||
// used by toRef
|
||||
// eslint-disable-next-line vue/no-unused-properties
|
||||
modelValue: {
|
||||
type: Object as PropType<Partial<Secret>>,
|
||||
default: undefined,
|
||||
},
|
||||
const i18n = useI18n();
|
||||
|
||||
isSaving: {
|
||||
type: Boolean,
|
||||
},
|
||||
|
||||
i18nPrefix: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
emits: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
'update:modelValue': (_value: Partial<Secret> | undefined): boolean => true,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
save: (_value: Partial<Secret>): boolean => true,
|
||||
},
|
||||
|
||||
setup: (props, ctx) => {
|
||||
const i18n = useI18n();
|
||||
|
||||
const modelValue = toRef(props, 'modelValue');
|
||||
const innerValue = computed({
|
||||
get: () => modelValue.value,
|
||||
set: (value) => {
|
||||
ctx.emit('update:modelValue', value);
|
||||
},
|
||||
});
|
||||
const images = computed<string>({
|
||||
get() {
|
||||
return innerValue.value?.image?.join(',') || '';
|
||||
},
|
||||
set(value) {
|
||||
if (innerValue.value) {
|
||||
innerValue.value.image = value
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s !== '');
|
||||
}
|
||||
},
|
||||
});
|
||||
const isEditingSecret = computed(() => !!innerValue.value?.id);
|
||||
|
||||
const secretEventsOptions: CheckboxOption[] = [
|
||||
{ value: WebhookEvents.Push, text: i18n.t('repo.build.event.push') },
|
||||
{ value: WebhookEvents.Tag, text: i18n.t('repo.build.event.tag') },
|
||||
{
|
||||
value: WebhookEvents.PullRequest,
|
||||
text: i18n.t('repo.build.event.pr'),
|
||||
description: i18n.t('repo.settings.secrets.events.pr_warning'),
|
||||
},
|
||||
{ value: WebhookEvents.Deploy, text: i18n.t('repo.build.event.deploy') },
|
||||
{ value: WebhookEvents.Cron, text: i18n.t('repo.build.event.cron') },
|
||||
];
|
||||
|
||||
function save() {
|
||||
if (!innerValue.value) {
|
||||
return;
|
||||
}
|
||||
ctx.emit('save', innerValue.value);
|
||||
}
|
||||
|
||||
return {
|
||||
innerValue,
|
||||
isEditingSecret,
|
||||
secretEventsOptions,
|
||||
images,
|
||||
save,
|
||||
};
|
||||
const modelValue = toRef(props, 'modelValue');
|
||||
const innerValue = computed({
|
||||
get: () => modelValue.value,
|
||||
set: (value) => {
|
||||
emit('update:modelValue', value);
|
||||
},
|
||||
});
|
||||
const images = computed<string>({
|
||||
get() {
|
||||
return innerValue.value?.image?.join(',') || '';
|
||||
},
|
||||
set(value) {
|
||||
if (innerValue.value) {
|
||||
innerValue.value.image = value
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s !== '');
|
||||
}
|
||||
},
|
||||
});
|
||||
const isEditingSecret = computed(() => !!innerValue.value?.id);
|
||||
|
||||
const secretEventsOptions: CheckboxOption[] = [
|
||||
{ value: WebhookEvents.Push, text: i18n.t('repo.build.event.push') },
|
||||
{ value: WebhookEvents.Tag, text: i18n.t('repo.build.event.tag') },
|
||||
{
|
||||
value: WebhookEvents.PullRequest,
|
||||
text: i18n.t('repo.build.event.pr'),
|
||||
description: i18n.t('repo.settings.secrets.events.pr_warning'),
|
||||
},
|
||||
{ value: WebhookEvents.Deploy, text: i18n.t('repo.build.event.deploy') },
|
||||
{ value: WebhookEvents.Cron, text: i18n.t('repo.build.event.cron') },
|
||||
{ value: WebhookEvents.Manual, text: i18n.t('repo.build.event.manual') },
|
||||
];
|
||||
|
||||
function save() {
|
||||
if (!innerValue.value) {
|
||||
return;
|
||||
}
|
||||
emit('save', innerValue.value);
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -24,59 +24,31 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, toRef } from 'vue';
|
||||
<script lang="ts" setup>
|
||||
import { toRef } from 'vue';
|
||||
|
||||
import IconButton from '~/components/atomic/IconButton.vue';
|
||||
import ListItem from '~/components/atomic/ListItem.vue';
|
||||
import { Secret } from '~/lib/api/types';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SecretList',
|
||||
const props = defineProps<{
|
||||
modelValue: Secret[];
|
||||
isDeleting: boolean;
|
||||
i18nPrefix: string;
|
||||
}>();
|
||||
|
||||
components: {
|
||||
ListItem,
|
||||
IconButton,
|
||||
},
|
||||
const emit = defineEmits<{
|
||||
(event: 'edit', secret: Secret): void;
|
||||
(event: 'delete', secret: Secret): void;
|
||||
}>();
|
||||
|
||||
props: {
|
||||
// used by toRef
|
||||
// eslint-disable-next-line vue/no-unused-properties
|
||||
modelValue: {
|
||||
type: Array as PropType<Secret[]>,
|
||||
required: true,
|
||||
},
|
||||
const secrets = toRef(props, 'modelValue');
|
||||
|
||||
isDeleting: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
function editSecret(secret: Secret) {
|
||||
emit('edit', secret);
|
||||
}
|
||||
|
||||
i18nPrefix: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
|
||||
emits: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
edit: (secret: Secret): boolean => true,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
delete: (secret: Secret): boolean => true,
|
||||
},
|
||||
|
||||
setup(props, ctx) {
|
||||
const secrets = toRef(props, 'modelValue');
|
||||
|
||||
function editSecret(secret: Secret) {
|
||||
ctx.emit('edit', secret);
|
||||
}
|
||||
|
||||
function deleteSecret(secret: Secret) {
|
||||
ctx.emit('delete', secret);
|
||||
}
|
||||
|
||||
return { secrets, editSecret, deleteSecret };
|
||||
},
|
||||
});
|
||||
function deleteSecret(secret: Secret) {
|
||||
emit('delete', secret);
|
||||
}
|
||||
</script>
|
||||
|
|
19
web/src/compositions/useInjectProvide.ts
Normal file
19
web/src/compositions/useInjectProvide.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { inject as vueInject, provide as vueProvide, Ref } from 'vue';
|
||||
|
||||
import { Repo } from '~/lib/api/types';
|
||||
|
||||
export type InjectKeys = {
|
||||
repo: Ref<Repo>;
|
||||
};
|
||||
|
||||
export function inject<T extends keyof InjectKeys>(key: T): InjectKeys[T] {
|
||||
const value = vueInject<InjectKeys[T]>(key);
|
||||
if (value === undefined) {
|
||||
throw new Error(`Please provide a value for ${key}`);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function provide<T extends keyof InjectKeys>(key: T, value: InjectKeys[T]): void {
|
||||
return vueProvide(key, value);
|
||||
}
|
|
@ -18,6 +18,11 @@ type RepoListOptions = {
|
|||
all?: boolean;
|
||||
flush?: boolean;
|
||||
};
|
||||
|
||||
type BuildOptions = {
|
||||
branch: string;
|
||||
variables: Record<string, string>;
|
||||
};
|
||||
export default class WoodpeckerClient extends ApiClient {
|
||||
getRepoList(opts?: RepoListOptions): Promise<Repo[]> {
|
||||
const query = encodeQueryString(opts);
|
||||
|
@ -58,6 +63,10 @@ export default class WoodpeckerClient extends ApiClient {
|
|||
return this._get(`/api/repos/${owner}/${repo}/builds?${query}`) as Promise<Build[]>;
|
||||
}
|
||||
|
||||
createBuild(owner: string, repo: string, options: BuildOptions): Promise<Build> {
|
||||
return this._post(`/api/repos/${owner}/${repo}/builds`, options) as Promise<Build>;
|
||||
}
|
||||
|
||||
getBuild(owner: string, repo: string, number: number | 'latest'): Promise<Build> {
|
||||
return this._get(`/api/repos/${owner}/${repo}/builds/${number}`) as Promise<Build>;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ export type Build = {
|
|||
|
||||
parent: number;
|
||||
|
||||
event: 'push' | 'tag' | 'pull_request' | 'deployment' | 'cron';
|
||||
event: 'push' | 'tag' | 'pull_request' | 'deployment' | 'cron' | 'manual';
|
||||
|
||||
// The current status of the build.
|
||||
status: BuildStatus;
|
||||
|
|
|
@ -4,4 +4,5 @@ export enum WebhookEvents {
|
|||
PullRequest = 'pull_request',
|
||||
Deploy = 'deployment',
|
||||
Cron = 'cron',
|
||||
Manual = 'manual',
|
||||
}
|
||||
|
|
|
@ -23,12 +23,21 @@
|
|||
</a>
|
||||
<IconButton v-if="repoPermissions.admin" class="ml-2" :to="{ name: 'repo-settings' }" icon="settings" />
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-y-2 items-center justify-between">
|
||||
<Tabs v-model="activeTab" disable-hash-mode class="mb-4">
|
||||
<Tab id="activity" :title="$t('repo.activity')" />
|
||||
<Tab id="branches" :title="$t('repo.branches')" />
|
||||
</Tabs>
|
||||
|
||||
<Tabs v-model="activeTab" disable-hash-mode class="mb-4">
|
||||
<Tab id="activity" :title="$t('repo.activity')" />
|
||||
<Tab id="branches" :title="$t('repo.branches')" />
|
||||
</Tabs>
|
||||
|
||||
<Button
|
||||
v-if="repoPermissions.push"
|
||||
type="submit"
|
||||
:text="$t('repo.manual_pipeline.trigger')"
|
||||
class="ml-auto"
|
||||
@click="showManualPipelinePopup = true"
|
||||
/>
|
||||
<ManualPipelinePopup :open="showManualPipelinePopup" @close="showManualPipelinePopup = false" />
|
||||
</div>
|
||||
<router-view />
|
||||
</FluidContainer>
|
||||
<router-view v-else-if="repo && repoPermissions" />
|
||||
|
@ -42,6 +51,7 @@ import { useRoute, useRouter } from 'vue-router';
|
|||
import Icon from '~/components/atomic/Icon.vue';
|
||||
import IconButton from '~/components/atomic/IconButton.vue';
|
||||
import FluidContainer from '~/components/layout/FluidContainer.vue';
|
||||
import ManualPipelinePopup from '~/components/layout/popups/ManualPipelinePopup.vue';
|
||||
import Tab from '~/components/tabs/Tab.vue';
|
||||
import Tabs from '~/components/tabs/Tabs.vue';
|
||||
import useApiClient from '~/compositions/useApiClient';
|
||||
|
@ -53,15 +63,11 @@ import BuildStore from '~/store/builds';
|
|||
import RepoStore from '~/store/repos';
|
||||
|
||||
const props = defineProps({
|
||||
// used by toRef
|
||||
// eslint-disable-next-line vue/no-unused-properties
|
||||
repoOwner: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
// used by toRef
|
||||
// eslint-disable-next-line vue/no-unused-properties
|
||||
repoName: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -87,6 +93,8 @@ provide('repo', repo);
|
|||
provide('repo-permissions', repoPermissions);
|
||||
provide('builds', builds);
|
||||
|
||||
const showManualPipelinePopup = ref(false);
|
||||
|
||||
async function loadRepo() {
|
||||
repoPermissions.value = await apiClient.getRepoPermissions(repoOwner.value, repoName.value);
|
||||
if (!repoPermissions.value.pull) {
|
||||
|
|
1156
web/yarn.lock
1156
web/yarn.lock
File diff suppressed because it is too large
Load diff
|
@ -215,6 +215,13 @@ func (c *client) BuildList(owner, name string) ([]*Build, error) {
|
|||
return out, err
|
||||
}
|
||||
|
||||
func (c *client) BuildCreate(owner, name string, options *BuildOptions) (*Build, error) {
|
||||
var out *Build
|
||||
uri := fmt.Sprintf(pathBuilds, c.addr, owner, name)
|
||||
err := c.post(uri, options, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// BuildQueue returns a list of enqueued builds.
|
||||
func (c *client) BuildQueue() ([]*Activity, error) {
|
||||
var out []*Activity
|
||||
|
|
|
@ -70,6 +70,9 @@ type Client interface {
|
|||
// the specified repository.
|
||||
BuildList(string, string) ([]*Build, error)
|
||||
|
||||
// BuildCreate returns creates a build on specified branch.
|
||||
BuildCreate(string, string, *BuildOptions) (*Build, error)
|
||||
|
||||
// BuildQueue returns a list of enqueued builds.
|
||||
BuildQueue() ([]*Activity, error)
|
||||
|
||||
|
|
|
@ -175,4 +175,10 @@ type (
|
|||
Created int64 `json:"created_at"`
|
||||
Branch string `json:"branch"`
|
||||
}
|
||||
|
||||
// BuildOptions is the JSON data for forging a new build
|
||||
BuildOptions struct {
|
||||
Branch string `json:"branch"`
|
||||
Variables map[string]string `json:"variables"`
|
||||
}
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue