mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-25 11:21:02 +00:00
Merge branch 'main' into backend/docker/non-conf-windows-container-support
This commit is contained in:
commit
35c7fe9597
97 changed files with 3562 additions and 3197 deletions
1
.github/renovate.json
vendored
1
.github/renovate.json
vendored
|
@ -2,7 +2,6 @@
|
|||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["github>woodpecker-ci/renovate-config"],
|
||||
"automergeType": "pr",
|
||||
"enabledManagers": ["woodpecker"],
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
|
|
|
@ -10,7 +10,7 @@ repos:
|
|||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- repo: https://github.com/golangci/golangci-lint
|
||||
rev: v1.61.0
|
||||
rev: v1.62.0
|
||||
hooks:
|
||||
- id: golangci-lint
|
||||
- repo: https://github.com/igorshubovych/markdownlint-cli
|
||||
|
|
|
@ -41,9 +41,6 @@ variables:
|
|||
|
||||
when:
|
||||
- event: [pull_request, tag]
|
||||
- event: push
|
||||
branch:
|
||||
- renovate/*
|
||||
- event: push
|
||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||
path: *when_path
|
||||
|
|
|
@ -31,7 +31,6 @@ when:
|
|||
- <<: *docker_path
|
||||
branch:
|
||||
- ${CI_REPO_DEFAULT_BRANCH}
|
||||
- renovate/*
|
||||
- event: pull_request_closed
|
||||
path: *when_path
|
||||
- event: manual
|
||||
|
|
|
@ -3,7 +3,6 @@ when:
|
|||
- event: push
|
||||
branch:
|
||||
- ${CI_REPO_DEFAULT_BRANCH}
|
||||
- renovate/*
|
||||
|
||||
variables:
|
||||
- &trivy_plugin docker.io/woodpeckerci/plugin-trivy:1.2.0
|
||||
|
|
|
@ -9,8 +9,6 @@ steps:
|
|||
depends_on: []
|
||||
when:
|
||||
- event: pull_request
|
||||
- event: push
|
||||
branch: renovate/*
|
||||
|
||||
- name: spellcheck
|
||||
image: docker.io/node:23-alpine
|
||||
|
|
|
@ -16,8 +16,6 @@ variables:
|
|||
|
||||
when:
|
||||
- event: pull_request
|
||||
- event: push
|
||||
branch: renovate/*
|
||||
- event: push
|
||||
branch: ${CI_REPO_DEFAULT_BRANCH}
|
||||
path: *when_path
|
||||
|
|
|
@ -3,7 +3,6 @@ when:
|
|||
- event: push
|
||||
branch:
|
||||
- release/*
|
||||
- renovate/*
|
||||
|
||||
variables:
|
||||
- &node_image 'docker.io/node:23-alpine'
|
||||
|
|
2
Makefile
2
Makefile
|
@ -40,7 +40,7 @@ CGO_ENABLED ?= 1 # only used to compile server
|
|||
HAS_GO = $(shell hash go > /dev/null 2>&1 && echo "GO" || echo "NOGO" )
|
||||
ifeq ($(HAS_GO),GO)
|
||||
# renovate: datasource=docker depName=docker.io/techknowlogick/xgo
|
||||
XGO_VERSION ?= go-1.22.x
|
||||
XGO_VERSION ?= go-1.23.x
|
||||
CGO_CFLAGS ?= $(shell go env CGO_CFLAGS)
|
||||
endif
|
||||
CGO_CFLAGS ?=
|
||||
|
|
|
@ -17,9 +17,9 @@ var (
|
|||
cancelWaitForUpdate context.CancelCauseFunc
|
||||
)
|
||||
|
||||
func Before(ctx context.Context, c *cli.Command) error {
|
||||
func Before(ctx context.Context, c *cli.Command) (context.Context, error) {
|
||||
if err := setupGlobalLogger(ctx, c); err != nil {
|
||||
return err
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
go func(context.Context) {
|
||||
|
@ -50,7 +50,7 @@ func Before(ctx context.Context, c *cli.Command) error {
|
|||
}
|
||||
}(ctx)
|
||||
|
||||
return config.Load(ctx, c)
|
||||
return ctx, config.Load(ctx, c)
|
||||
}
|
||||
|
||||
func After(_ context.Context, _ *cli.Command) error {
|
||||
|
|
|
@ -24,8 +24,8 @@ import (
|
|||
"go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker"
|
||||
)
|
||||
|
||||
//nolint:mnd
|
||||
var pipelineListCmd = &cli.Command{
|
||||
func buildPipelineListCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "ls",
|
||||
Usage: "show pipeline history",
|
||||
ArgsUsage: "<repo-id|repo-full-name>",
|
||||
|
@ -46,9 +46,11 @@ var pipelineListCmd = &cli.Command{
|
|||
&cli.IntFlag{
|
||||
Name: "limit",
|
||||
Usage: "limit the list size",
|
||||
//nolint:mnd
|
||||
Value: 25,
|
||||
},
|
||||
}...),
|
||||
}
|
||||
}
|
||||
|
||||
func List(ctx context.Context, c *cli.Command) error {
|
||||
|
|
|
@ -110,7 +110,7 @@ func TestPipelineList(t *testing.T) {
|
|||
mockClient.On("PipelineList", mock.Anything).Return(tt.pipelines, tt.pipelineErr)
|
||||
mockClient.On("RepoLookup", mock.Anything).Return(&woodpecker.Repo{ID: tt.repoID}, nil)
|
||||
|
||||
command := pipelineListCmd
|
||||
command := buildPipelineListCmd()
|
||||
command.Writer = io.Discard
|
||||
command.Action = func(ctx context.Context, c *cli.Command) error {
|
||||
pipelines, err := pipelineList(ctx, c, mockClient)
|
||||
|
|
|
@ -31,7 +31,7 @@ var Command = &cli.Command{
|
|||
Name: "pipeline",
|
||||
Usage: "manage pipelines",
|
||||
Commands: []*cli.Command{
|
||||
pipelineListCmd,
|
||||
buildPipelineListCmd(),
|
||||
pipelineLastCmd,
|
||||
pipelineLogsCmd,
|
||||
pipelineInfoCmd,
|
||||
|
|
|
@ -65,6 +65,7 @@ Visibility: {{ .Visibility }}
|
|||
Private: {{ .IsSCMPrivate }}
|
||||
Trusted: {{ .IsTrusted }}
|
||||
Gated: {{ .IsGated }}
|
||||
Require approval for: {{ .RequireApproval }}
|
||||
Clone url: {{ .Clone }}
|
||||
Allow pull-requests: {{ .AllowPullRequests }}
|
||||
`
|
||||
|
|
|
@ -39,6 +39,10 @@ var repoUpdateCmd = &cli.Command{
|
|||
Name: "gated",
|
||||
Usage: "repository is gated",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "require-approval",
|
||||
Usage: "repository requires approval for",
|
||||
},
|
||||
&cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Usage: "repository timeout",
|
||||
|
@ -79,6 +83,7 @@ func repoUpdate(ctx context.Context, c *cli.Command) error {
|
|||
timeout = c.Duration("timeout")
|
||||
trusted = c.Bool("trusted")
|
||||
gated = c.Bool("gated")
|
||||
requireApproval = c.String("require-approval")
|
||||
pipelineCounter = int(c.Int("pipeline-counter"))
|
||||
unsafe = c.Bool("unsafe")
|
||||
)
|
||||
|
@ -87,8 +92,29 @@ func repoUpdate(ctx context.Context, c *cli.Command) error {
|
|||
if c.IsSet("trusted") {
|
||||
patch.IsTrusted = &trusted
|
||||
}
|
||||
// TODO: remove isGated in next major release
|
||||
if c.IsSet("gated") {
|
||||
patch.IsGated = &gated
|
||||
if gated {
|
||||
patch.RequireApproval = &woodpecker.RequireApprovalAllEvents
|
||||
} else {
|
||||
patch.RequireApproval = &woodpecker.RequireApprovalNone
|
||||
}
|
||||
}
|
||||
if c.IsSet("require-approval") {
|
||||
if mode := woodpecker.ApprovalMode(requireApproval); mode.Valid() {
|
||||
patch.RequireApproval = &mode
|
||||
} else {
|
||||
return fmt.Errorf("update approval mode failed: '%s' is no valid mode", mode)
|
||||
}
|
||||
|
||||
// TODO: remove isGated in next major release
|
||||
if requireApproval == string(woodpecker.RequireApprovalAllEvents) {
|
||||
trueBool := true
|
||||
patch.IsGated = &trueBool
|
||||
} else if requireApproval == string(woodpecker.RequireApprovalNone) {
|
||||
falseBool := false
|
||||
patch.IsGated = &falseBool
|
||||
}
|
||||
}
|
||||
if c.IsSet("timeout") {
|
||||
v := int64(timeout / time.Minute)
|
||||
|
|
|
@ -4905,6 +4905,9 @@ const docTemplate = `{
|
|||
"forge_url": {
|
||||
"type": "string"
|
||||
},
|
||||
"from_fork": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -5068,9 +5071,6 @@ const docTemplate = `{
|
|||
"full_name": {
|
||||
"type": "string"
|
||||
},
|
||||
"gated": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -5092,6 +5092,9 @@ const docTemplate = `{
|
|||
"private": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"require_approval": {
|
||||
"$ref": "#/definitions/model.ApprovalMode"
|
||||
},
|
||||
"scm": {
|
||||
"$ref": "#/definitions/SCMKind"
|
||||
},
|
||||
|
@ -5125,11 +5128,15 @@ const docTemplate = `{
|
|||
"type": "string"
|
||||
},
|
||||
"gated": {
|
||||
"description": "TODO: deprecated in favor of RequireApproval =\u003e Remove in next major release",
|
||||
"type": "boolean"
|
||||
},
|
||||
"netrc_only_trusted": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"require_approval": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeout": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -5621,6 +5628,27 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"model.ApprovalMode": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"forks",
|
||||
"pull_requests",
|
||||
"all_events"
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"RequireApprovalAllEvents": "require approval for all external events",
|
||||
"RequireApprovalForks": "require approval for PRs from forks (default)",
|
||||
"RequireApprovalNone": "require approval for no events",
|
||||
"RequireApprovalPullRequests": "require approval for all PRs"
|
||||
},
|
||||
"x-enum-varnames": [
|
||||
"RequireApprovalNone",
|
||||
"RequireApprovalForks",
|
||||
"RequireApprovalPullRequests",
|
||||
"RequireApprovalAllEvents"
|
||||
]
|
||||
},
|
||||
"model.ForgeType": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
|
|
|
@ -3,7 +3,7 @@ version: '3'
|
|||
|
||||
services:
|
||||
gitea-database:
|
||||
image: postgres:17.0-alpine
|
||||
image: postgres:17.1-alpine
|
||||
environment:
|
||||
POSTGRES_USER: gitea
|
||||
POSTGRES_PASSWORD: 123456
|
||||
|
|
|
@ -25,10 +25,9 @@ Only activate this option if you trust all users who have push access to your re
|
|||
Otherwise, these users will be able to steal secrets that are only available for `deploy` events.
|
||||
:::
|
||||
|
||||
## Protected
|
||||
## Require approval for
|
||||
|
||||
Every pipeline initiated by an webhook event needs to be approved by a project members with push permissions before being executed.
|
||||
The protected option can be used as an additional review process before running potentially harmful pipelines. Especially if pipelines can be executed by third-parties through pull-requests.
|
||||
To prevent malicious pipelines from extracting secrets or running harmful commands or to prevent accidental pipeline runs, you can require approval for an additional review process. Depending on the enabled option, a pipeline will be put on hold after creation and will only continue after approval. The default restrictive setting is `Approvals for forked repositories`.
|
||||
|
||||
## Trusted
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 353 KiB |
|
@ -34,7 +34,7 @@ Install make on:
|
|||
|
||||
### Install Node.js & `pnpm`
|
||||
|
||||
Install [Node.js (>=14)](https://nodejs.org/en/download/) if you want to build Woodpecker's UI or documentation.
|
||||
Install [Node.js (>=20)](https://nodejs.org/en/download/package-manager) if you want to build Woodpecker's UI or documentation.
|
||||
|
||||
For dependency installation (`node_modules`) of UI and documentation of Woodpecker the package manager pnpm is used.
|
||||
[This guide](https://pnpm.io/installation) describes the installation of `pnpm`.
|
||||
|
|
|
@ -14,9 +14,9 @@
|
|||
"write-heading-ids": "docusaurus write-heading-ids"
|
||||
},
|
||||
"dependencies": {
|
||||
"@docusaurus/core": "^3.5.2",
|
||||
"@docusaurus/plugin-content-blog": "^3.5.2",
|
||||
"@docusaurus/preset-classic": "^3.5.2",
|
||||
"@docusaurus/core": "^3.6.1",
|
||||
"@docusaurus/plugin-content-blog": "^3.6.1",
|
||||
"@docusaurus/preset-classic": "^3.6.1",
|
||||
"@easyops-cn/docusaurus-search-local": "^0.45.0",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
|
@ -26,7 +26,7 @@
|
|||
"prism-react-renderer": "^2.4.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"redocusaurus": "^2.1.2",
|
||||
"redocusaurus": "^2.2.0",
|
||||
"url-loader": "^4.1.1"
|
||||
},
|
||||
"browserslist": {
|
||||
|
@ -42,10 +42,10 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.5.2",
|
||||
"@docusaurus/tsconfig": "3.5.2",
|
||||
"@docusaurus/types": "^3.5.2",
|
||||
"@types/node": "^20.17.1",
|
||||
"@docusaurus/module-type-aliases": "^3.6.1",
|
||||
"@docusaurus/tsconfig": "3.6.1",
|
||||
"@docusaurus/types": "^3.6.1",
|
||||
"@types/node": "^22.9.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-helmet": "^6.1.11",
|
||||
"@types/react-router-dom": "^5.3.3",
|
||||
|
|
|
@ -10,17 +10,17 @@
|
|||
"style": "mkdir -p dist/theme/ && cp src/theme/style.css dist/theme/style.css"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "^3.3.2",
|
||||
"@docusaurus/theme-classic": "^3.3.2",
|
||||
"@docusaurus/types": "^3.3.2",
|
||||
"@docusaurus/module-type-aliases": "^3.6.1",
|
||||
"@docusaurus/theme-classic": "^3.6.1",
|
||||
"@docusaurus/types": "^3.6.1",
|
||||
"@tsconfig/docusaurus": "^2.0.3",
|
||||
"@types/node": "^20.12.13",
|
||||
"axios": "^1.7.2",
|
||||
"concurrently": "^9.0.0",
|
||||
"isomorphic-dompurify": "^2.11.0",
|
||||
"marked": "^14.0.0",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.4.5"
|
||||
"@types/node": "^22.9.0",
|
||||
"axios": "^1.7.7",
|
||||
"concurrently": "^9.1.0",
|
||||
"isomorphic-dompurify": "^2.16.0",
|
||||
"marked": "^15.0.0",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.6.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.2 || ^18.0.0",
|
||||
|
@ -28,6 +28,6 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"fuse.js": "^7.0.0",
|
||||
"yaml": "^2.4.2"
|
||||
"yaml": "^2.6.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -224,6 +224,11 @@
|
|||
"name": "EditorConfig Checker",
|
||||
"docs": "https://codeberg.org/woodpecker-plugins/editorconfig-checker/raw/branch/main/docs.md",
|
||||
"verified": true
|
||||
},
|
||||
{
|
||||
"name": "Microsoft Teams Notify",
|
||||
"docs": "https://raw.githubusercontent.com/GECO-IT/woodpecker-plugin-teams-notify/refs/heads/main/docs.md",
|
||||
"verified": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
2575
docs/pnpm-lock.yaml
2575
docs/pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -38,6 +38,7 @@
|
|||
addlicense
|
||||
protoc-gen-go
|
||||
protoc-gen-go-grpc
|
||||
gcc
|
||||
];
|
||||
CFLAGS = "-I${pkgs.glibc.dev}/include";
|
||||
LDFLAGS = "-L${pkgs.glibc}/lib";
|
||||
|
|
34
go.mod
34
go.mod
|
@ -1,6 +1,6 @@
|
|||
module go.woodpecker-ci.org/woodpecker/v2
|
||||
|
||||
go 1.22.0
|
||||
go 1.22.7
|
||||
|
||||
toolchain go1.23.2
|
||||
|
||||
|
@ -11,7 +11,7 @@ require (
|
|||
codeberg.org/6543/xyaml v1.1.0
|
||||
codeberg.org/mvdkleijn/forgejo-sdk/forgejo v1.2.0
|
||||
github.com/6543/logfile-open v1.2.1
|
||||
github.com/adrg/xdg v0.5.2
|
||||
github.com/adrg/xdg v0.5.3
|
||||
github.com/bmatcuk/doublestar/v4 v4.7.1
|
||||
github.com/caddyserver/certmagic v0.21.4
|
||||
github.com/cenkalti/backoff/v4 v4.3.0
|
||||
|
@ -25,7 +25,7 @@ require (
|
|||
github.com/drone/envsubst v1.0.3
|
||||
github.com/expr-lang/expr v1.16.9
|
||||
github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf
|
||||
github.com/fsnotify/fsnotify v1.7.0
|
||||
github.com/fsnotify/fsnotify v1.8.0
|
||||
github.com/gdgvda/cron v0.3.0
|
||||
github.com/getkin/kin-openapi v0.127.0
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
|
@ -55,21 +55,21 @@ require (
|
|||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.4
|
||||
github.com/urfave/cli-docs/v3 v3.0.0-alpha5
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.0.20241004184838-20ef97b2155a
|
||||
github.com/xanzy/go-gitlab v0.112.0
|
||||
github.com/urfave/cli-docs/v3 v3.0.0-alpha6
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.3
|
||||
github.com/xanzy/go-gitlab v0.113.0
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
github.com/yaronf/httpsign v0.3.1
|
||||
github.com/zalando/go-keyring v0.2.6
|
||||
go.uber.org/multierr v1.11.0
|
||||
golang.org/x/crypto v0.28.0
|
||||
golang.org/x/net v0.30.0
|
||||
golang.org/x/oauth2 v0.23.0
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/term v0.25.0
|
||||
golang.org/x/text v0.19.0
|
||||
google.golang.org/grpc v1.67.1
|
||||
google.golang.org/protobuf v1.35.1
|
||||
golang.org/x/crypto v0.29.0
|
||||
golang.org/x/net v0.31.0
|
||||
golang.org/x/oauth2 v0.24.0
|
||||
golang.org/x/sync v0.9.0
|
||||
golang.org/x/term v0.26.0
|
||||
golang.org/x/text v0.20.0
|
||||
google.golang.org/grpc v1.68.0
|
||||
google.golang.org/protobuf v1.35.2
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.31.2
|
||||
k8s.io/apimachinery v0.31.2
|
||||
|
@ -204,11 +204,11 @@ require (
|
|||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/mod v0.18.0 // indirect
|
||||
golang.org/x/sys v0.26.0 // indirect
|
||||
golang.org/x/sys v0.27.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/tools v0.22.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gotest.tools/v3 v3.4.0 // indirect
|
||||
|
|
64
go.sum
64
go.sum
|
@ -30,8 +30,8 @@ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6
|
|||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/adrg/xdg v0.5.2 h1:HNeVffMIG56GLMaoKTcTcyFhD2xS/dhyuBlKSNCM6Ug=
|
||||
github.com/adrg/xdg v0.5.2/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
|
@ -138,8 +138,8 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw
|
|||
github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf h1:NrF81UtW8gG2LBGkXFQFqlfNnvMt9WdB46sfdJY4oqc=
|
||||
github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
|
@ -521,14 +521,14 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
|||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/urfave/cli-docs/v3 v3.0.0-alpha5 h1:H1oWnR2/GN0dNm2PVylws+GxSOD6YOwW/jI5l78YfPk=
|
||||
github.com/urfave/cli-docs/v3 v3.0.0-alpha5/go.mod h1:AIqom6Q60U4tiqHp41i7+/AB2XHgi1WvQ7jOFlccmZ4=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.0.20241004184838-20ef97b2155a h1:ipFw/N7kumxX+CA9UoKXX86MNfYsfsom8YOdUC+Rsfw=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.0.20241004184838-20ef97b2155a/go.mod h1:Z1ItyMma7t6I7zHG9OpbExhHQOSkFf/96n+mAZ9MtVI=
|
||||
github.com/urfave/cli-docs/v3 v3.0.0-alpha6 h1:w/l/N0xw1rO/aHRIGXJ0lDwwYFOzilup1qGvIytP3BI=
|
||||
github.com/urfave/cli-docs/v3 v3.0.0-alpha6/go.mod h1:p7Z4lg8FSTrPB9GTaNyTrK3ygffHZcK3w0cU2VE+mzU=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.3 h1:RfQlgUHMRxDMwEEmGsrHd+mXYJpWpXlcJM8w86cpjGs=
|
||||
github.com/urfave/cli/v3 v3.0.0-alpha9.3/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xanzy/go-gitlab v0.112.0 h1:6Z0cqEooCvBMfBIHw+CgO4AKGRV8na/9781xOb0+DKw=
|
||||
github.com/xanzy/go-gitlab v0.112.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY=
|
||||
github.com/xanzy/go-gitlab v0.113.0 h1:v5O4R+YZbJGxKqa9iIZxjMyeKkMKBN8P6sZsNl+YckM=
|
||||
github.com/xanzy/go-gitlab v0.113.0/go.mod h1:wKNKh3GkYDMOsGmnfuX+ITCmDuSDWFO0G+C4AygL9RY=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
|
@ -601,8 +601,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
|||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
|
||||
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
|
@ -624,17 +624,17 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=
|
||||
golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
@ -664,14 +664,14 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
|
||||
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
||||
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
@ -679,8 +679,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
@ -705,14 +705,14 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 h1:wKguEg1hsxI2/L3hUYrpo1RVi48K+uTyzKqprwLXsb8=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142/go.mod h1:d6be+8HhtEtucleCbxpPW9PA9XwISACu8nvpPqF0BVo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 h1:e7S5W7MGGLaSu8j3YjdezkZ+m1/Nm0uRVRMEMGk26Xs=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E=
|
||||
google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
|
||||
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
|
||||
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
|
|
@ -91,6 +91,7 @@ func PostRepo(c *gin.Context) {
|
|||
repo.Update(from)
|
||||
} else {
|
||||
repo = from
|
||||
repo.RequireApproval = model.RequireApprovalForks
|
||||
repo.AllowPull = true
|
||||
repo.AllowDeploy = false
|
||||
repo.NetrcOnlyTrusted = true
|
||||
|
@ -250,8 +251,20 @@ func PatchRepo(c *gin.Context) {
|
|||
if in.AllowDeploy != nil {
|
||||
repo.AllowDeploy = *in.AllowDeploy
|
||||
}
|
||||
if in.IsGated != nil {
|
||||
repo.IsGated = *in.IsGated
|
||||
|
||||
if in.RequireApproval != nil {
|
||||
if mode := model.ApprovalMode(*in.RequireApproval); mode.Valid() {
|
||||
repo.RequireApproval = mode
|
||||
} else {
|
||||
c.String(http.StatusBadRequest, "Invalid require-approval setting")
|
||||
return
|
||||
}
|
||||
} else if in.IsGated != nil { // TODO: remove isGated in next major release
|
||||
if *in.IsGated {
|
||||
repo.RequireApproval = model.RequireApprovalAllEvents
|
||||
} else {
|
||||
repo.RequireApproval = model.RequireApprovalForks
|
||||
}
|
||||
}
|
||||
if in.Timeout != nil {
|
||||
repo.Timeout = *in.Timeout
|
||||
|
|
|
@ -183,6 +183,7 @@ func convertPullHook(from *internal.PullRequestHook) *model.Pipeline {
|
|||
Author: from.Actor.Login,
|
||||
Sender: from.Actor.Login,
|
||||
Timestamp: from.PullRequest.Updated.UTC().Unix(),
|
||||
FromFork: from.PullRequest.Source.Repo.UUID != from.PullRequest.Dest.Repo.UUID,
|
||||
}
|
||||
|
||||
if from.PullRequest.State == stateClosed {
|
||||
|
|
|
@ -123,6 +123,7 @@ func convertPullRequestEvent(ev *bb.PullRequestEvent, baseURL string) *model.Pip
|
|||
Ref: fmt.Sprintf("refs/pull-requests/%d/from", ev.PullRequest.ID),
|
||||
ForgeURL: fmt.Sprintf("%s/projects/%s/repos/%s/commits/%s", baseURL, ev.PullRequest.Source.Repository.Project.Key, ev.PullRequest.Source.Repository.Slug, ev.PullRequest.Source.Latest),
|
||||
Refspec: fmt.Sprintf("%s:%s", ev.PullRequest.Source.DisplayID, ev.PullRequest.Target.DisplayID),
|
||||
FromFork: ev.PullRequest.Source.Repository.ID != ev.PullRequest.Target.Repository.ID,
|
||||
}
|
||||
|
||||
if ev.EventKey == bb.EventKeyPullRequestMerged || ev.EventKey == bb.EventKeyPullRequestDeclined || ev.EventKey == bb.EventKeyPullRequestDeleted {
|
||||
|
|
|
@ -171,6 +171,7 @@ func pipelineFromPullRequest(hook *pullRequestHook) *model.Pipeline {
|
|||
hook.PullRequest.Base.Ref,
|
||||
),
|
||||
PullRequestLabels: convertLabels(hook.PullRequest.Labels),
|
||||
FromFork: hook.PullRequest.Head.RepoID != hook.PullRequest.Base.RepoID,
|
||||
}
|
||||
|
||||
return pipeline
|
||||
|
|
|
@ -172,6 +172,7 @@ func pipelineFromPullRequest(hook *pullRequestHook) *model.Pipeline {
|
|||
hook.PullRequest.Base.Ref,
|
||||
),
|
||||
PullRequestLabels: convertLabels(hook.PullRequest.Labels),
|
||||
FromFork: hook.PullRequest.Head.RepoID != hook.PullRequest.Base.RepoID,
|
||||
}
|
||||
|
||||
return pipeline
|
||||
|
|
|
@ -157,6 +157,8 @@ func parsePullHook(hook *github.PullRequestEvent, merge bool) (*github.PullReque
|
|||
event = model.EventPullClosed
|
||||
}
|
||||
|
||||
fromFork := hook.GetPullRequest().GetHead().GetRepo().GetID() != hook.GetPullRequest().GetBase().GetRepo().GetID()
|
||||
|
||||
pipeline := &model.Pipeline{
|
||||
Event: event,
|
||||
Commit: hook.GetPullRequest().GetHead().GetSHA(),
|
||||
|
@ -173,6 +175,7 @@ func parsePullHook(hook *github.PullRequestEvent, merge bool) (*github.PullReque
|
|||
hook.GetPullRequest().GetBase().GetRef(),
|
||||
),
|
||||
PullRequestLabels: convertLabels(hook.GetPullRequest().Labels),
|
||||
FromFork: fromFork,
|
||||
}
|
||||
if merge {
|
||||
pipeline.Ref = fmt.Sprintf(mergeRefs, hook.GetPullRequest().GetNumber())
|
||||
|
|
|
@ -138,6 +138,7 @@ func convertMergeRequestHook(hook *gitlab.MergeEvent, req *http.Request) (int, *
|
|||
pipeline.Title = obj.Title
|
||||
pipeline.ForgeURL = obj.URL
|
||||
pipeline.PullRequestLabels = convertLabels(hook.Labels)
|
||||
pipeline.FromFork = target.PathWithNamespace != source.PathWithNamespace
|
||||
|
||||
return obj.IID, repo, pipeline, nil
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ type Pipeline struct {
|
|||
AdditionalVariables map[string]string `json:"variables,omitempty" xorm:"json 'additional_variables'"`
|
||||
PullRequestLabels []string `json:"pr_labels,omitempty" xorm:"json 'pr_labels'"`
|
||||
IsPrerelease bool `json:"is_prerelease,omitempty" xorm:"is_prerelease"`
|
||||
FromFork bool `json:"from_fork,omitempty" xorm:"from_fork"`
|
||||
} // @name Pipeline
|
||||
|
||||
// TableName return database table name for xorm.
|
||||
|
|
|
@ -20,6 +20,27 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
type ApprovalMode string
|
||||
|
||||
const (
|
||||
RequireApprovalNone ApprovalMode = "none" // require approval for no events
|
||||
RequireApprovalForks ApprovalMode = "forks" // require approval for PRs from forks (default)
|
||||
RequireApprovalPullRequests ApprovalMode = "pull_requests" // require approval for all PRs
|
||||
RequireApprovalAllEvents ApprovalMode = "all_events" // require approval for all external events
|
||||
)
|
||||
|
||||
func (mode ApprovalMode) Valid() bool {
|
||||
switch mode {
|
||||
case RequireApprovalNone,
|
||||
RequireApprovalForks,
|
||||
RequireApprovalPullRequests,
|
||||
RequireApprovalAllEvents:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Repo represents a repository.
|
||||
type Repo struct {
|
||||
ID int64 `json:"id,omitempty" xorm:"pk autoincr 'id'"`
|
||||
|
@ -42,7 +63,7 @@ type Repo struct {
|
|||
Visibility RepoVisibility `json:"visibility" xorm:"varchar(10) 'visibility'"`
|
||||
IsSCMPrivate bool `json:"private" xorm:"private"`
|
||||
Trusted TrustedConfiguration `json:"trusted" xorm:"json 'trusted'"`
|
||||
IsGated bool `json:"gated" xorm:"gated"`
|
||||
RequireApproval ApprovalMode `json:"require_approval" xorm:"varchar(50) require_approval"`
|
||||
IsActive bool `json:"active" xorm:"active"`
|
||||
AllowPull bool `json:"allow_pr" xorm:"allow_pr"`
|
||||
AllowDeploy bool `json:"allow_deploy" xorm:"allow_deploy"`
|
||||
|
@ -109,7 +130,8 @@ func (r *Repo) Update(from *Repo) {
|
|||
// RepoPatch represents a repository patch object.
|
||||
type RepoPatch struct {
|
||||
Config *string `json:"config_file,omitempty"`
|
||||
IsGated *bool `json:"gated,omitempty"`
|
||||
IsGated *bool `json:"gated,omitempty"` // TODO: deprecated in favor of RequireApproval => Remove in next major release
|
||||
RequireApproval *string `json:"require_approval,omitempty"`
|
||||
Timeout *int64 `json:"timeout,omitempty"`
|
||||
Visibility *string `json:"visibility,omitempty"`
|
||||
AllowPull *bool `json:"allow_pr,omitempty"`
|
||||
|
|
|
@ -27,8 +27,7 @@ import (
|
|||
"go.woodpecker-ci.org/woodpecker/v2/server/store"
|
||||
)
|
||||
|
||||
// Approve update the status to pending for a blocked pipeline because of a gated repo
|
||||
// and start them afterward.
|
||||
// Approve update the status to pending for a blocked pipeline so it can be executed.
|
||||
func Approve(ctx context.Context, store store.Store, currentPipeline *model.Pipeline, user *model.User, repo *model.Repo) (*model.Pipeline, error) {
|
||||
if currentPipeline.Status != model.StatusBlocked {
|
||||
return nil, ErrBadRequest{Msg: fmt.Sprintf("cannot approve a pipeline with status %s", currentPipeline.Status)}
|
||||
|
|
|
@ -68,7 +68,7 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline
|
|||
// update some pipeline fields
|
||||
pipeline.RepoID = repo.ID
|
||||
pipeline.Status = model.StatusCreated
|
||||
setGatedState(repo, pipeline)
|
||||
setApprovalState(repo, pipeline)
|
||||
err = _store.CreatePipeline(pipeline)
|
||||
if err != nil {
|
||||
msg := fmt.Errorf("failed to save pipeline for %s", repo.FullName)
|
||||
|
|
|
@ -26,7 +26,7 @@ import (
|
|||
"go.woodpecker-ci.org/woodpecker/v2/server/store"
|
||||
)
|
||||
|
||||
// Decline updates the status to declined for blocked pipelines because of a gated repo.
|
||||
// Decline updates the status to declined for blocked pipelines.
|
||||
func Decline(ctx context.Context, store store.Store, pipeline *model.Pipeline, user *model.User, repo *model.Repo) (*model.Pipeline, error) {
|
||||
forge, err := server.Config.Services.Manager.ForgeFromRepo(repo)
|
||||
if err != nil {
|
||||
|
|
|
@ -16,11 +16,40 @@ package pipeline
|
|||
|
||||
import "go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
|
||||
func setGatedState(repo *model.Repo, pipeline *model.Pipeline) {
|
||||
// TODO(336): extend gated feature with an allow/block List
|
||||
if repo.IsGated &&
|
||||
// events created by woodpecker itself should run right away
|
||||
pipeline.Event != model.EventCron && pipeline.Event != model.EventManual {
|
||||
pipeline.Status = model.StatusBlocked
|
||||
func setApprovalState(repo *model.Repo, pipeline *model.Pipeline) {
|
||||
if !needsApproval(repo, pipeline) {
|
||||
return
|
||||
}
|
||||
|
||||
// set pipeline status to blocked and require approval
|
||||
pipeline.Status = model.StatusBlocked
|
||||
}
|
||||
|
||||
func needsApproval(repo *model.Repo, pipeline *model.Pipeline) bool {
|
||||
// skip events created by woodpecker itself
|
||||
if pipeline.Event == model.EventCron || pipeline.Event == model.EventManual {
|
||||
return false
|
||||
}
|
||||
|
||||
// repository allows all events without approval
|
||||
if repo.RequireApproval == model.RequireApprovalNone {
|
||||
return false
|
||||
}
|
||||
|
||||
// repository requires approval for pull requests from forks
|
||||
if pipeline.Event == model.EventPull && pipeline.FromFork {
|
||||
return true
|
||||
}
|
||||
|
||||
// repository requires approval for pull requests
|
||||
if pipeline.Event == model.EventPull && repo.RequireApproval == model.RequireApprovalPullRequests {
|
||||
return true
|
||||
}
|
||||
|
||||
// repository requires approval for all events
|
||||
if repo.RequireApproval == model.RequireApprovalAllEvents {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
|
78
server/pipeline/gated_test.go
Normal file
78
server/pipeline/gated_test.go
Normal file
|
@ -0,0 +1,78 @@
|
|||
package pipeline
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
)
|
||||
|
||||
func TestSetGatedState(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
repo *model.Repo
|
||||
pipeline *model.Pipeline
|
||||
expectBlocked bool
|
||||
}{
|
||||
{
|
||||
name: "by-pass for cron",
|
||||
repo: &model.Repo{
|
||||
RequireApproval: model.RequireApprovalAllEvents,
|
||||
},
|
||||
pipeline: &model.Pipeline{
|
||||
Event: model.EventCron,
|
||||
},
|
||||
expectBlocked: false,
|
||||
},
|
||||
{
|
||||
name: "by-pass for manual pipeline",
|
||||
repo: &model.Repo{
|
||||
RequireApproval: model.RequireApprovalAllEvents,
|
||||
},
|
||||
pipeline: &model.Pipeline{
|
||||
Event: model.EventManual,
|
||||
},
|
||||
expectBlocked: false,
|
||||
},
|
||||
{
|
||||
name: "require approval for fork PRs",
|
||||
repo: &model.Repo{
|
||||
RequireApproval: model.RequireApprovalForks,
|
||||
},
|
||||
pipeline: &model.Pipeline{
|
||||
Event: model.EventPull,
|
||||
FromFork: true,
|
||||
},
|
||||
expectBlocked: true,
|
||||
},
|
||||
{
|
||||
name: "require approval for PRs",
|
||||
repo: &model.Repo{
|
||||
RequireApproval: model.RequireApprovalPullRequests,
|
||||
},
|
||||
pipeline: &model.Pipeline{
|
||||
Event: model.EventPull,
|
||||
FromFork: false,
|
||||
},
|
||||
expectBlocked: true,
|
||||
},
|
||||
{
|
||||
name: "require approval for everything",
|
||||
repo: &model.Repo{
|
||||
RequireApproval: model.RequireApprovalAllEvents,
|
||||
},
|
||||
pipeline: &model.Pipeline{
|
||||
Event: model.EventPush,
|
||||
},
|
||||
expectBlocked: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
setApprovalState(tc.repo, tc.pipeline)
|
||||
assert.Equal(t, tc.expectBlocked, tc.pipeline.Status == model.StatusBlocked)
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@ package queue
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
|
@ -26,64 +27,85 @@ import (
|
|||
"go.woodpecker-ci.org/woodpecker/v2/server/model"
|
||||
)
|
||||
|
||||
var filterFnTrue = func(*model.Task) (bool, int) { return true, 1 }
|
||||
var (
|
||||
filterFnTrue = func(*model.Task) (bool, int) { return true, 1 }
|
||||
genDummyTask = func() *model.Task {
|
||||
return &model.Task{
|
||||
ID: "1",
|
||||
Data: []byte("{}"),
|
||||
}
|
||||
}
|
||||
waitForProcess = func() { time.Sleep(processTimeInterval + 10*time.Millisecond) }
|
||||
)
|
||||
|
||||
func TestFifo(t *testing.T) {
|
||||
want := &model.Task{ID: "1"}
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
t.Cleanup(func() { cancel(nil) })
|
||||
|
||||
q := NewMemoryQueue(ctx)
|
||||
assert.NoError(t, q.Push(ctx, want))
|
||||
dummyTask := genDummyTask()
|
||||
|
||||
assert.NoError(t, q.Push(ctx, dummyTask))
|
||||
waitForProcess()
|
||||
info := q.Info(ctx)
|
||||
assert.Len(t, info.Pending, 1, "expect task in pending queue")
|
||||
|
||||
got, err := q.Poll(ctx, 1, filterFnTrue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, want, got)
|
||||
assert.Equal(t, dummyTask, got)
|
||||
|
||||
waitForProcess()
|
||||
info = q.Info(ctx)
|
||||
assert.Len(t, info.Pending, 0, "expect task removed from pending queue")
|
||||
assert.Len(t, info.Running, 1, "expect task in running queue")
|
||||
|
||||
assert.NoError(t, q.Done(ctx, got.ID, model.StatusSuccess))
|
||||
|
||||
waitForProcess()
|
||||
info = q.Info(ctx)
|
||||
assert.Len(t, info.Pending, 0, "expect task removed from pending queue")
|
||||
assert.Len(t, info.Running, 0, "expect task removed from running queue")
|
||||
}
|
||||
|
||||
func TestFifoExpire(t *testing.T) {
|
||||
want := &model.Task{ID: "1"}
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
t.Cleanup(func() { cancel(nil) })
|
||||
|
||||
q, _ := NewMemoryQueue(ctx).(*fifo)
|
||||
assert.NotNil(t, q)
|
||||
|
||||
dummyTask := genDummyTask()
|
||||
|
||||
q.extension = 0
|
||||
assert.NoError(t, q.Push(ctx, want))
|
||||
assert.NoError(t, q.Push(ctx, dummyTask))
|
||||
waitForProcess()
|
||||
info := q.Info(ctx)
|
||||
assert.Len(t, info.Pending, 1, "expect task in pending queue")
|
||||
|
||||
got, err := q.Poll(ctx, 1, filterFnTrue)
|
||||
waitForProcess()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, want, got)
|
||||
assert.Equal(t, dummyTask, got)
|
||||
|
||||
// cancel the context to let the process func end
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond)
|
||||
cancel(nil)
|
||||
}()
|
||||
q.process()
|
||||
info = q.Info(ctx)
|
||||
assert.Len(t, info.Pending, 1, "expect task re-added to pending queue")
|
||||
}
|
||||
|
||||
func TestFifoWait(t *testing.T) {
|
||||
want := &model.Task{ID: "1"}
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
t.Cleanup(func() { cancel(nil) })
|
||||
|
||||
q, _ := NewMemoryQueue(ctx).(*fifo)
|
||||
assert.NoError(t, q.Push(ctx, want))
|
||||
assert.NotNil(t, q)
|
||||
|
||||
dummyTask := genDummyTask()
|
||||
|
||||
assert.NoError(t, q.Push(ctx, dummyTask))
|
||||
|
||||
waitForProcess()
|
||||
got, err := q.Poll(ctx, 1, filterFnTrue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, want, got)
|
||||
assert.Equal(t, dummyTask, got)
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
@ -98,27 +120,34 @@ func TestFifoWait(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFifoEvict(t *testing.T) {
|
||||
t1 := &model.Task{ID: "1"}
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
t.Cleanup(func() { cancel(nil) })
|
||||
|
||||
q := NewMemoryQueue(ctx)
|
||||
assert.NoError(t, q.Push(ctx, t1))
|
||||
dummyTask := genDummyTask()
|
||||
|
||||
assert.NoError(t, q.Push(ctx, dummyTask))
|
||||
|
||||
waitForProcess()
|
||||
info := q.Info(ctx)
|
||||
assert.Len(t, info.Pending, 1, "expect task in pending queue")
|
||||
err := q.Evict(ctx, t1.ID)
|
||||
|
||||
err := q.Evict(ctx, dummyTask.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
waitForProcess()
|
||||
info = q.Info(ctx)
|
||||
assert.Len(t, info.Pending, 0)
|
||||
err = q.Evict(ctx, t1.ID)
|
||||
|
||||
err = q.Evict(ctx, dummyTask.ID)
|
||||
assert.ErrorIs(t, err, ErrNotFound)
|
||||
}
|
||||
|
||||
func TestFifoDependencies(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
task1 := &model.Task{
|
||||
ID: "1",
|
||||
}
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
t.Cleanup(func() { cancel(nil) })
|
||||
|
||||
task1 := genDummyTask()
|
||||
task2 := &model.Task{
|
||||
ID: "2",
|
||||
Dependencies: []string{"1"},
|
||||
|
@ -126,31 +155,34 @@ func TestFifoDependencies(t *testing.T) {
|
|||
}
|
||||
|
||||
q, _ := NewMemoryQueue(ctx).(*fifo)
|
||||
assert.NotNil(t, q)
|
||||
|
||||
assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task1}))
|
||||
|
||||
waitForProcess()
|
||||
got, err := q.Poll(ctx, 1, filterFnTrue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, task1, got)
|
||||
|
||||
waitForProcess()
|
||||
assert.NoError(t, q.Done(ctx, got.ID, model.StatusSuccess))
|
||||
|
||||
waitForProcess()
|
||||
got, err = q.Poll(ctx, 1, filterFnTrue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, task2, got)
|
||||
}
|
||||
|
||||
func TestFifoErrors(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
task1 := &model.Task{
|
||||
ID: "1",
|
||||
}
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
t.Cleanup(func() { cancel(nil) })
|
||||
|
||||
task1 := genDummyTask()
|
||||
task2 := &model.Task{
|
||||
ID: "2",
|
||||
Dependencies: []string{"1"},
|
||||
DepStatus: make(map[string]model.StatusValue),
|
||||
}
|
||||
|
||||
task3 := &model.Task{
|
||||
ID: "3",
|
||||
Dependencies: []string{"1"},
|
||||
|
@ -159,19 +191,24 @@ func TestFifoErrors(t *testing.T) {
|
|||
}
|
||||
|
||||
q, _ := NewMemoryQueue(ctx).(*fifo)
|
||||
assert.NotNil(t, q)
|
||||
|
||||
assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1}))
|
||||
|
||||
waitForProcess()
|
||||
got, err := q.Poll(ctx, 1, filterFnTrue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, task1, got)
|
||||
|
||||
assert.NoError(t, q.Error(ctx, got.ID, fmt.Errorf("exit code 1, there was an error")))
|
||||
|
||||
waitForProcess()
|
||||
got, err = q.Poll(ctx, 1, filterFnTrue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, task2, got)
|
||||
assert.False(t, got.ShouldRun())
|
||||
|
||||
waitForProcess()
|
||||
got, err = q.Poll(ctx, 1, filterFnTrue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, task3, got)
|
||||
|
@ -179,15 +216,13 @@ func TestFifoErrors(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFifoErrors2(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
task1 := &model.Task{
|
||||
ID: "1",
|
||||
}
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
t.Cleanup(func() { cancel(nil) })
|
||||
|
||||
task1 := genDummyTask()
|
||||
task2 := &model.Task{
|
||||
ID: "2",
|
||||
}
|
||||
|
||||
task3 := &model.Task{
|
||||
ID: "3",
|
||||
Dependencies: []string{"1", "2"},
|
||||
|
@ -195,9 +230,12 @@ func TestFifoErrors2(t *testing.T) {
|
|||
}
|
||||
|
||||
q, _ := NewMemoryQueue(ctx).(*fifo)
|
||||
assert.NotNil(t, q)
|
||||
|
||||
assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1}))
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
waitForProcess()
|
||||
got, err := q.Poll(ctx, 1, filterFnTrue)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, got != task1 && got != task2, "expect task1 or task2 returned from queue as task3 depends on them")
|
||||
|
@ -210,6 +248,7 @@ func TestFifoErrors2(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
waitForProcess()
|
||||
got, err := q.Poll(ctx, 1, filterFnTrue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, task3, got)
|
||||
|
@ -217,17 +256,15 @@ func TestFifoErrors2(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFifoErrorsMultiThread(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
task1 := &model.Task{
|
||||
ID: "1",
|
||||
}
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
t.Cleanup(func() { cancel(nil) })
|
||||
|
||||
task1 := genDummyTask()
|
||||
task2 := &model.Task{
|
||||
ID: "2",
|
||||
Dependencies: []string{"1"},
|
||||
DepStatus: make(map[string]model.StatusValue),
|
||||
}
|
||||
|
||||
task3 := &model.Task{
|
||||
ID: "3",
|
||||
Dependencies: []string{"1", "2"},
|
||||
|
@ -235,15 +272,21 @@ func TestFifoErrorsMultiThread(t *testing.T) {
|
|||
}
|
||||
|
||||
q, _ := NewMemoryQueue(ctx).(*fifo)
|
||||
assert.NotNil(t, q)
|
||||
|
||||
assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1}))
|
||||
|
||||
obtainedWorkCh := make(chan *model.Task)
|
||||
defer func() { close(obtainedWorkCh) }()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
go func(i int) {
|
||||
for {
|
||||
fmt.Printf("Worker %d started\n", i)
|
||||
got, err := q.Poll(ctx, 1, filterFnTrue)
|
||||
if err != nil && errors.Is(err, context.Canceled) {
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
obtainedWorkCh <- got
|
||||
}
|
||||
|
@ -266,7 +309,11 @@ func TestFifoErrorsMultiThread(t *testing.T) {
|
|||
go func() {
|
||||
for {
|
||||
fmt.Printf("Worker spawned\n")
|
||||
got, _ := q.Poll(ctx, 1, filterFnTrue)
|
||||
got, err := q.Poll(ctx, 1, filterFnTrue)
|
||||
if err != nil && errors.Is(err, context.Canceled) {
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
obtainedWorkCh <- got
|
||||
}
|
||||
}()
|
||||
|
@ -277,7 +324,11 @@ func TestFifoErrorsMultiThread(t *testing.T) {
|
|||
go func() {
|
||||
for {
|
||||
fmt.Printf("Worker spawned\n")
|
||||
got, _ := q.Poll(ctx, 1, filterFnTrue)
|
||||
got, err := q.Poll(ctx, 1, filterFnTrue)
|
||||
if err != nil && errors.Is(err, context.Canceled) {
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
obtainedWorkCh <- got
|
||||
}
|
||||
}()
|
||||
|
@ -297,17 +348,15 @@ func TestFifoErrorsMultiThread(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFifoTransitiveErrors(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
task1 := &model.Task{
|
||||
ID: "1",
|
||||
}
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
t.Cleanup(func() { cancel(nil) })
|
||||
|
||||
task1 := genDummyTask()
|
||||
task2 := &model.Task{
|
||||
ID: "2",
|
||||
Dependencies: []string{"1"},
|
||||
DepStatus: make(map[string]model.StatusValue),
|
||||
}
|
||||
|
||||
task3 := &model.Task{
|
||||
ID: "3",
|
||||
Dependencies: []string{"2"},
|
||||
|
@ -315,19 +364,24 @@ func TestFifoTransitiveErrors(t *testing.T) {
|
|||
}
|
||||
|
||||
q, _ := NewMemoryQueue(ctx).(*fifo)
|
||||
assert.NotNil(t, q)
|
||||
|
||||
assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1}))
|
||||
|
||||
waitForProcess()
|
||||
got, err := q.Poll(ctx, 1, filterFnTrue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, task1, got)
|
||||
assert.NoError(t, q.Error(ctx, got.ID, fmt.Errorf("exit code 1, there was an error")))
|
||||
|
||||
waitForProcess()
|
||||
got, err = q.Poll(ctx, 1, filterFnTrue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, task2, got)
|
||||
assert.False(t, got.ShouldRun(), "expect task2 should not run, since task1 failed")
|
||||
assert.NoError(t, q.Done(ctx, got.ID, model.StatusSkipped))
|
||||
|
||||
waitForProcess()
|
||||
got, err = q.Poll(ctx, 1, filterFnTrue)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, task3, got)
|
||||
|
@ -335,17 +389,15 @@ func TestFifoTransitiveErrors(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFifoCancel(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
task1 := &model.Task{
|
||||
ID: "1",
|
||||
}
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
t.Cleanup(func() { cancel(nil) })
|
||||
|
||||
task1 := genDummyTask()
|
||||
task2 := &model.Task{
|
||||
ID: "2",
|
||||
Dependencies: []string{"1"},
|
||||
DepStatus: make(map[string]model.StatusValue),
|
||||
}
|
||||
|
||||
task3 := &model.Task{
|
||||
ID: "3",
|
||||
Dependencies: []string{"1"},
|
||||
|
@ -354,24 +406,33 @@ func TestFifoCancel(t *testing.T) {
|
|||
}
|
||||
|
||||
q, _ := NewMemoryQueue(ctx).(*fifo)
|
||||
assert.NotNil(t, q)
|
||||
|
||||
assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1}))
|
||||
|
||||
_, _ = q.Poll(ctx, 1, filterFnTrue)
|
||||
assert.NoError(t, q.Error(ctx, task1.ID, fmt.Errorf("canceled")))
|
||||
assert.NoError(t, q.Error(ctx, task2.ID, fmt.Errorf("canceled")))
|
||||
assert.NoError(t, q.Error(ctx, task3.ID, fmt.Errorf("canceled")))
|
||||
|
||||
info := q.Info(ctx)
|
||||
assert.Len(t, info.Pending, 0, "all pipelines should be canceled")
|
||||
|
||||
time.Sleep(processTimeInterval * 2)
|
||||
info = q.Info(ctx)
|
||||
assert.Len(t, info.Pending, 2, "canceled are rescheduled")
|
||||
assert.Len(t, info.Running, 0, "canceled are rescheduled")
|
||||
assert.Len(t, info.WaitingOnDeps, 0, "canceled are rescheduled")
|
||||
}
|
||||
|
||||
func TestFifoPause(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
task1 := &model.Task{
|
||||
ID: "1",
|
||||
}
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
t.Cleanup(func() { cancel(nil) })
|
||||
|
||||
q, _ := NewMemoryQueue(ctx).(*fifo)
|
||||
assert.NotNil(t, q)
|
||||
|
||||
dummyTask := genDummyTask()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
|
@ -381,8 +442,8 @@ func TestFifoPause(t *testing.T) {
|
|||
|
||||
q.Pause()
|
||||
t0 := time.Now()
|
||||
assert.NoError(t, q.Push(ctx, task1))
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
assert.NoError(t, q.Push(ctx, dummyTask))
|
||||
waitForProcess()
|
||||
q.Resume()
|
||||
|
||||
wg.Wait()
|
||||
|
@ -391,37 +452,37 @@ func TestFifoPause(t *testing.T) {
|
|||
assert.Greater(t, t1.Sub(t0), 20*time.Millisecond, "should have waited til resume")
|
||||
|
||||
q.Pause()
|
||||
assert.NoError(t, q.Push(ctx, task1))
|
||||
assert.NoError(t, q.Push(ctx, dummyTask))
|
||||
q.Resume()
|
||||
_, _ = q.Poll(ctx, 1, filterFnTrue)
|
||||
}
|
||||
|
||||
func TestFifoPauseResume(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
task1 := &model.Task{
|
||||
ID: "1",
|
||||
}
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
t.Cleanup(func() { cancel(nil) })
|
||||
|
||||
q, _ := NewMemoryQueue(ctx).(*fifo)
|
||||
assert.NotNil(t, q)
|
||||
|
||||
dummyTask := genDummyTask()
|
||||
|
||||
q.Pause()
|
||||
assert.NoError(t, q.Push(ctx, task1))
|
||||
assert.NoError(t, q.Push(ctx, dummyTask))
|
||||
q.Resume()
|
||||
|
||||
_, _ = q.Poll(ctx, 1, filterFnTrue)
|
||||
}
|
||||
|
||||
func TestWaitingVsPending(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
task1 := &model.Task{
|
||||
ID: "1",
|
||||
}
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
t.Cleanup(func() { cancel(nil) })
|
||||
|
||||
task1 := genDummyTask()
|
||||
task2 := &model.Task{
|
||||
ID: "2",
|
||||
Dependencies: []string{"1"},
|
||||
DepStatus: make(map[string]model.StatusValue),
|
||||
}
|
||||
|
||||
task3 := &model.Task{
|
||||
ID: "3",
|
||||
Dependencies: []string{"1"},
|
||||
|
@ -430,10 +491,13 @@ func TestWaitingVsPending(t *testing.T) {
|
|||
}
|
||||
|
||||
q, _ := NewMemoryQueue(ctx).(*fifo)
|
||||
assert.NotNil(t, q)
|
||||
|
||||
assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1}))
|
||||
|
||||
got, _ := q.Poll(ctx, 1, filterFnTrue)
|
||||
|
||||
waitForProcess()
|
||||
info := q.Info(ctx)
|
||||
assert.Equal(t, 2, info.Stats.WaitingOnDeps)
|
||||
|
||||
|
@ -442,6 +506,7 @@ func TestWaitingVsPending(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, task2, got)
|
||||
|
||||
waitForProcess()
|
||||
info = q.Info(ctx)
|
||||
assert.Equal(t, 0, info.Stats.WaitingOnDeps)
|
||||
assert.Equal(t, 1, info.Stats.Pending)
|
||||
|
@ -518,7 +583,9 @@ func TestShouldRun(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFifoWithScoring(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
ctx, cancel := context.WithCancelCause(context.Background())
|
||||
t.Cleanup(func() { cancel(nil) })
|
||||
|
||||
q := NewMemoryQueue(ctx)
|
||||
|
||||
// Create tasks with different labels
|
||||
|
@ -530,9 +597,7 @@ func TestFifoWithScoring(t *testing.T) {
|
|||
{ID: "5", Labels: map[string]string{"org-id": "*", "platform": "linux"}},
|
||||
}
|
||||
|
||||
for _, task := range tasks {
|
||||
assert.NoError(t, q.Push(ctx, task))
|
||||
}
|
||||
assert.NoError(t, q.PushAtOnce(ctx, tasks))
|
||||
|
||||
// Create filter functions for different workers
|
||||
filters := map[int]FilterFn{
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
// 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 migration
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"src.techknowlogick.com/xormigrate"
|
||||
"xorm.io/builder"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
var gatedToRequireApproval = xormigrate.Migration{
|
||||
ID: "gated-to-require-approval",
|
||||
MigrateSession: func(sess *xorm.Session) (err error) {
|
||||
const (
|
||||
RequireApprovalNone string = "none"
|
||||
RequireApprovalForks string = "forks"
|
||||
RequireApprovalPullRequests string = "pull_requests"
|
||||
RequireApprovalAllEvents string = "all_events"
|
||||
)
|
||||
|
||||
type repos struct {
|
||||
ID int64 `xorm:"pk autoincr 'id'"`
|
||||
IsGated bool `xorm:"gated"`
|
||||
RequireApproval string `xorm:"require_approval"`
|
||||
Visibility string `xorm:"varchar(10) 'visibility'"`
|
||||
}
|
||||
|
||||
if err := sess.Sync(new(repos)); err != nil {
|
||||
return fmt.Errorf("sync new models failed: %w", err)
|
||||
}
|
||||
|
||||
// migrate gated repos
|
||||
if _, err := sess.Exec(
|
||||
builder.Update(builder.Eq{"require_approval": RequireApprovalAllEvents}).
|
||||
From("repos").
|
||||
Where(builder.Eq{"gated": true})); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// migrate public repos to new default require approval
|
||||
if _, err := sess.Exec(
|
||||
builder.Update(builder.Eq{"require_approval": RequireApprovalForks}).
|
||||
From("repos").
|
||||
Where(builder.Eq{"gated": false, "visibility": "public"})); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// migrate private repos to new default require approval
|
||||
if _, err := sess.Exec(
|
||||
builder.Update(builder.Eq{"require_approval": RequireApprovalNone}).
|
||||
From("repos").
|
||||
Where(builder.Eq{"gated": false}.And(builder.Neq{"visibility": "public"}))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dropTableColumns(sess, "repos", "gated")
|
||||
},
|
||||
}
|
|
@ -47,6 +47,7 @@ var migrationTasks = []*xormigrate.Migration{
|
|||
&addCustomLabelsToAgent,
|
||||
&splitTrusted,
|
||||
&correctPotentialCorruptOrgsUsersRelation,
|
||||
&gatedToRequireApproval,
|
||||
}
|
||||
|
||||
var allBeans = []any{
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
"node": ">=20"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
|
@ -18,7 +18,7 @@
|
|||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@intlify/unplugin-vue-i18n": "^5.0.0",
|
||||
"@intlify/unplugin-vue-i18n": "^6.0.0",
|
||||
"@kyvg/vue3-notification": "^3.2.1",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@vueuse/core": "^11.0.0",
|
||||
|
@ -45,7 +45,7 @@
|
|||
"@intlify/eslint-plugin-vue-i18n": "3.0.0",
|
||||
"@types/eslint__js": "^8.42.3",
|
||||
"@types/lodash": "^4.17.7",
|
||||
"@types/node": "^20.14.15",
|
||||
"@types/node": "^22.0.0",
|
||||
"@types/prismjs": "^1.26.4",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/tinycolor2": "^1.4.6",
|
||||
|
|
2220
web/pnpm-lock.yaml
2220
web/pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -88,10 +88,6 @@
|
|||
"allow": "Allow deployments",
|
||||
"desc": "Allow deployments from successful pipelines. Only use if you trust all users with push access."
|
||||
},
|
||||
"protected": {
|
||||
"protected": "Protected",
|
||||
"desc": "Every pipeline needs to be approved before being executed."
|
||||
},
|
||||
"netrc_only_trusted": {
|
||||
"netrc_only_trusted": "Only inject netrc credentials into trusted clone plugins",
|
||||
"desc": "If enabled, git netrc credentials are only available for trusted clone plugins set in `WOODPECKER_PLUGINS_TRUSTED_CLONE`. Otherwise, all clone plugins can use the netrc credentials. This option has no effect on non-clone steps."
|
||||
|
@ -504,5 +500,14 @@
|
|||
"internal_error": "Some internal error occurred",
|
||||
"registration_closed": "The registration is closed",
|
||||
"access_denied": "You are not allowed to access this instance",
|
||||
"invalid_state": "The OAuth state is invalid"
|
||||
"invalid_state": "The OAuth state is invalid",
|
||||
"require_approval": {
|
||||
"require_approval_for": "Require approval for",
|
||||
"none": "No approval required",
|
||||
"none_desc": "This setting can be dangerous and should only be used on private forges where all users are trusted.",
|
||||
"forks": "Pull request from forked repositories",
|
||||
"pull_requests": "All pull requests",
|
||||
"all_events": "All events from forge",
|
||||
"desc": "Prevent malicious pipelines from exposing secrets or running harmful tasks by approving them before execution."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Settings :title="$t('admin.settings.agents.agents')" :desc="desc">
|
||||
<template #titleActions>
|
||||
<Settings :title="$t('admin.settings.agents.agents')" :description>
|
||||
<template #headerActions>
|
||||
<Button
|
||||
v-if="selectedAgent"
|
||||
:text="$t('admin.settings.agents.show')"
|
||||
|
@ -46,7 +46,7 @@ import AgentForm from './AgentForm.vue';
|
|||
import AgentList from './AgentList.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
desc: string;
|
||||
description: string;
|
||||
loadAgents: (page: number) => Promise<Agent[] | null>;
|
||||
createAgent: (agent: Partial<Agent>) => Promise<Agent>;
|
||||
updateAgent: (agent: Agent) => Promise<Agent | void>;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<component
|
||||
:is="to === undefined ? 'button' : httpLink ? 'a' : 'router-link'"
|
||||
v-bind="btnAttrs"
|
||||
class="relative flex items-center py-1 px-2 rounded-md border shadow-sm cursor-pointer transition-all duration-150 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
class="relative flex flex-shrink-0 whitespace-nowrap items-center py-1 px-2 rounded-md border shadow-sm cursor-pointer transition-all duration-150 overflow-hidden disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
:class="{
|
||||
'bg-wp-control-neutral-100 hover:bg-wp-control-neutral-200 border-wp-control-neutral-300 text-wp-text-100':
|
||||
color === 'gray',
|
||||
|
@ -16,7 +16,7 @@
|
|||
>
|
||||
<slot>
|
||||
<Icon v-if="startIcon" :name="startIcon" class="!w-6 !h-6" :class="{ invisible: isLoading, 'mr-1': text }" />
|
||||
<span :class="{ invisible: isLoading }" class="flex-shrink-0">{{ text }}</span>
|
||||
<span :class="{ invisible: isLoading }">{{ text }}</span>
|
||||
<Icon v-if="endIcon" :name="endIcon" class="ml-2 w-6 h-6" :class="{ invisible: isLoading }" />
|
||||
<div
|
||||
v-if="isLoading"
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<div
|
||||
class="flex gap-2 items-center text-gray-700 font-bold rounded-md p-2 border border-solid border-l-6 border-wp-hint-warn-200 bg-wp-hint-warn-100"
|
||||
class="flex gap-4 items-center text-gray-700 font-bold rounded-md p-4 border border-solid border-l-6 border-wp-hint-warn-200 bg-wp-hint-warn-100"
|
||||
>
|
||||
<Icon v-if="!textOnly" name="warning" />
|
||||
<Icon v-if="!textOnly" name="warning" class="flex-shrink-0" />
|
||||
<slot>
|
||||
<span class="whitespace-pre">{{ text }}</span>
|
||||
<span class="whitespace-pre-wrap">{{ text }}</span>
|
||||
</slot>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
type="radio"
|
||||
class="radio relative flex-shrink-0 border bg-wp-control-neutral-100 border-wp-control-neutral-200 cursor-pointer rounded-full w-5 h-5 checked:bg-wp-control-ok-200 checked:border-wp-control-ok-200 focus-visible:border-wp-control-neutral-300 checked:focus-visible:border-wp-control-ok-300"
|
||||
:value="option.value"
|
||||
:checked="innerValue.includes(option.value)"
|
||||
:checked="innerValue?.includes(option.value)"
|
||||
@click="innerValue = option.value"
|
||||
/>
|
||||
<div class="flex flex-col ml-4">
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
<template>
|
||||
<Panel>
|
||||
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-wp-background-100">
|
||||
<div class="ml-2">
|
||||
<h1 class="text-xl text-wp-text-100">{{ title }}</h1>
|
||||
<p v-if="desc" class="text-sm text-wp-text-alt-100">
|
||||
{{ desc }}
|
||||
<div class="flex flex-col border-b mb-4 pb-4 justify-center dark:border-wp-background-100">
|
||||
<h1 class="text-xl text-wp-text-100 flex items-center gap-1">
|
||||
{{ title }}
|
||||
<DocsLink v-if="docsUrl" :topic="title" :url="docsUrl" />
|
||||
</p>
|
||||
<Warning v-if="warning" class="text-sm mt-1" :text="warning" />
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
<div class="ml-auto">
|
||||
<slot v-if="$slots.titleActions" name="titleActions" />
|
||||
<div class="flex flex-wrap gap-x-4 gap-y-2 items-center justify-between">
|
||||
<p v-if="description" class="text-sm text-wp-text-alt-100">{{ description }}</p>
|
||||
<div v-if="$slots.headerActions">
|
||||
<slot name="headerActions" />
|
||||
</div>
|
||||
</div>
|
||||
<slot name="headerEnd" />
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</Panel>
|
||||
|
@ -21,13 +21,11 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import DocsLink from '~/components/atomic/DocsLink.vue';
|
||||
import Warning from '~/components/atomic/Warning.vue';
|
||||
import Panel from '~/components/layout/Panel.vue';
|
||||
|
||||
defineProps<{
|
||||
title: string;
|
||||
desc?: string;
|
||||
description?: string;
|
||||
docsUrl?: string;
|
||||
warning?: string;
|
||||
}>();
|
||||
</script>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
>
|
||||
<div v-if="pipelineCount > 0" class="spinner" />
|
||||
<div
|
||||
class="z-1 flex items-center justify-center h-full w-full font-bold bg-white bg-opacity-15 dark:bg-black dark:bg-opacity-10 rounded-md"
|
||||
class="z-0 flex items-center justify-center h-full w-full font-bold bg-white bg-opacity-15 dark:bg-black dark:bg-opacity-10 rounded-md"
|
||||
>
|
||||
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
|
||||
{{ pipelineCount > 9 ? '9+' : pipelineCount }}
|
||||
|
@ -31,17 +31,17 @@ onMounted(async () => {
|
|||
</script>
|
||||
|
||||
<style scoped>
|
||||
@keyframes spinner-rotate {
|
||||
@keyframes rotate {
|
||||
100% {
|
||||
transform: rotate(1turn);
|
||||
}
|
||||
}
|
||||
.spinner {
|
||||
@apply absolute z-0 inset-1.5 rounded-md;
|
||||
@apply absolute inset-1.5 rounded-md;
|
||||
overflow: hidden;
|
||||
}
|
||||
.spinner::before {
|
||||
@apply absolute -z-2 bg-wp-primary-200 dark:bg-wp-primary-300;
|
||||
@apply absolute bg-wp-primary-200 dark:bg-wp-primary-300;
|
||||
content: '';
|
||||
left: -50%;
|
||||
top: -50%;
|
||||
|
@ -51,11 +51,16 @@ onMounted(async () => {
|
|||
background-size:
|
||||
50% 50%,
|
||||
50% 50%;
|
||||
background-image: linear-gradient(#fff, transparent);
|
||||
animation: spinner-rotate 1.5s linear infinite;
|
||||
background-image: linear-gradient(#fff, #fff);
|
||||
animation: rotate 1.5s linear infinite;
|
||||
}
|
||||
.spinner::after {
|
||||
@apply absolute inset-0.5 rounded-md bg-blend-darken bg-wp-primary-200 dark:bg-wp-primary-300;
|
||||
@apply absolute inset-0.5 bg-wp-primary-200 dark:bg-wp-primary-300;
|
||||
/*
|
||||
The nested border radius needs to be calculated correctly to look right:
|
||||
https://www.30secondsofcode.org/css/s/nested-border-radius/
|
||||
*/
|
||||
border-radius: calc(0.375rem - 0.125rem);
|
||||
content: '';
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -13,45 +13,28 @@
|
|||
</InputField>
|
||||
<InputField v-slot="{ id }" :label="$t('repo.deploy_pipeline.variables.title')">
|
||||
<span class="text-sm text-wp-text-alt-100 mb-2">{{ $t('repo.deploy_pipeline.variables.desc') }}</span>
|
||||
<div class="flex flex-col gap-2">
|
||||
<div v-for="(_, i) in payload.variables" :key="i" class="flex gap-4">
|
||||
<TextField
|
||||
<KeyValueEditor
|
||||
:id="id"
|
||||
v-model="payload.variables[i].name"
|
||||
:placeholder="$t('repo.deploy_pipeline.variables.name')"
|
||||
v-model="payload.variables"
|
||||
:key-placeholder="$t('repo.deploy_pipeline.variables.name')"
|
||||
:value-placeholder="$t('repo.deploy_pipeline.variables.value')"
|
||||
:delete-title="$t('repo.deploy_pipeline.variables.delete')"
|
||||
@update:is-valid="isVariablesValid = $event"
|
||||
/>
|
||||
<TextField
|
||||
:id="id"
|
||||
v-model="payload.variables[i].value"
|
||||
:placeholder="$t('repo.deploy_pipeline.variables.value')"
|
||||
/>
|
||||
<div class="w-10 flex-shrink-0">
|
||||
<Button
|
||||
v-if="i !== payload.variables.length - 1"
|
||||
color="red"
|
||||
class="ml-auto"
|
||||
:title="$t('repo.deploy_pipeline.variables.delete')"
|
||||
@click="deleteVar(i)"
|
||||
>
|
||||
<Icon name="remove" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</InputField>
|
||||
<Button type="submit" :text="$t('repo.deploy_pipeline.trigger')" />
|
||||
<Button type="submit" :text="$t('repo.deploy_pipeline.trigger')" :disabled="!isFormValid" />
|
||||
</form>
|
||||
</Panel>
|
||||
</Popup>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, onMounted, ref, toRef, watch } from 'vue';
|
||||
import { computed, onMounted, ref, toRef } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
import Icon from '~/components/atomic/Icon.vue';
|
||||
import InputField from '~/components/form/InputField.vue';
|
||||
import KeyValueEditor from '~/components/form/KeyValueEditor.vue';
|
||||
import TextField from '~/components/form/TextField.vue';
|
||||
import Panel from '~/components/layout/Panel.vue';
|
||||
import Popup from '~/components/layout/Popup.vue';
|
||||
|
@ -68,55 +51,37 @@ const emit = defineEmits<{
|
|||
}>();
|
||||
|
||||
const apiClient = useApiClient();
|
||||
|
||||
const repo = inject('repo');
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const payload = ref<{ id: string; environment: string; task: string; variables: { name: string; value: string }[] }>({
|
||||
const payload = ref<{
|
||||
id: string;
|
||||
environment: string;
|
||||
task: string;
|
||||
variables: Record<string, string>;
|
||||
}>({
|
||||
id: '',
|
||||
environment: '',
|
||||
task: '',
|
||||
variables: [
|
||||
{
|
||||
name: '',
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
variables: {},
|
||||
});
|
||||
|
||||
const pipelineOptions = computed(() => {
|
||||
const variables = Object.fromEntries(
|
||||
payload.value.variables.filter((e) => e.name !== '').map((item) => [item.name, item.value]),
|
||||
);
|
||||
return {
|
||||
...payload.value,
|
||||
variables,
|
||||
};
|
||||
const isVariablesValid = ref(true);
|
||||
|
||||
const isFormValid = computed(() => {
|
||||
return payload.value.environment !== '' && isVariablesValid.value;
|
||||
});
|
||||
|
||||
const pipelineOptions = computed(() => ({
|
||||
...payload.value,
|
||||
variables: payload.value.variables,
|
||||
}));
|
||||
|
||||
const loading = ref(true);
|
||||
onMounted(async () => {
|
||||
loading.value = false;
|
||||
});
|
||||
|
||||
watch(
|
||||
payload,
|
||||
() => {
|
||||
if (payload.value.variables[payload.value.variables.length - 1].name !== '') {
|
||||
payload.value.variables.push({
|
||||
name: '',
|
||||
value: '',
|
||||
});
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
function deleteVar(index: number) {
|
||||
payload.value.variables.splice(index, 1);
|
||||
}
|
||||
|
||||
const pipelineNumber = toRef(props, 'pipelineNumber');
|
||||
async function triggerDeployPipeline() {
|
||||
loading.value = true;
|
||||
|
|
|
@ -24,26 +24,26 @@
|
|||
</div>
|
||||
<TextField
|
||||
v-if="searchBoxPresent"
|
||||
class="w-auto <md:w-full <md:order-3"
|
||||
class="w-auto <md:w-full flex-grow <md:order-3"
|
||||
:aria-label="$t('search')"
|
||||
:placeholder="$t('search')"
|
||||
:model-value="search"
|
||||
@update:model-value="(value: string) => $emit('update:search', value)"
|
||||
/>
|
||||
<div
|
||||
v-if="$slots.titleActions"
|
||||
v-if="$slots.headerActions"
|
||||
class="flex items-center md:justify-end gap-x-2 min-w-0"
|
||||
:class="{
|
||||
'md:flex-1': searchBoxPresent,
|
||||
}"
|
||||
>
|
||||
<slot name="titleActions" />
|
||||
<slot name="headerActions" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="enableTabs" class="flex md:items-center flex-col py-2 md:flex-row md:justify-between md:py-0">
|
||||
<Tabs class="<md:order-2" />
|
||||
<div v-if="$slots.titleActions" class="flex content-start md:justify-end">
|
||||
<div v-if="$slots.headerActions" class="flex content-start md:justify-end">
|
||||
<slot name="tabActions" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -52,6 +52,8 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
import IconButton from '~/components/atomic/IconButton.vue';
|
||||
import TextField from '~/components/form/TextField.vue';
|
||||
import Container from '~/components/layout/Container.vue';
|
||||
|
@ -69,5 +71,5 @@ defineEmits<{
|
|||
(event: 'update:search', query: string): void;
|
||||
}>();
|
||||
|
||||
const searchBoxPresent = props.search !== undefined;
|
||||
const searchBoxPresent = computed(() => props.search !== undefined);
|
||||
</script>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
@update:search="(value) => $emit('update:search', value)"
|
||||
>
|
||||
<template #title><slot name="title" /></template>
|
||||
<template v-if="$slots.titleActions" #titleActions><slot name="titleActions" /></template>
|
||||
<template v-if="$slots.headerActions" #headerActions><slot name="headerActions" /></template>
|
||||
<template v-if="$slots.tabActions" #tabActions><slot name="tabActions" /></template>
|
||||
</Header>
|
||||
|
||||
|
@ -18,8 +18,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue';
|
||||
|
||||
import Container from '~/components/layout/Container.vue';
|
||||
import { useTabsProvider } from '~/compositions/useTabs';
|
||||
|
||||
|
@ -33,37 +31,16 @@ const props = defineProps<{
|
|||
|
||||
// Tabs
|
||||
enableTabs?: boolean;
|
||||
disableTabUrlHashMode?: boolean;
|
||||
activeTab?: string;
|
||||
|
||||
// Content
|
||||
fluidContent?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'update:activeTab', value: string | undefined): void;
|
||||
defineEmits<{
|
||||
(event: 'update:search', value: string): void;
|
||||
}>();
|
||||
|
||||
if (props.enableTabs) {
|
||||
const internalActiveTab = ref(props.activeTab);
|
||||
|
||||
watch(
|
||||
() => props.activeTab,
|
||||
(activeTab) => {
|
||||
internalActiveTab.value = activeTab;
|
||||
},
|
||||
);
|
||||
|
||||
useTabsProvider({
|
||||
activeTab: computed({
|
||||
get: () => internalActiveTab.value,
|
||||
set: (value) => {
|
||||
internalActiveTab.value = value;
|
||||
emit('update:activeTab', value);
|
||||
},
|
||||
}),
|
||||
disableUrlHashMode: computed(() => props.disableTabUrlHashMode || false),
|
||||
});
|
||||
useTabsProvider();
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,38 +1,40 @@
|
|||
<template>
|
||||
<div v-if="$slots.default" v-show="isActive" :aria-hidden="!isActive">
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
<template><span /></template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue';
|
||||
import { onMounted } from 'vue';
|
||||
import type { RouteLocationRaw } from 'vue-router';
|
||||
|
||||
import type { IconNames } from '~/components/atomic/Icon.vue';
|
||||
import { useTabsClient, type Tab } from '~/compositions/useTabs';
|
||||
import { useTabsClient } from '~/compositions/useTabs';
|
||||
|
||||
const props = defineProps<{
|
||||
id?: string;
|
||||
to: RouteLocationRaw;
|
||||
title: string;
|
||||
icon?: IconNames;
|
||||
iconClass?: string;
|
||||
matchChildren?: boolean;
|
||||
}>();
|
||||
|
||||
const { tabs, activeTab } = useTabsClient();
|
||||
const tab = ref<Tab>();
|
||||
const { tabs } = useTabsClient();
|
||||
|
||||
// TODO: find a better way to compare routes like
|
||||
// https://github.com/vuejs/router/blob/0eaaeb9697acd40ad524d913d0348748e9797acb/packages/router/src/utils/index.ts#L17
|
||||
function isSameRoute(a: RouteLocationRaw, b: RouteLocationRaw): boolean {
|
||||
return JSON.stringify(a) === JSON.stringify(b);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
tab.value = {
|
||||
id: props.id || props.title.toLocaleLowerCase().replace(' ', '-') || tabs.value.length.toString(),
|
||||
// don't add tab if tab id is already present
|
||||
if (tabs.value.find(({ to }) => isSameRoute(to, props.to))) {
|
||||
return;
|
||||
}
|
||||
|
||||
tabs.value.push({
|
||||
to: props.to,
|
||||
title: props.title,
|
||||
icon: props.icon,
|
||||
iconClass: props.iconClass,
|
||||
};
|
||||
|
||||
// don't add tab if tab id is already present
|
||||
if (!tabs.value.find(({ id }) => id === props.id)) {
|
||||
tabs.value.push(tab.value);
|
||||
}
|
||||
matchChildren: props.matchChildren,
|
||||
});
|
||||
});
|
||||
|
||||
const isActive = computed(() => tab.value && tab.value.id === activeTab.value);
|
||||
</script>
|
||||
|
|
|
@ -1,46 +1,27 @@
|
|||
<template>
|
||||
<div class="flex flex-wrap">
|
||||
<button
|
||||
<router-link
|
||||
v-for="tab in tabs"
|
||||
:key="tab.id"
|
||||
class="w-full py-1 md:py-2 md:w-auto md:px-6 flex cursor-pointer md:border-b-2 text-wp-text-100 hover:text-wp-text-200 items-center"
|
||||
:class="{
|
||||
'border-wp-text-100': activeTab === tab.id,
|
||||
'border-transparent': activeTab !== tab.id,
|
||||
}"
|
||||
type="button"
|
||||
@click="selectTab(tab)"
|
||||
:key="tab.title"
|
||||
v-slot="{ isActive, isExactActive }"
|
||||
:to="tab.to"
|
||||
class="border-transparent w-full py-1 md:py-2 md:w-auto md:px-6 flex cursor-pointer md:border-b-2 text-wp-text-100 hover:text-wp-text-200 items-center"
|
||||
:active-class="tab.matchChildren ? '!border-wp-text-100' : ''"
|
||||
:exact-active-class="tab.matchChildren ? '' : '!border-wp-text-100'"
|
||||
>
|
||||
<Icon v-if="activeTab === tab.id" name="chevron-right" class="md:hidden" />
|
||||
<Icon v-if="isExactActive || (isActive && tab.matchChildren)" name="chevron-right" class="md:hidden" />
|
||||
<Icon v-else name="blank" class="md:hidden" />
|
||||
<span class="flex gap-2 items-center flex-row-reverse md:flex-row">
|
||||
<Icon v-if="tab.icon" :name="tab.icon" :class="tab.iconClass" />
|
||||
<span>{{ tab.title }}</span>
|
||||
</span>
|
||||
</button>
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import Icon from '~/components/atomic/Icon.vue';
|
||||
import { useTabsClient, type Tab } from '~/compositions/useTabs';
|
||||
import { useTabsClient } from '~/compositions/useTabs';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const { activeTab, tabs, disableUrlHashMode } = useTabsClient();
|
||||
|
||||
async function selectTab(tab: Tab) {
|
||||
if (tab.id === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeTab.value = tab.id;
|
||||
|
||||
if (!disableUrlHashMode.value) {
|
||||
await router.replace({ params: route.params, hash: `#${tab.id}` });
|
||||
}
|
||||
}
|
||||
const { tabs } = useTabsClient();
|
||||
</script>
|
||||
|
|
|
@ -1,12 +1,17 @@
|
|||
import type { InjectionKey, Ref } from 'vue';
|
||||
import { inject as vueInject, provide as vueProvide } from 'vue';
|
||||
|
||||
import type { Org, OrgPermissions, Repo } from '~/lib/api/types';
|
||||
import type { Org, OrgPermissions, Pipeline, PipelineConfig, Repo } from '~/lib/api/types';
|
||||
|
||||
import type { Tab } from './useTabs';
|
||||
|
||||
export interface InjectKeys {
|
||||
repo: Ref<Repo>;
|
||||
org: Ref<Org | undefined>;
|
||||
'org-permissions': Ref<OrgPermissions | undefined>;
|
||||
pipeline: Ref<Pipeline | undefined>;
|
||||
'pipeline-configs': Ref<PipelineConfig[] | undefined>;
|
||||
tabs: Ref<Tab[]>;
|
||||
}
|
||||
|
||||
export function inject<T extends keyof InjectKeys>(key: T): InjectKeys[T] {
|
||||
|
|
|
@ -1,49 +1,24 @@
|
|||
import { inject, onMounted, provide, ref, type Ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { ref } from 'vue';
|
||||
import type { RouteLocationRaw } from 'vue-router';
|
||||
|
||||
import type { IconNames } from '~/components/atomic/Icon.vue';
|
||||
|
||||
import { inject, provide } from './useInjectProvide';
|
||||
|
||||
export interface Tab {
|
||||
id: string;
|
||||
to: RouteLocationRaw;
|
||||
title: string;
|
||||
icon?: IconNames;
|
||||
iconClass?: string;
|
||||
matchChildren?: boolean;
|
||||
}
|
||||
|
||||
export function useTabsProvider({
|
||||
activeTab,
|
||||
disableUrlHashMode,
|
||||
}: {
|
||||
activeTab: Ref<string | undefined>;
|
||||
disableUrlHashMode: Ref<boolean>;
|
||||
}) {
|
||||
const route = useRoute();
|
||||
|
||||
export function useTabsProvider() {
|
||||
const tabs = ref<Tab[]>([]);
|
||||
|
||||
provide('tabs', tabs);
|
||||
provide('disable-url-hash-mode', disableUrlHashMode);
|
||||
provide('active-tab', activeTab);
|
||||
|
||||
onMounted(() => {
|
||||
if (activeTab.value !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hashTab = route.hash.replace(/^#/, '');
|
||||
|
||||
activeTab.value = hashTab || tabs.value[0].id;
|
||||
});
|
||||
}
|
||||
|
||||
export function useTabsClient() {
|
||||
const tabs = inject<Ref<Tab[]>>('tabs');
|
||||
const disableUrlHashMode = inject<Ref<boolean>>('disable-url-hash-mode');
|
||||
const activeTab = inject<Ref<string>>('active-tab');
|
||||
|
||||
if (activeTab === undefined || tabs === undefined || disableUrlHashMode === undefined) {
|
||||
throw new Error('Please use this "useTabsClient" composition inside a component running "useTabsProvider".');
|
||||
}
|
||||
|
||||
return { activeTab, tabs, disableUrlHashMode };
|
||||
const tabs = inject('tabs');
|
||||
return { tabs };
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ export interface Repo {
|
|||
|
||||
last_pipeline: number;
|
||||
|
||||
gated: boolean;
|
||||
require_approval: RepoRequireApproval;
|
||||
|
||||
// Events that will cancel running pipelines before starting a new one
|
||||
cancel_previous_pipeline_events: string[];
|
||||
|
@ -81,6 +81,13 @@ export enum RepoVisibility {
|
|||
Private = 'private',
|
||||
Internal = 'internal',
|
||||
}
|
||||
|
||||
export enum RepoRequireApproval {
|
||||
None = 'none',
|
||||
Forks = 'forks',
|
||||
PullRequests = 'pull_requests',
|
||||
AllEvents = 'all_events',
|
||||
}
|
||||
/* eslint-enable */
|
||||
|
||||
export type RepoSettings = Pick<
|
||||
|
@ -89,7 +96,7 @@ export type RepoSettings = Pick<
|
|||
| 'timeout'
|
||||
| 'visibility'
|
||||
| 'trusted'
|
||||
| 'gated'
|
||||
| 'require_approval'
|
||||
| 'allow_pr'
|
||||
| 'allow_deploy'
|
||||
| 'cancel_previous_pipeline_events'
|
||||
|
|
|
@ -42,30 +42,39 @@ const routes: RouteRecordRaw[] = [
|
|||
},
|
||||
{
|
||||
path: 'branches',
|
||||
meta: { repoHeader: true },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'repo-branches',
|
||||
component: (): Component => import('~/views/repo/RepoBranches.vue'),
|
||||
meta: { repoHeader: true },
|
||||
},
|
||||
{
|
||||
path: 'branches/:branch',
|
||||
path: ':branch',
|
||||
name: 'repo-branch',
|
||||
component: (): Component => import('~/views/repo/RepoBranch.vue'),
|
||||
meta: { repoHeader: true },
|
||||
props: (route) => ({ branch: route.params.branch }),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
path: 'pull-requests',
|
||||
meta: { repoHeader: true },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'repo-pull-requests',
|
||||
component: (): Component => import('~/views/repo/RepoPullRequests.vue'),
|
||||
meta: { repoHeader: true },
|
||||
},
|
||||
{
|
||||
path: 'pull-requests/:pullRequest',
|
||||
path: ':pullRequest',
|
||||
name: 'repo-pull-request',
|
||||
component: (): Component => import('~/views/repo/RepoPullRequest.vue'),
|
||||
meta: { repoHeader: true },
|
||||
props: (route) => ({ pullRequest: route.params.pullRequest }),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'pipeline/:pipelineId',
|
||||
component: (): Component => import('~/views/repo/pipeline/PipelineWrapper.vue'),
|
||||
|
@ -98,15 +107,53 @@ const routes: RouteRecordRaw[] = [
|
|||
path: 'debug',
|
||||
name: 'repo-pipeline-debug',
|
||||
component: (): Component => import('~/views/repo/pipeline/PipelineDebug.vue'),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
name: 'repo-settings',
|
||||
component: (): Component => import('~/views/repo/RepoSettings.vue'),
|
||||
component: (): Component => import('~/views/repo/settings/RepoSettings.vue'),
|
||||
meta: { authentication: 'required' },
|
||||
props: true,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'repo-settings',
|
||||
component: (): Component => import('~/views/repo/settings/General.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'secrets',
|
||||
name: 'repo-settings-secrets',
|
||||
component: (): Component => import('~/views/repo/settings/Secrets.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'registries',
|
||||
name: 'repo-settings-registries',
|
||||
component: (): Component => import('~/views/repo/settings/Registries.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'crons',
|
||||
name: 'repo-settings-crons',
|
||||
component: (): Component => import('~/views/repo/settings/Crons.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'badge',
|
||||
name: 'repo-settings-badge',
|
||||
component: (): Component => import('~/views/repo/settings/Badge.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'actions',
|
||||
name: 'repo-settings-actions',
|
||||
component: (): Component => import('~/views/repo/settings/Actions.vue'),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: 'manual',
|
||||
|
@ -137,9 +184,29 @@ const routes: RouteRecordRaw[] = [
|
|||
{
|
||||
path: 'settings',
|
||||
name: 'org-settings',
|
||||
component: (): Component => import('~/views/org/OrgSettings.vue'),
|
||||
component: (): Component => import('~/views/org/settings/OrgSettingsWrapper.vue'),
|
||||
meta: { authentication: 'required' },
|
||||
props: true,
|
||||
children: [
|
||||
{
|
||||
path: 'secrets',
|
||||
name: 'org-settings-secrets',
|
||||
component: (): Component => import('~/views/org/settings/OrgSecrets.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'registries',
|
||||
name: 'org-settings-registries',
|
||||
component: (): Component => import('~/views/org/settings/OrgRegistries.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'agents',
|
||||
name: 'org-settings-agents',
|
||||
component: (): Component => import('~/views/org/settings/OrgAgents.vue'),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -150,18 +217,98 @@ const routes: RouteRecordRaw[] = [
|
|||
},
|
||||
{
|
||||
path: `${rootPath}/admin`,
|
||||
name: 'admin-settings',
|
||||
component: (): Component => import('~/views/admin/AdminSettings.vue'),
|
||||
component: (): Component => import('~/views/admin/AdminSettingsWrapper.vue'),
|
||||
props: true,
|
||||
meta: { authentication: 'required' },
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'admin-settings',
|
||||
component: (): Component => import('~/views/admin/AdminInfo.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'secrets',
|
||||
name: 'admin-settings-secrets',
|
||||
component: (): Component => import('~/views/admin/AdminSecrets.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'registries',
|
||||
name: 'admin-settings-registries',
|
||||
component: (): Component => import('~/views/admin/AdminRegistries.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'repos',
|
||||
name: 'admin-settings-repos',
|
||||
component: (): Component => import('~/views/admin/AdminRepos.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
name: 'admin-settings-users',
|
||||
component: (): Component => import('~/views/admin/AdminUsers.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'orgs',
|
||||
name: 'admin-settings-orgs',
|
||||
component: (): Component => import('~/views/admin/AdminOrgs.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'agents',
|
||||
name: 'admin-settings-agents',
|
||||
component: (): Component => import('~/views/admin/AdminAgents.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'queue',
|
||||
name: 'admin-settings-queue',
|
||||
component: (): Component => import('~/views/admin/AdminQueue.vue'),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
{
|
||||
path: `${rootPath}/user`,
|
||||
name: 'user',
|
||||
component: (): Component => import('~/views/User.vue'),
|
||||
component: (): Component => import('~/views/user/UserWrapper.vue'),
|
||||
meta: { authentication: 'required' },
|
||||
props: true,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
name: 'user',
|
||||
component: (): Component => import('~/views/user/UserGeneral.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'secrets',
|
||||
name: 'user-secrets',
|
||||
component: (): Component => import('~/views/user/UserSecrets.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'registries',
|
||||
name: 'user-registries',
|
||||
component: (): Component => import('~/views/user/UserRegistries.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'cli-and-api',
|
||||
name: 'user-cli-and-api',
|
||||
component: (): Component => import('~/views/user/UserCLIAndAPI.vue'),
|
||||
props: true,
|
||||
},
|
||||
{
|
||||
path: 'agents',
|
||||
name: 'user-agents',
|
||||
component: (): Component => import('~/views/user/UserAgents.vue'),
|
||||
props: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: `${rootPath}/login`,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
{{ $t('repositories') }}
|
||||
</template>
|
||||
|
||||
<template #titleActions>
|
||||
<template #headerActions>
|
||||
<Button :to="{ name: 'repo-add' }" start-icon="plus" :text="$t('repo.add')" />
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
<template>
|
||||
<Scaffold enable-tabs>
|
||||
<template #title>{{ $t('user.settings.settings') }}</template>
|
||||
<template #titleActions><Button :text="$t('logout')" :to="`${address}/logout`" /></template>
|
||||
<Tab id="general" :title="$t('user.settings.general.general')">
|
||||
<UserGeneralTab />
|
||||
</Tab>
|
||||
<Tab id="secrets" :title="$t('secrets.secrets')">
|
||||
<UserSecretsTab />
|
||||
</Tab>
|
||||
<Tab id="registries" :title="$t('registries.registries')">
|
||||
<UserRegistriesTab />
|
||||
</Tab>
|
||||
<Tab id="cli-and-api" :title="$t('user.settings.cli_and_api.cli_and_api')">
|
||||
<UserCLIAndAPITab />
|
||||
</Tab>
|
||||
<Tab v-if="useConfig().userRegisteredAgents" id="agents" :title="$t('admin.settings.agents.agents')">
|
||||
<UserAgentsTab />
|
||||
</Tab>
|
||||
</Scaffold>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
||||
import Tab from '~/components/layout/scaffold/Tab.vue';
|
||||
import UserAgentsTab from '~/components/user/UserAgentsTab.vue';
|
||||
import UserCLIAndAPITab from '~/components/user/UserCLIAndAPITab.vue';
|
||||
import UserGeneralTab from '~/components/user/UserGeneralTab.vue';
|
||||
import UserRegistriesTab from '~/components/user/UserRegistriesTab.vue';
|
||||
import UserSecretsTab from '~/components/user/UserSecretsTab.vue';
|
||||
import useConfig from '~/compositions/useConfig';
|
||||
|
||||
const address = `${window.location.protocol}//${window.location.host}${useConfig().rootPath}`; // port is included in location.host
|
||||
</script>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<AgentManager
|
||||
:desc="$t('admin.settings.agents.desc')"
|
||||
:description="$t('admin.settings.agents.desc')"
|
||||
:load-agents="loadAgents"
|
||||
:create-agent="createAgent"
|
||||
:update-agent="updateAgent"
|
|
@ -3,7 +3,7 @@
|
|||
<div class="flex flex-col items-center gap-4">
|
||||
<WoodpeckerLogo class="w-32 h-32 fill-wp-text-200" />
|
||||
|
||||
<i18n-t keypath="running_version" tag="p" class="text-xl">
|
||||
<i18n-t keypath="running_version" tag="p" class="text-xl text-center">
|
||||
<span class="font-bold">{{ version?.current }}</span>
|
||||
</i18n-t>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<Settings :title="$t('admin.settings.orgs.orgs')" :desc="$t('admin.settings.orgs.desc')">
|
||||
<Settings :title="$t('admin.settings.orgs.orgs')" :description="$t('admin.settings.orgs.desc')">
|
||||
<div class="space-y-4 text-wp-text-100">
|
||||
<ListItem
|
||||
v-for="org in orgs"
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Settings :title="$t('admin.settings.queue.queue')" :desc="$t('admin.settings.queue.desc')">
|
||||
<template #titleActions>
|
||||
<Settings :title="$t('admin.settings.queue.queue')" :description="$t('admin.settings.queue.desc')">
|
||||
<template #headerActions>
|
||||
<div v-if="queueInfo">
|
||||
<div class="flex items-center gap-2">
|
||||
<Button
|
||||
|
@ -78,6 +78,7 @@
|
|||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import AdminQueueStats from '~/components/admin/settings/queue/AdminQueueStats.vue';
|
||||
import Badge from '~/components/atomic/Badge.vue';
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
import Icon from '~/components/atomic/Icon.vue';
|
||||
|
@ -87,8 +88,6 @@ import useApiClient from '~/compositions/useApiClient';
|
|||
import useNotifications from '~/compositions/useNotifications';
|
||||
import type { QueueInfo } from '~/lib/api/types';
|
||||
|
||||
import AdminQueueStats from './queue/AdminQueueStats.vue';
|
||||
|
||||
const apiClient = useApiClient();
|
||||
const notifications = useNotifications();
|
||||
const { t } = useI18n();
|
|
@ -1,11 +1,10 @@
|
|||
<template>
|
||||
<Settings
|
||||
:title="$t('registries.registries')"
|
||||
:desc="$t('admin.settings.registries.desc')"
|
||||
:description="$t('admin.settings.registries.desc')"
|
||||
docs-url="docs/usage/registries"
|
||||
:warning="$t('admin.settings.registries.warning')"
|
||||
>
|
||||
<template #titleActions>
|
||||
<template #headerActions>
|
||||
<Button
|
||||
v-if="selectedRegistry"
|
||||
:text="$t('registries.show')"
|
||||
|
@ -15,6 +14,10 @@
|
|||
<Button v-else :text="$t('registries.add')" start-icon="plus" @click="showAddRegistry" />
|
||||
</template>
|
||||
|
||||
<template #headerEnd>
|
||||
<Warning class="text-sm mt-4" :text="$t('admin.settings.registries.warning')" />
|
||||
</template>
|
||||
|
||||
<RegistryList
|
||||
v-if="!selectedRegistry"
|
||||
v-model="registries"
|
||||
|
@ -39,6 +42,7 @@ import { computed, ref } from 'vue';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
import Warning from '~/components/atomic/Warning.vue';
|
||||
import Settings from '~/components/layout/Settings.vue';
|
||||
import RegistryEdit from '~/components/registry/RegistryEdit.vue';
|
||||
import RegistryList from '~/components/registry/RegistryList.vue';
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Settings :title="$t('admin.settings.repos.repos')" :desc="$t('admin.settings.repos.desc')">
|
||||
<template #titleActions>
|
||||
<Settings :title="$t('admin.settings.repos.repos')" :description="$t('admin.settings.repos.desc')">
|
||||
<template #headerActions>
|
||||
<Button
|
||||
start-icon="heal"
|
||||
:is-loading="isRepairingRepos"
|
|
@ -1,15 +1,18 @@
|
|||
<template>
|
||||
<Settings
|
||||
:title="$t('secrets.secrets')"
|
||||
:desc="$t('admin.settings.secrets.desc')"
|
||||
:description="$t('admin.settings.secrets.desc')"
|
||||
docs-url="docs/usage/secrets"
|
||||
:warning="$t('admin.settings.secrets.warning')"
|
||||
>
|
||||
<template #titleActions>
|
||||
<template #headerActions>
|
||||
<Button v-if="selectedSecret" :text="$t('secrets.show')" start-icon="back" @click="selectedSecret = undefined" />
|
||||
<Button v-else :text="$t('secrets.add')" start-icon="plus" @click="showAddSecret" />
|
||||
</template>
|
||||
|
||||
<template #headerEnd>
|
||||
<Warning class="text-sm mt-4" :text="$t('admin.settings.secrets.warning')" />
|
||||
</template>
|
||||
|
||||
<SecretList
|
||||
v-if="!selectedSecret"
|
||||
v-model="secrets"
|
||||
|
@ -34,6 +37,7 @@ import { computed, ref } from 'vue';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
import Warning from '~/components/atomic/Warning.vue';
|
||||
import Settings from '~/components/layout/Settings.vue';
|
||||
import SecretEdit from '~/components/secrets/SecretEdit.vue';
|
||||
import SecretList from '~/components/secrets/SecretList.vue';
|
|
@ -1,67 +0,0 @@
|
|||
<template>
|
||||
<Scaffold enable-tabs>
|
||||
<template #title>
|
||||
{{ $t('settings') }}
|
||||
</template>
|
||||
<Tab id="info" :title="$t('info')">
|
||||
<AdminInfoTab />
|
||||
</Tab>
|
||||
<Tab id="secrets" :title="$t('secrets.secrets')">
|
||||
<AdminSecretsTab />
|
||||
</Tab>
|
||||
<Tab id="registries" :title="$t('registries.registries')">
|
||||
<AdminRegistriesTab />
|
||||
</Tab>
|
||||
<Tab id="repos" :title="$t('admin.settings.repos.repos')">
|
||||
<AdminReposTab />
|
||||
</Tab>
|
||||
<Tab id="users" :title="$t('admin.settings.users.users')">
|
||||
<AdminUsersTab />
|
||||
</Tab>
|
||||
<Tab id="orgs" :title="$t('admin.settings.orgs.orgs')">
|
||||
<AdminOrgsTab />
|
||||
</Tab>
|
||||
<Tab id="agents" :title="$t('admin.settings.agents.agents')">
|
||||
<AdminAgentsTab />
|
||||
</Tab>
|
||||
<Tab id="queue" :title="$t('admin.settings.queue.queue')">
|
||||
<AdminQueueTab />
|
||||
</Tab>
|
||||
</Scaffold>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import AdminAgentsTab from '~/components/admin/settings/AdminAgentsTab.vue';
|
||||
import AdminInfoTab from '~/components/admin/settings/AdminInfoTab.vue';
|
||||
import AdminOrgsTab from '~/components/admin/settings/AdminOrgsTab.vue';
|
||||
import AdminQueueTab from '~/components/admin/settings/AdminQueueTab.vue';
|
||||
import AdminRegistriesTab from '~/components/admin/settings/AdminRegistriesTab.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';
|
||||
import Tab from '~/components/layout/scaffold/Tab.vue';
|
||||
import useAuthentication from '~/compositions/useAuthentication';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
||||
|
||||
const notifications = useNotifications();
|
||||
const router = useRouter();
|
||||
const i18n = useI18n();
|
||||
const { user } = useAuthentication();
|
||||
|
||||
onMounted(async () => {
|
||||
if (!user?.admin) {
|
||||
notifications.notify({ type: 'error', title: i18n.t('admin.settings.not_allowed') });
|
||||
await router.replace({ name: 'home' });
|
||||
}
|
||||
|
||||
if (!user?.admin) {
|
||||
notifications.notify({ type: 'error', title: i18n.t('admin.settings.not_allowed') });
|
||||
await router.replace({ name: 'home' });
|
||||
}
|
||||
});
|
||||
</script>
|
40
web/src/views/admin/AdminSettingsWrapper.vue
Normal file
40
web/src/views/admin/AdminSettingsWrapper.vue
Normal file
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<Scaffold enable-tabs>
|
||||
<template #title>
|
||||
{{ $t('settings') }}
|
||||
</template>
|
||||
<Tab :to="{ name: 'admin-settings' }" :title="$t('info')" />
|
||||
<Tab :to="{ name: 'admin-settings-secrets' }" :title="$t('secrets.secrets')" />
|
||||
<Tab :to="{ name: 'admin-settings-registries' }" :title="$t('registries.registries')" />
|
||||
<Tab :to="{ name: 'admin-settings-repos' }" :title="$t('admin.settings.repos.repos')" />
|
||||
<Tab :to="{ name: 'admin-settings-users' }" :title="$t('admin.settings.users.users')" />
|
||||
<Tab :to="{ name: 'admin-settings-orgs' }" :title="$t('admin.settings.orgs.orgs')" />
|
||||
<Tab :to="{ name: 'admin-settings-agents' }" :title="$t('admin.settings.agents.agents')" />
|
||||
<Tab :to="{ name: 'admin-settings-queue' }" :title="$t('admin.settings.queue.queue')" />
|
||||
|
||||
<router-view />
|
||||
</Scaffold>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
||||
import Tab from '~/components/layout/scaffold/Tab.vue';
|
||||
import useAuthentication from '~/compositions/useAuthentication';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
||||
|
||||
const notifications = useNotifications();
|
||||
const router = useRouter();
|
||||
const i18n = useI18n();
|
||||
const { user } = useAuthentication();
|
||||
|
||||
onMounted(async () => {
|
||||
if (!user?.admin) {
|
||||
notifications.notify({ type: 'error', title: i18n.t('admin.settings.not_allowed') });
|
||||
await router.replace({ name: 'home' });
|
||||
}
|
||||
});
|
||||
</script>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Settings :title="$t('admin.settings.users.users')" :desc="$t('admin.settings.users.desc')">
|
||||
<template #titleActions>
|
||||
<Settings :title="$t('admin.settings.users.users')" :description="$t('admin.settings.users.desc')">
|
||||
<template #headerActions>
|
||||
<Button
|
||||
v-if="selectedUser"
|
||||
:text="$t('admin.settings.users.show')"
|
|
@ -4,11 +4,11 @@
|
|||
{{ org.name }}
|
||||
</template>
|
||||
|
||||
<template #titleActions>
|
||||
<template #headerActions>
|
||||
<IconButton
|
||||
v-if="orgPermissions.admin"
|
||||
icon="settings"
|
||||
:to="{ name: org.is_user ? 'user' : 'org-settings' }"
|
||||
:to="{ name: org.is_user ? 'user' : 'org-settings-secrets' }"
|
||||
:title="$t('settings')"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
{{ org.name }}
|
||||
</template>
|
||||
|
||||
<template #titleActions>
|
||||
<template #headerActions>
|
||||
<IconButton
|
||||
v-if="orgPermissions.admin"
|
||||
:to="{ name: org.is_user ? 'user' : 'repo-settings' }"
|
||||
:to="{ name: org.is_user ? 'user' : 'org-settings-secrets' }"
|
||||
:title="$t('settings')"
|
||||
icon="settings"
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<AgentManager
|
||||
:desc="$t('org.settings.agents.desc')"
|
||||
:description="$t('org.settings.agents.desc')"
|
||||
:load-agents="loadAgents"
|
||||
:create-agent="createAgent"
|
||||
:update-agent="updateAgent"
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<Settings
|
||||
:title="$t('registries.registries')"
|
||||
:desc="$t('org.settings.registries.desc')"
|
||||
:description="$t('org.settings.registries.desc')"
|
||||
docs-url="docs/usage/registries"
|
||||
>
|
||||
<template #titleActions>
|
||||
<template #headerActions>
|
||||
<Button
|
||||
v-if="selectedRegistry"
|
||||
:text="$t('registries.show')"
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Settings :title="$t('secrets.secrets')" :desc="$t('org.settings.secrets.desc')" docs-url="docs/usage/secrets">
|
||||
<template #titleActions>
|
||||
<Settings :title="$t('secrets.secrets')" :description="$t('org.settings.secrets.desc')" docs-url="docs/usage/secrets">
|
||||
<template #headerActions>
|
||||
<Button v-if="selectedSecret" :text="$t('secrets.show')" start-icon="back" @click="selectedSecret = undefined" />
|
||||
<Button v-else :text="$t('secrets.add')" start-icon="plus" @click="showAddSecret" />
|
||||
</template>
|
|
@ -11,17 +11,15 @@
|
|||
</span>
|
||||
</template>
|
||||
|
||||
<Tab id="secrets" :title="$t('secrets.secrets')">
|
||||
<OrgSecretsTab />
|
||||
</Tab>
|
||||
<Tab :to="{ name: 'org-settings-secrets' }" :title="$t('secrets.secrets')" />
|
||||
<Tab :to="{ name: 'org-settings-registries' }" :title="$t('registries.registries')" />
|
||||
<Tab
|
||||
v-if="useConfig().userRegisteredAgents"
|
||||
:to="{ name: 'org-settings-agents' }"
|
||||
:title="$t('admin.settings.agents.agents')"
|
||||
/>
|
||||
|
||||
<Tab id="registries" :title="$t('registries.registries')">
|
||||
<OrgRegistriesTab />
|
||||
</Tab>
|
||||
|
||||
<Tab v-if="useConfig().userRegisteredAgents" id="agents" :title="$t('admin.settings.agents.agents')">
|
||||
<OrgAgentsTab />
|
||||
</Tab>
|
||||
<router-view />
|
||||
</Scaffold>
|
||||
</template>
|
||||
|
||||
|
@ -32,9 +30,6 @@ import { useRouter } from 'vue-router';
|
|||
|
||||
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
||||
import Tab from '~/components/layout/scaffold/Tab.vue';
|
||||
import OrgAgentsTab from '~/components/org/settings/OrgAgentsTab.vue';
|
||||
import OrgRegistriesTab from '~/components/org/settings/OrgRegistriesTab.vue';
|
||||
import OrgSecretsTab from '~/components/org/settings/OrgSecretsTab.vue';
|
||||
import useConfig from '~/compositions/useConfig';
|
||||
import { inject } from '~/compositions/useInjectProvide';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
|
@ -1,10 +1,5 @@
|
|||
<template>
|
||||
<Scaffold
|
||||
v-if="repo && repoPermissions && route.meta.repoHeader"
|
||||
v-model:active-tab="activeTab"
|
||||
enable-tabs
|
||||
disable-tab-url-hash-mode
|
||||
>
|
||||
<Scaffold v-if="repo && repoPermissions && route.meta.repoHeader" enable-tabs>
|
||||
<template #title>
|
||||
<span class="flex">
|
||||
<router-link :to="{ name: 'org', params: { orgId: repo.org_id } }" class="hover:underline">{{
|
||||
|
@ -15,7 +10,7 @@
|
|||
{{ repo.name }}
|
||||
</span>
|
||||
</template>
|
||||
<template #titleActions>
|
||||
<template #headerActions>
|
||||
<a v-if="badgeUrl" :href="badgeUrl" target="_blank">
|
||||
<img :src="badgeUrl" />
|
||||
</a>
|
||||
|
@ -43,9 +38,14 @@
|
|||
/>
|
||||
</template>
|
||||
|
||||
<Tab id="activity" :title="$t('repo.activity')" />
|
||||
<Tab id="branches" :title="$t('repo.branches')" />
|
||||
<Tab v-if="repo.pr_enabled && repo.allow_pr" id="pull_requests" :title="$t('repo.pull_requests')" />
|
||||
<Tab :to="{ name: 'repo' }" :title="$t('repo.activity')" />
|
||||
<Tab :to="{ name: 'repo-branches' }" match-children :title="$t('repo.branches')" />
|
||||
<Tab
|
||||
v-if="repo.pr_enabled && repo.allow_pr"
|
||||
:to="{ name: 'repo-pull-requests' }"
|
||||
match-children
|
||||
:title="$t('repo.pull_requests')"
|
||||
/>
|
||||
|
||||
<router-view />
|
||||
</Scaffold>
|
||||
|
@ -132,25 +132,4 @@ watch([repositoryId], () => {
|
|||
});
|
||||
|
||||
const badgeUrl = computed(() => repo.value && `${config.rootPath}/api/badges/${repo.value.id}/status.svg`);
|
||||
|
||||
const activeTab = computed({
|
||||
get() {
|
||||
if (route.name === 'repo-branches' || route.name === 'repo-branch') {
|
||||
return 'branches';
|
||||
}
|
||||
if (route.name === 'repo-pull-requests' || route.name === 'repo-pull-request') {
|
||||
return 'pull_requests';
|
||||
}
|
||||
return 'activity';
|
||||
},
|
||||
set(tab: string) {
|
||||
if (tab === 'branches') {
|
||||
router.push({ name: 'repo-branches' });
|
||||
} else if (tab === 'pull_requests') {
|
||||
router.push({ name: 'repo-pull-requests' });
|
||||
} else {
|
||||
router.push({ name: 'repo' });
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="flex flex-col gap-y-6">
|
||||
<Panel
|
||||
v-for="pipelineConfig in pipelineConfigsDecoded || []"
|
||||
v-for="pipelineConfig in pipelineConfigsDecoded"
|
||||
:key="pipelineConfig.hash"
|
||||
:collapsable="pipelineConfigsDecoded && pipelineConfigsDecoded.length > 1"
|
||||
collapsed-by-default
|
||||
|
@ -14,21 +14,22 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { decode } from 'js-base64';
|
||||
import { computed, inject, type Ref } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
|
||||
import SyntaxHighlight from '~/components/atomic/SyntaxHighlight';
|
||||
import Panel from '~/components/layout/Panel.vue';
|
||||
import type { PipelineConfig } from '~/lib/api/types';
|
||||
import { inject } from '~/compositions/useInjectProvide';
|
||||
|
||||
const pipelineConfigs = inject<Ref<PipelineConfig[]>>('pipeline-configs');
|
||||
const pipelineConfigs = inject('pipeline-configs');
|
||||
if (!pipelineConfigs) {
|
||||
throw new Error('Unexpected: "pipelineConfigs" should be provided at this place');
|
||||
}
|
||||
|
||||
const pipelineConfigsDecoded = computed(() =>
|
||||
pipelineConfigs.value.map((i) => ({
|
||||
const pipelineConfigsDecoded = computed(
|
||||
() =>
|
||||
pipelineConfigs.value?.map((i) => ({
|
||||
...i,
|
||||
data: decode(i.data),
|
||||
})),
|
||||
})) ?? [],
|
||||
);
|
||||
</script>
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
<template>
|
||||
<Scaffold
|
||||
v-if="pipeline && repo"
|
||||
v-model:active-tab="activeTab"
|
||||
enable-tabs
|
||||
disable-tab-url-hash-mode
|
||||
:go-back="goBack"
|
||||
:fluid-content="activeTab === 'tasks'"
|
||||
:fluid-content="route.name === 'repo-pipeline'"
|
||||
full-width-header
|
||||
>
|
||||
<template #title>
|
||||
|
@ -19,7 +17,7 @@
|
|||
</span>
|
||||
</template>
|
||||
|
||||
<template #titleActions>
|
||||
<template #headerActions>
|
||||
<div class="flex md:items-center flex-col gap-2 md:flex-row md:justify-between min-w-0">
|
||||
<div class="flex content-start gap-2 min-w-0">
|
||||
<PipelineStatusIcon :status="pipeline.status" class="flex flex-shrink-0" />
|
||||
|
@ -75,10 +73,10 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<Tab id="tasks" :title="$t('repo.pipeline.tasks')" />
|
||||
<Tab :to="{ name: 'repo-pipeline' }" :title="$t('repo.pipeline.tasks')" />
|
||||
<Tab
|
||||
v-if="pipeline.errors && pipeline.errors.length > 0"
|
||||
id="errors"
|
||||
:to="{ name: 'repo-pipeline-errors' }"
|
||||
icon="attention"
|
||||
:title="
|
||||
pipeline.errors.some((e) => !e.is_warning)
|
||||
|
@ -87,20 +85,24 @@
|
|||
"
|
||||
:icon-class="pipeline.errors.some((e) => !e.is_warning) ? 'text-wp-state-error-100' : 'text-wp-state-warn-100'"
|
||||
/>
|
||||
<Tab id="config" :title="$t('repo.pipeline.config')" />
|
||||
<Tab :to="{ name: 'repo-pipeline-config' }" :title="$t('repo.pipeline.config')" />
|
||||
<Tab
|
||||
v-if="pipeline.changed_files && pipeline.changed_files.length > 0"
|
||||
id="changed-files"
|
||||
:to="{ name: 'repo-pipeline-changed-files' }"
|
||||
:title="$t('repo.pipeline.files', { files: pipeline.changed_files?.length })"
|
||||
/>
|
||||
<Tab v-if="repoPermissions && repoPermissions.push" id="debug" :title="$t('repo.pipeline.debug.title')" />
|
||||
<Tab
|
||||
v-if="repoPermissions && repoPermissions.push"
|
||||
:to="{ name: 'repo-pipeline-debug' }"
|
||||
:title="$t('repo.pipeline.debug.title')"
|
||||
/>
|
||||
|
||||
<router-view />
|
||||
</Scaffold>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, inject, onBeforeUnmount, onMounted, provide, ref, toRef, watch, type Ref } from 'vue';
|
||||
import { computed, inject, onBeforeUnmount, onMounted, ref, toRef, watch, type Ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
|
@ -113,6 +115,7 @@ import PipelineStatusIcon from '~/components/repo/pipeline/PipelineStatusIcon.vu
|
|||
import useApiClient from '~/compositions/useApiClient';
|
||||
import { useAsyncAction } from '~/compositions/useAsyncAction';
|
||||
import { useFavicon } from '~/compositions/useFavicon';
|
||||
import { provide } from '~/compositions/useInjectProvide';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
||||
import usePipeline from '~/compositions/usePipeline';
|
||||
import { useRouteBack } from '~/compositions/useRouteBack';
|
||||
|
@ -206,48 +209,5 @@ onBeforeUnmount(() => {
|
|||
favicon.updateStatus('default');
|
||||
});
|
||||
|
||||
const activeTab = computed({
|
||||
get() {
|
||||
if (route.name === 'repo-pipeline-changed-files') {
|
||||
return 'changed-files';
|
||||
}
|
||||
|
||||
if (route.name === 'repo-pipeline-config') {
|
||||
return 'config';
|
||||
}
|
||||
|
||||
if (route.name === 'repo-pipeline-errors') {
|
||||
return 'errors';
|
||||
}
|
||||
|
||||
if (route.name === 'repo-pipeline-debug' && repoPermissions.value?.push) {
|
||||
return 'debug';
|
||||
}
|
||||
|
||||
return 'tasks';
|
||||
},
|
||||
set(tab: string) {
|
||||
if (tab === 'tasks') {
|
||||
router.replace({ name: 'repo-pipeline' });
|
||||
}
|
||||
|
||||
if (tab === 'changed-files') {
|
||||
router.replace({ name: 'repo-pipeline-changed-files' });
|
||||
}
|
||||
|
||||
if (tab === 'config') {
|
||||
router.replace({ name: 'repo-pipeline-config' });
|
||||
}
|
||||
|
||||
if (tab === 'errors') {
|
||||
router.replace({ name: 'repo-pipeline-errors' });
|
||||
}
|
||||
|
||||
if (tab === 'debug' && repoPermissions.value?.push) {
|
||||
router.replace({ name: 'repo-pipeline-debug' });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const goBack = useRouteBack({ name: 'repo' });
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Settings :title="$t('repo.settings.badge.badge')">
|
||||
<template #titleActions>
|
||||
<template #headerActions>
|
||||
<a v-if="badgeUrl" :href="badgeUrl" target="_blank">
|
||||
<img :src="badgeUrl" />
|
||||
</a>
|
|
@ -1,6 +1,10 @@
|
|||
<template>
|
||||
<Settings :title="$t('repo.settings.crons.crons')" :desc="$t('repo.settings.crons.desc')" docs-url="docs/usage/cron">
|
||||
<template #titleActions>
|
||||
<Settings
|
||||
:title="$t('repo.settings.crons.crons')"
|
||||
:description="$t('repo.settings.crons.desc')"
|
||||
docs-url="docs/usage/cron"
|
||||
>
|
||||
<template #headerActions>
|
||||
<Button
|
||||
v-if="selectedCron"
|
||||
start-icon="back"
|
|
@ -1,26 +1,6 @@
|
|||
<template>
|
||||
<Settings :title="$t('repo.settings.general.general')">
|
||||
<form v-if="repoSettings" class="flex flex-col" @submit.prevent="saveRepoSettings">
|
||||
<InputField
|
||||
docs-url="docs/usage/project-settings#pipeline-path"
|
||||
:label="$t('repo.settings.general.pipeline_path.path')"
|
||||
>
|
||||
<template #default="{ id }">
|
||||
<TextField
|
||||
:id="id"
|
||||
v-model="repoSettings.config_file"
|
||||
:placeholder="$t('repo.settings.general.pipeline_path.default')"
|
||||
/>
|
||||
</template>
|
||||
<template #description>
|
||||
<i18n-t keypath="repo.settings.general.pipeline_path.desc" tag="p" class="text-sm text-wp-text-alt-100">
|
||||
<span class="code-box-inline">{{ $t('repo.settings.general.pipeline_path.desc_path_example') }}</span>
|
||||
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
|
||||
<span class="code-box-inline">/</span>
|
||||
</i18n-t>
|
||||
</template>
|
||||
</InputField>
|
||||
|
||||
<InputField
|
||||
docs-url="docs/usage/project-settings#project-settings-1"
|
||||
:label="$t('repo.settings.general.project')"
|
||||
|
@ -35,11 +15,6 @@
|
|||
:label="$t('repo.settings.general.allow_deploy.allow')"
|
||||
:description="$t('repo.settings.general.allow_deploy.desc')"
|
||||
/>
|
||||
<Checkbox
|
||||
v-model="repoSettings.gated"
|
||||
:label="$t('repo.settings.general.protected.protected')"
|
||||
:description="$t('repo.settings.general.protected.desc')"
|
||||
/>
|
||||
<Checkbox
|
||||
v-model="repoSettings.netrc_only_trusted"
|
||||
:label="$t('repo.settings.general.netrc_only_trusted.netrc_only_trusted')"
|
||||
|
@ -69,6 +44,36 @@
|
|||
/>
|
||||
</InputField>
|
||||
|
||||
<InputField :label="$t('require_approval.require_approval_for')">
|
||||
<RadioField
|
||||
v-model="repoSettings.require_approval"
|
||||
:options="[
|
||||
{
|
||||
value: RepoRequireApproval.None,
|
||||
text: $t('require_approval.none'),
|
||||
description: $t('require_approval.none_desc'),
|
||||
},
|
||||
{
|
||||
value: RepoRequireApproval.Forks,
|
||||
text: $t('require_approval.forks'),
|
||||
},
|
||||
{
|
||||
value: RepoRequireApproval.PullRequests,
|
||||
text: $t('require_approval.pull_requests'),
|
||||
},
|
||||
{
|
||||
value: RepoRequireApproval.AllEvents,
|
||||
text: $t('require_approval.all_events'),
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<template #description>
|
||||
<p class="text-sm">
|
||||
{{ $t('require_approval.desc') }}
|
||||
</p>
|
||||
</template>
|
||||
</InputField>
|
||||
|
||||
<InputField
|
||||
docs-url="docs/usage/project-settings#project-visibility"
|
||||
:label="$t('repo.settings.general.visibility.visibility')"
|
||||
|
@ -87,6 +92,26 @@
|
|||
</div>
|
||||
</InputField>
|
||||
|
||||
<InputField
|
||||
docs-url="docs/usage/project-settings#pipeline-path"
|
||||
:label="$t('repo.settings.general.pipeline_path.path')"
|
||||
>
|
||||
<template #default="{ id }">
|
||||
<TextField
|
||||
:id="id"
|
||||
v-model="repoSettings.config_file"
|
||||
:placeholder="$t('repo.settings.general.pipeline_path.default')"
|
||||
/>
|
||||
</template>
|
||||
<template #description>
|
||||
<i18n-t keypath="repo.settings.general.pipeline_path.desc" tag="p" class="text-sm text-wp-text-alt-100">
|
||||
<span class="code-box-inline">{{ $t('repo.settings.general.pipeline_path.desc_path_example') }}</span>
|
||||
<!-- eslint-disable-next-line @intlify/vue-i18n/no-raw-text -->
|
||||
<span class="code-box-inline">/</span>
|
||||
</i18n-t>
|
||||
</template>
|
||||
</InputField>
|
||||
|
||||
<InputField
|
||||
docs-url="docs/usage/project-settings#cancel-previous-pipelines"
|
||||
:label="$t('repo.settings.general.cancel_prev.cancel')"
|
||||
|
@ -130,7 +155,7 @@ import useApiClient from '~/compositions/useApiClient';
|
|||
import { useAsyncAction } from '~/compositions/useAsyncAction';
|
||||
import useAuthentication from '~/compositions/useAuthentication';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
||||
import { RepoVisibility, WebhookEvents, type Repo, type RepoSettings } from '~/lib/api/types';
|
||||
import { RepoRequireApproval, RepoVisibility, WebhookEvents, type Repo, type RepoSettings } from '~/lib/api/types';
|
||||
import { useRepoStore } from '~/store/repos';
|
||||
|
||||
const apiClient = useApiClient();
|
||||
|
@ -151,7 +176,7 @@ function loadRepoSettings() {
|
|||
config_file: repo.value.config_file,
|
||||
timeout: repo.value.timeout,
|
||||
visibility: repo.value.visibility,
|
||||
gated: repo.value.gated,
|
||||
require_approval: repo.value.require_approval,
|
||||
trusted: repo.value.trusted,
|
||||
allow_pr: repo.value.allow_pr,
|
||||
allow_deploy: repo.value.allow_deploy,
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Settings :title="$t('registries.credentials')" :desc="$t('registries.desc')" docs-url="docs/usage/registries">
|
||||
<template #titleActions>
|
||||
<Settings :title="$t('registries.credentials')" :description="$t('registries.desc')" docs-url="docs/usage/registries">
|
||||
<template #headerActions>
|
||||
<Button
|
||||
v-if="selectedRegistry"
|
||||
:text="$t('registries.show')"
|
|
@ -16,24 +16,14 @@
|
|||
</span>
|
||||
</template>
|
||||
|
||||
<Tab id="general" :title="$t('repo.settings.general.general')">
|
||||
<GeneralTab />
|
||||
</Tab>
|
||||
<Tab id="secrets" :title="$t('secrets.secrets')">
|
||||
<SecretsTab />
|
||||
</Tab>
|
||||
<Tab id="registries" :title="$t('registries.registries')">
|
||||
<RegistriesTab />
|
||||
</Tab>
|
||||
<Tab id="crons" :title="$t('repo.settings.crons.crons')">
|
||||
<CronTab />
|
||||
</Tab>
|
||||
<Tab id="badge" :title="$t('repo.settings.badge.badge')">
|
||||
<BadgeTab />
|
||||
</Tab>
|
||||
<Tab id="actions" :title="$t('repo.settings.actions.actions')">
|
||||
<ActionsTab />
|
||||
</Tab>
|
||||
<Tab :to="{ name: 'repo-settings' }" :title="$t('repo.settings.general.general')" />
|
||||
<Tab :to="{ name: 'repo-settings-secrets' }" :title="$t('secrets.secrets')" />
|
||||
<Tab :to="{ name: 'repo-settings-registries' }" :title="$t('registries.registries')" />
|
||||
<Tab :to="{ name: 'repo-settings-crons' }" :title="$t('repo.settings.crons.crons')" />
|
||||
<Tab :to="{ name: 'repo-settings-badge' }" :title="$t('repo.settings.badge.badge')" />
|
||||
<Tab :to="{ name: 'repo-settings-actions' }" :title="$t('repo.settings.actions.actions')" />
|
||||
|
||||
<router-view />
|
||||
</Scaffold>
|
||||
</template>
|
||||
|
||||
|
@ -44,12 +34,6 @@ import { useRouter } from 'vue-router';
|
|||
|
||||
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
||||
import Tab from '~/components/layout/scaffold/Tab.vue';
|
||||
import ActionsTab from '~/components/repo/settings/ActionsTab.vue';
|
||||
import BadgeTab from '~/components/repo/settings/BadgeTab.vue';
|
||||
import CronTab from '~/components/repo/settings/CronTab.vue';
|
||||
import GeneralTab from '~/components/repo/settings/GeneralTab.vue';
|
||||
import RegistriesTab from '~/components/repo/settings/RegistriesTab.vue';
|
||||
import SecretsTab from '~/components/repo/settings/SecretsTab.vue';
|
||||
import useNotifications from '~/compositions/useNotifications';
|
||||
import { useRouteBack } from '~/compositions/useRouteBack';
|
||||
import type { Repo, RepoPermissions } from '~/lib/api/types';
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Settings :title="$t('secrets.secrets')" :desc="$t('secrets.desc')" docs-url="docs/usage/secrets">
|
||||
<template #titleActions>
|
||||
<Settings :title="$t('secrets.secrets')" :description="$t('secrets.desc')" docs-url="docs/usage/secrets">
|
||||
<template #headerActions>
|
||||
<Button v-if="selectedSecret" :text="$t('secrets.show')" start-icon="back" @click="selectedSecret = undefined" />
|
||||
<Button v-else :text="$t('secrets.add')" start-icon="plus" @click="showAddSecret" />
|
||||
</template>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<AgentManager
|
||||
:desc="$t('user.settings.agents.desc')"
|
||||
:description="$t('user.settings.agents.desc')"
|
||||
:load-agents="loadAgents"
|
||||
:create-agent="createAgent"
|
||||
:update-agent="updateAgent"
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<Settings :title="$t('user.settings.cli_and_api.cli_and_api')" :desc="$t('user.settings.cli_and_api.desc')">
|
||||
<Settings :title="$t('user.settings.cli_and_api.cli_and_api')" :description="$t('user.settings.cli_and_api.desc')">
|
||||
<InputField :label="$t('user.settings.cli_and_api.cli_usage')">
|
||||
<template #titleActions>
|
||||
<template #headerActions>
|
||||
<a :href="cliDownload" target="_blank" class="ml-4 text-wp-link-100 hover:text-wp-link-200">{{
|
||||
$t('user.settings.cli_and_api.download_cli')
|
||||
}}</a>
|
||||
|
@ -10,14 +10,14 @@
|
|||
</InputField>
|
||||
|
||||
<InputField :label="$t('user.settings.cli_and_api.token')">
|
||||
<template #titleActions>
|
||||
<template #headerActions>
|
||||
<Button class="ml-auto" :text="$t('user.settings.cli_and_api.reset_token')" @click="resetToken" />
|
||||
</template>
|
||||
<pre class="code-box">{{ token }}</pre>
|
||||
</InputField>
|
||||
|
||||
<InputField :label="$t('user.settings.cli_and_api.api_usage')">
|
||||
<template #titleActions>
|
||||
<template #headerActions>
|
||||
<a
|
||||
v-if="enableSwagger"
|
||||
:href="`${address}/swagger/index.html`"
|
|
@ -1,22 +1,18 @@
|
|||
<template>
|
||||
<Panel>
|
||||
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-wp-background-100">
|
||||
<div class="ml-2">
|
||||
<h1 class="text-xl text-wp-text-100">{{ $t('registries.registries') }}</h1>
|
||||
<p class="text-sm text-wp-text-alt-100">
|
||||
{{ $t('user.settings.registries.desc') }}
|
||||
<DocsLink :topic="$t('registries.registries')" url="docs/usage/registries" />
|
||||
</p>
|
||||
</div>
|
||||
<Settings
|
||||
:title="$t('registries.registries')"
|
||||
:description="$t('user.settings.registries.desc')"
|
||||
docs-url="docs/usage/registries"
|
||||
>
|
||||
<template #headerActions>
|
||||
<Button
|
||||
v-if="selectedRegistry"
|
||||
class="ml-auto"
|
||||
:text="$t('registries.show')"
|
||||
start-icon="back"
|
||||
@click="selectedRegistry = undefined"
|
||||
/>
|
||||
<Button v-else class="ml-auto" :text="$t('registries.add')" start-icon="plus" @click="showAddRegistry" />
|
||||
</div>
|
||||
<Button v-else :text="$t('registries.add')" start-icon="plus" @click="showAddRegistry" />
|
||||
</template>
|
||||
|
||||
<RegistryList
|
||||
v-if="!selectedRegistry"
|
||||
|
@ -33,7 +29,7 @@
|
|||
@save="createRegistry"
|
||||
@cancel="selectedRegistry = undefined"
|
||||
/>
|
||||
</Panel>
|
||||
</Settings>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -42,8 +38,7 @@ import { computed, ref } from 'vue';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
import DocsLink from '~/components/atomic/DocsLink.vue';
|
||||
import Panel from '~/components/layout/Panel.vue';
|
||||
import Settings from '~/components/layout/Settings.vue';
|
||||
import RegistryEdit from '~/components/registry/RegistryEdit.vue';
|
||||
import RegistryList from '~/components/registry/RegistryList.vue';
|
||||
import useApiClient from '~/compositions/useApiClient';
|
|
@ -1,22 +1,13 @@
|
|||
<template>
|
||||
<Panel>
|
||||
<div class="flex flex-row border-b mb-4 pb-4 items-center dark:border-wp-background-100">
|
||||
<div class="ml-2">
|
||||
<h1 class="text-xl text-wp-text-100">{{ $t('secrets.secrets') }}</h1>
|
||||
<p class="text-sm text-wp-text-alt-100">
|
||||
{{ $t('user.settings.secrets.desc') }}
|
||||
<DocsLink :topic="$t('secrets.secrets')" url="docs/usage/secrets" />
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
v-if="selectedSecret"
|
||||
class="ml-auto"
|
||||
:text="$t('secrets.show')"
|
||||
start-icon="back"
|
||||
@click="selectedSecret = undefined"
|
||||
/>
|
||||
<Button v-else class="ml-auto" :text="$t('secrets.add')" start-icon="plus" @click="showAddSecret" />
|
||||
</div>
|
||||
<Settings
|
||||
:title="$t('secrets.secrets')"
|
||||
:description="$t('user.settings.secrets.desc')"
|
||||
docs-url="docs/usage/secrets"
|
||||
>
|
||||
<template #headerActions>
|
||||
<Button v-if="selectedSecret" :text="$t('secrets.show')" start-icon="back" @click="selectedSecret = undefined" />
|
||||
<Button v-else :text="$t('secrets.add')" start-icon="plus" @click="showAddSecret" />
|
||||
</template>
|
||||
|
||||
<SecretList
|
||||
v-if="!selectedSecret"
|
||||
|
@ -33,7 +24,7 @@
|
|||
@save="createSecret"
|
||||
@cancel="selectedSecret = undefined"
|
||||
/>
|
||||
</Panel>
|
||||
</Settings>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
@ -42,8 +33,7 @@ import { computed, ref } from 'vue';
|
|||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
import DocsLink from '~/components/atomic/DocsLink.vue';
|
||||
import Panel from '~/components/layout/Panel.vue';
|
||||
import Settings from '~/components/layout/Settings.vue';
|
||||
import SecretEdit from '~/components/secrets/SecretEdit.vue';
|
||||
import SecretList from '~/components/secrets/SecretList.vue';
|
||||
import useApiClient from '~/compositions/useApiClient';
|
27
web/src/views/user/UserWrapper.vue
Normal file
27
web/src/views/user/UserWrapper.vue
Normal file
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<Scaffold enable-tabs>
|
||||
<template #title>{{ $t('user.settings.settings') }}</template>
|
||||
<template #headerActions><Button :text="$t('logout')" :to="`${address}/logout`" /></template>
|
||||
|
||||
<Tab :to="{ name: 'user' }" :title="$t('user.settings.general.general')" />
|
||||
<Tab :to="{ name: 'user-secrets' }" :title="$t('secrets.secrets')" />
|
||||
<Tab :to="{ name: 'user-registries' }" :title="$t('registries.registries')" />
|
||||
<Tab :to="{ name: 'user-cli-and-api' }" :title="$t('user.settings.cli_and_api.cli_and_api')" />
|
||||
<Tab
|
||||
v-if="useConfig().userRegisteredAgents"
|
||||
:to="{ name: 'user-agents' }"
|
||||
:title="$t('admin.settings.agents.agents')"
|
||||
/>
|
||||
|
||||
<router-view />
|
||||
</Scaffold>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Button from '~/components/atomic/Button.vue';
|
||||
import Scaffold from '~/components/layout/scaffold/Scaffold.vue';
|
||||
import Tab from '~/components/layout/scaffold/Tab.vue';
|
||||
import useConfig from '~/compositions/useConfig';
|
||||
|
||||
const address = `${window.location.protocol}//${window.location.host}${useConfig().rootPath}`; // port is included in location.host
|
||||
</script>
|
|
@ -14,6 +14,27 @@
|
|||
|
||||
package woodpecker
|
||||
|
||||
type ApprovalMode string
|
||||
|
||||
var (
|
||||
RequireApprovalNone ApprovalMode = "none" // require approval for no events
|
||||
RequireApprovalForks ApprovalMode = "forks" // require approval for PRs from forks
|
||||
RequireApprovalPullRequests ApprovalMode = "pull_requests" // require approval for all PRs (default)
|
||||
RequireApprovalAllEvents ApprovalMode = "all_events" // require approval for all events
|
||||
)
|
||||
|
||||
func (mode ApprovalMode) Valid() bool {
|
||||
switch mode {
|
||||
case RequireApprovalNone,
|
||||
RequireApprovalForks,
|
||||
RequireApprovalPullRequests,
|
||||
RequireApprovalAllEvents:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type (
|
||||
// User represents a user account.
|
||||
User struct {
|
||||
|
@ -41,7 +62,8 @@ type (
|
|||
Visibility string `json:"visibility"`
|
||||
IsSCMPrivate bool `json:"private"`
|
||||
IsTrusted bool `json:"trusted"`
|
||||
IsGated bool `json:"gated"`
|
||||
IsGated bool `json:"gated,omitempty"` // TODO: remove in next major release
|
||||
RequireApproval ApprovalMode `json:"require_approval"`
|
||||
IsActive bool `json:"active"`
|
||||
AllowPullRequests bool `json:"allow_pr"`
|
||||
Config string `json:"config_file"`
|
||||
|
@ -53,7 +75,8 @@ type (
|
|||
RepoPatch struct {
|
||||
Config *string `json:"config_file,omitempty"`
|
||||
IsTrusted *bool `json:"trusted,omitempty"`
|
||||
IsGated *bool `json:"gated,omitempty"`
|
||||
IsGated *bool `json:"gated,omitempty"` // TODO: remove in next major release
|
||||
RequireApproval *ApprovalMode `json:"require_approval,omitempty"`
|
||||
Timeout *int64 `json:"timeout,omitempty"`
|
||||
Visibility *string `json:"visibility"`
|
||||
AllowPull *bool `json:"allow_pr,omitempty"`
|
||||
|
|
Loading…
Reference in a new issue