mirror of
https://github.com/go-gitea/gitea.git
synced 2025-07-21 18:54:39 +02:00
Merge branch 'go-gitea:main' into main
This commit is contained in:
commit
e11a3398b6
@ -81,6 +81,10 @@ var microcmdUserCreate = &cli.Command{
|
|||||||
Name: "restricted",
|
Name: "restricted",
|
||||||
Usage: "Make a restricted user account",
|
Usage: "Make a restricted user account",
|
||||||
},
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "fullname",
|
||||||
|
Usage: `The full, human-readable name of the user`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,6 +195,7 @@ func runCreateUser(c *cli.Context) error {
|
|||||||
Passwd: password,
|
Passwd: password,
|
||||||
MustChangePassword: mustChangePassword,
|
MustChangePassword: mustChangePassword,
|
||||||
Visibility: visibility,
|
Visibility: visibility,
|
||||||
|
FullName: c.String("fullname"),
|
||||||
}
|
}
|
||||||
|
|
||||||
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
overwriteDefault := &user_model.CreateUserOverwriteOptions{
|
||||||
|
@ -50,17 +50,17 @@ func TestAdminUserCreate(t *testing.T) {
|
|||||||
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false"))
|
assert.Equal(t, check{IsAdmin: false, MustChangePassword: false}, createCheck("u5", "--must-change-password=false"))
|
||||||
})
|
})
|
||||||
|
|
||||||
createUser := func(name, args string) error {
|
createUser := func(name string, args ...string) error {
|
||||||
return app.Run(strings.Fields(fmt.Sprintf("./gitea admin user create --username %s --email %s@gitea.local %s", name, name, args)))
|
return app.Run(append([]string{"./gitea", "admin", "user", "create", "--username", name, "--email", name + "@gitea.local"}, args...))
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("UserType", func(t *testing.T) {
|
t.Run("UserType", func(t *testing.T) {
|
||||||
reset()
|
reset()
|
||||||
assert.ErrorContains(t, createUser("u", "--user-type invalid"), "invalid user type")
|
assert.ErrorContains(t, createUser("u", "--user-type", "invalid"), "invalid user type")
|
||||||
assert.ErrorContains(t, createUser("u", "--user-type bot --password 123"), "can only be set for individual users")
|
assert.ErrorContains(t, createUser("u", "--user-type", "bot", "--password", "123"), "can only be set for individual users")
|
||||||
assert.ErrorContains(t, createUser("u", "--user-type bot --must-change-password"), "can only be set for individual users")
|
assert.ErrorContains(t, createUser("u", "--user-type", "bot", "--must-change-password"), "can only be set for individual users")
|
||||||
|
|
||||||
assert.NoError(t, createUser("u", "--user-type bot"))
|
assert.NoError(t, createUser("u", "--user-type", "bot"))
|
||||||
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "u"})
|
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "u"})
|
||||||
assert.Equal(t, user_model.UserTypeBot, u.Type)
|
assert.Equal(t, user_model.UserTypeBot, u.Type)
|
||||||
assert.Empty(t, u.Passwd)
|
assert.Empty(t, u.Passwd)
|
||||||
@ -75,7 +75,7 @@ func TestAdminUserCreate(t *testing.T) {
|
|||||||
|
|
||||||
// using "--access-token" only means "all" access
|
// using "--access-token" only means "all" access
|
||||||
reset()
|
reset()
|
||||||
assert.NoError(t, createUser("u", "--random-password --access-token"))
|
assert.NoError(t, createUser("u", "--random-password", "--access-token"))
|
||||||
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
|
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
|
||||||
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
|
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
accessToken := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"})
|
accessToken := unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "gitea-admin"})
|
||||||
@ -85,7 +85,7 @@ func TestAdminUserCreate(t *testing.T) {
|
|||||||
|
|
||||||
// using "--access-token" with name & scopes
|
// using "--access-token" with name & scopes
|
||||||
reset()
|
reset()
|
||||||
assert.NoError(t, createUser("u", "--random-password --access-token --access-token-name new-token-name --access-token-scopes read:issue,read:user"))
|
assert.NoError(t, createUser("u", "--random-password", "--access-token", "--access-token-name", "new-token-name", "--access-token-scopes", "read:issue,read:user"))
|
||||||
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
|
assert.Equal(t, 1, unittest.GetCount(t, &user_model.User{}))
|
||||||
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
|
assert.Equal(t, 1, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
accessToken = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"})
|
accessToken = unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{Name: "new-token-name"})
|
||||||
@ -98,23 +98,38 @@ func TestAdminUserCreate(t *testing.T) {
|
|||||||
|
|
||||||
// using "--access-token-name" without "--access-token"
|
// using "--access-token-name" without "--access-token"
|
||||||
reset()
|
reset()
|
||||||
err = createUser("u", "--random-password --access-token-name new-token-name")
|
err = createUser("u", "--random-password", "--access-token-name", "new-token-name")
|
||||||
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
|
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
|
||||||
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
|
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
|
||||||
|
|
||||||
// using "--access-token-scopes" without "--access-token"
|
// using "--access-token-scopes" without "--access-token"
|
||||||
reset()
|
reset()
|
||||||
err = createUser("u", "--random-password --access-token-scopes read:issue")
|
err = createUser("u", "--random-password", "--access-token-scopes", "read:issue")
|
||||||
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
|
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
|
||||||
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
|
assert.ErrorContains(t, err, "access-token-name and access-token-scopes flags are only valid when access-token flag is set")
|
||||||
|
|
||||||
// empty permission
|
// empty permission
|
||||||
reset()
|
reset()
|
||||||
err = createUser("u", "--random-password --access-token --access-token-scopes public-only")
|
err = createUser("u", "--random-password", "--access-token", "--access-token-scopes", "public-only")
|
||||||
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
|
assert.Equal(t, 0, unittest.GetCount(t, &user_model.User{}))
|
||||||
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
assert.Equal(t, 0, unittest.GetCount(t, &auth_model.AccessToken{}))
|
||||||
assert.ErrorContains(t, err, "access token does not have any permission")
|
assert.ErrorContains(t, err, "access token does not have any permission")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("UserFields", func(t *testing.T) {
|
||||||
|
reset()
|
||||||
|
assert.NoError(t, createUser("u-FullNameWithSpace", "--random-password", "--fullname", "First O'Middle Last"))
|
||||||
|
unittest.AssertExistsAndLoadBean(t, &user_model.User{
|
||||||
|
Name: "u-FullNameWithSpace",
|
||||||
|
LowerName: "u-fullnamewithspace",
|
||||||
|
FullName: "First O'Middle Last",
|
||||||
|
Email: "u-FullNameWithSpace@gitea.local",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NoError(t, createUser("u-FullNameEmpty", "--random-password", "--fullname", ""))
|
||||||
|
u := unittest.AssertExistsAndLoadBean(t, &user_model.User{LowerName: "u-fullnameempty"})
|
||||||
|
assert.Empty(t, u.FullName)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -59,27 +59,16 @@ RUN_USER = ; git
|
|||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;
|
;;
|
||||||
;; The protocol the server listens on. One of 'http', 'https', 'http+unix', 'fcgi' or 'fcgi+unix'. Defaults to 'http'
|
;; The protocol the server listens on. One of "http", "https", "http+unix", "fcgi" or "fcgi+unix".
|
||||||
;; Note: Value must be lowercase.
|
|
||||||
;PROTOCOL = http
|
;PROTOCOL = http
|
||||||
;;
|
;;
|
||||||
;; Expect PROXY protocol headers on connections
|
;; Set the domain for the server.
|
||||||
;USE_PROXY_PROTOCOL = false
|
;; Most users should set it to the real website domain of their Gitea instance.
|
||||||
;;
|
|
||||||
;; Use PROXY protocol in TLS Bridging mode
|
|
||||||
;PROXY_PROTOCOL_TLS_BRIDGING = false
|
|
||||||
;;
|
|
||||||
; Timeout to wait for PROXY protocol header (set to 0 to have no timeout)
|
|
||||||
;PROXY_PROTOCOL_HEADER_TIMEOUT=5s
|
|
||||||
;;
|
|
||||||
; Accept PROXY protocol headers with UNKNOWN type
|
|
||||||
;PROXY_PROTOCOL_ACCEPT_UNKNOWN=false
|
|
||||||
;;
|
|
||||||
;; Set the domain for the server
|
|
||||||
;DOMAIN = localhost
|
;DOMAIN = localhost
|
||||||
;;
|
;;
|
||||||
;; The AppURL used by Gitea to generate absolute links, defaults to "{PROTOCOL}://{DOMAIN}:{HTTP_PORT}/".
|
;; The AppURL used by Gitea to generate absolute links, defaults to "{PROTOCOL}://{DOMAIN}:{HTTP_PORT}/".
|
||||||
;; Most users should set it to the real website URL of their Gitea instance.
|
;; Most users should set it to the real website URL of their Gitea instance when there is a reverse proxy.
|
||||||
|
;; When it is empty, Gitea will use HTTP "Host" header to generate ROOT_URL, and fall back to the default one if no "Host" header.
|
||||||
;ROOT_URL =
|
;ROOT_URL =
|
||||||
;;
|
;;
|
||||||
;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy.
|
;; For development purpose only. It makes Gitea handle sub-path ("/sub-path/owner/repo/...") directly when debugging without a reverse proxy.
|
||||||
@ -90,13 +79,25 @@ RUN_USER = ; git
|
|||||||
;STATIC_URL_PREFIX =
|
;STATIC_URL_PREFIX =
|
||||||
;;
|
;;
|
||||||
;; The address to listen on. Either a IPv4/IPv6 address or the path to a unix socket.
|
;; The address to listen on. Either a IPv4/IPv6 address or the path to a unix socket.
|
||||||
;; If PROTOCOL is set to `http+unix` or `fcgi+unix`, this should be the name of the Unix socket file to use.
|
;; If PROTOCOL is set to "http+unix" or "fcgi+unix", this should be the name of the Unix socket file to use.
|
||||||
;; Relative paths will be made absolute against the _`AppWorkPath`_.
|
;; Relative paths will be made absolute against the _`AppWorkPath`_.
|
||||||
;HTTP_ADDR = 0.0.0.0
|
;HTTP_ADDR = 0.0.0.0
|
||||||
;;
|
;;
|
||||||
;; The port to listen on. Leave empty when using a unix socket.
|
;; The port to listen on for "http" or "https" protocol. Leave empty when using a unix socket.
|
||||||
;HTTP_PORT = 3000
|
;HTTP_PORT = 3000
|
||||||
;;
|
;;
|
||||||
|
;; Expect PROXY protocol headers on connections
|
||||||
|
;USE_PROXY_PROTOCOL = false
|
||||||
|
;;
|
||||||
|
;; Use PROXY protocol in TLS Bridging mode
|
||||||
|
;PROXY_PROTOCOL_TLS_BRIDGING = false
|
||||||
|
;;
|
||||||
|
;; Timeout to wait for PROXY protocol header (set to 0 to have no timeout)
|
||||||
|
;PROXY_PROTOCOL_HEADER_TIMEOUT = 5s
|
||||||
|
;;
|
||||||
|
;; Accept PROXY protocol headers with UNKNOWN type
|
||||||
|
;PROXY_PROTOCOL_ACCEPT_UNKNOWN = false
|
||||||
|
;;
|
||||||
;; If REDIRECT_OTHER_PORT is true, and PROTOCOL is set to https an http server
|
;; If REDIRECT_OTHER_PORT is true, and PROTOCOL is set to https an http server
|
||||||
;; will be started on PORT_TO_REDIRECT and it will redirect plain, non-secure http requests to the main
|
;; will be started on PORT_TO_REDIRECT and it will redirect plain, non-secure http requests to the main
|
||||||
;; ROOT_URL. Defaults are false for REDIRECT_OTHER_PORT and 80 for
|
;; ROOT_URL. Defaults are false for REDIRECT_OTHER_PORT and 80 for
|
||||||
|
2
go.mod
2
go.mod
@ -120,7 +120,7 @@ require (
|
|||||||
gitlab.com/gitlab-org/api/client-go v0.126.0
|
gitlab.com/gitlab-org/api/client-go v0.126.0
|
||||||
golang.org/x/crypto v0.36.0
|
golang.org/x/crypto v0.36.0
|
||||||
golang.org/x/image v0.25.0
|
golang.org/x/image v0.25.0
|
||||||
golang.org/x/net v0.37.0
|
golang.org/x/net v0.38.0
|
||||||
golang.org/x/oauth2 v0.28.0
|
golang.org/x/oauth2 v0.28.0
|
||||||
golang.org/x/sync v0.12.0
|
golang.org/x/sync v0.12.0
|
||||||
golang.org/x/sys v0.31.0
|
golang.org/x/sys v0.31.0
|
||||||
|
4
go.sum
4
go.sum
@ -875,8 +875,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
|||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc=
|
||||||
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models/shared/types"
|
"code.gitea.io/gitea/models/shared/types"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
"code.gitea.io/gitea/modules/translation"
|
"code.gitea.io/gitea/modules/translation"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
@ -123,8 +124,15 @@ func (r *ActionRunner) IsOnline() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Editable checks if the runner is editable by the user
|
// EditableInContext checks if the runner is editable by the "context" owner/repo
|
||||||
func (r *ActionRunner) Editable(ownerID, repoID int64) bool {
|
// ownerID == 0 and repoID == 0 means "admin" context, any runner including global runners could be edited
|
||||||
|
// ownerID == 0 and repoID != 0 means "repo" context, any runner belonging to the given repo could be edited
|
||||||
|
// ownerID != 0 and repoID == 0 means "owner(org/user)" context, any runner belonging to the given user/org could be edited
|
||||||
|
// ownerID != 0 and repoID != 0 means "owner" OR "repo" context, legacy behavior, but we should forbid using it
|
||||||
|
func (r *ActionRunner) EditableInContext(ownerID, repoID int64) bool {
|
||||||
|
if ownerID != 0 && repoID != 0 {
|
||||||
|
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||||
|
}
|
||||||
if ownerID == 0 && repoID == 0 {
|
if ownerID == 0 && repoID == 0 {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -168,6 +176,12 @@ func init() {
|
|||||||
db.RegisterModel(&ActionRunner{})
|
db.RegisterModel(&ActionRunner{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FindRunnerOptions
|
||||||
|
// ownerID == 0 and repoID == 0 means any runner including global runners
|
||||||
|
// repoID != 0 and WithAvailable == false means any runner for the given repo
|
||||||
|
// repoID != 0 and WithAvailable == true means any runner for the given repo, parent user/org, and global runners
|
||||||
|
// ownerID != 0 and repoID == 0 and WithAvailable == false means any runner for the given user/org
|
||||||
|
// ownerID != 0 and repoID == 0 and WithAvailable == true means any runner for the given user/org and global runners
|
||||||
type FindRunnerOptions struct {
|
type FindRunnerOptions struct {
|
||||||
db.ListOptions
|
db.ListOptions
|
||||||
IDs []int64
|
IDs []int64
|
||||||
|
40
models/fixtures/action_runner.yml
Normal file
40
models/fixtures/action_runner.yml
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
-
|
||||||
|
id: 34346
|
||||||
|
name: runner_to_be_deleted-user
|
||||||
|
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF18
|
||||||
|
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF18
|
||||||
|
version: "1.0.0"
|
||||||
|
owner_id: 1
|
||||||
|
repo_id: 0
|
||||||
|
description: "This runner is going to be deleted"
|
||||||
|
agent_labels: '["runner_to_be_deleted","linux"]'
|
||||||
|
-
|
||||||
|
id: 34347
|
||||||
|
name: runner_to_be_deleted-org
|
||||||
|
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF19
|
||||||
|
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF19
|
||||||
|
version: "1.0.0"
|
||||||
|
owner_id: 3
|
||||||
|
repo_id: 0
|
||||||
|
description: "This runner is going to be deleted"
|
||||||
|
agent_labels: '["runner_to_be_deleted","linux"]'
|
||||||
|
-
|
||||||
|
id: 34348
|
||||||
|
name: runner_to_be_deleted-repo1
|
||||||
|
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF20
|
||||||
|
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF20
|
||||||
|
version: "1.0.0"
|
||||||
|
owner_id: 0
|
||||||
|
repo_id: 1
|
||||||
|
description: "This runner is going to be deleted"
|
||||||
|
agent_labels: '["runner_to_be_deleted","linux"]'
|
||||||
|
-
|
||||||
|
id: 34349
|
||||||
|
name: runner_to_be_deleted
|
||||||
|
uuid: 3EF231BD-FBB7-4E4B-9602-E6F28363EF17
|
||||||
|
token_hash: 3EF231BD-FBB7-4E4B-9602-E6F28363EF17
|
||||||
|
version: "1.0.0"
|
||||||
|
owner_id: 0
|
||||||
|
repo_id: 0
|
||||||
|
description: "This runner is going to be deleted"
|
||||||
|
agent_labels: '["runner_to_be_deleted","linux"]'
|
@ -279,9 +279,7 @@ func (opts *PackageSearchOptions) configureOrderBy(e db.Engine) {
|
|||||||
default:
|
default:
|
||||||
e.Desc("package_version.created_unix")
|
e.Desc("package_version.created_unix")
|
||||||
}
|
}
|
||||||
|
e.Desc("package_version.id") // Sort by id for stable order with duplicates in the other field
|
||||||
// Sort by id for stable order with duplicates in the other field
|
|
||||||
e.Asc("package_version.id")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchVersions gets all versions of packages matching the search options
|
// SearchVersions gets all versions of packages matching the search options
|
||||||
|
@ -70,11 +70,16 @@ func GuessCurrentHostURL(ctx context.Context) string {
|
|||||||
// 1. The reverse proxy is configured correctly, it passes "X-Forwarded-Proto/Host" headers. Perfect, Gitea can handle it correctly.
|
// 1. The reverse proxy is configured correctly, it passes "X-Forwarded-Proto/Host" headers. Perfect, Gitea can handle it correctly.
|
||||||
// 2. The reverse proxy is not configured correctly, doesn't pass "X-Forwarded-Proto/Host" headers, eg: only one "proxy_pass http://gitea:3000" in Nginx.
|
// 2. The reverse proxy is not configured correctly, doesn't pass "X-Forwarded-Proto/Host" headers, eg: only one "proxy_pass http://gitea:3000" in Nginx.
|
||||||
// 3. There is no reverse proxy.
|
// 3. There is no reverse proxy.
|
||||||
// Without an extra config option, Gitea is impossible to distinguish between case 2 and case 3,
|
// Without more information, Gitea is impossible to distinguish between case 2 and case 3, then case 2 would result in
|
||||||
// then case 2 would result in wrong guess like guessed AppURL becomes "http://gitea:3000/", which is not accessible by end users.
|
// wrong guess like guessed AppURL becomes "http://gitea:3000/" behind a "https" reverse proxy, which is not accessible by end users.
|
||||||
// So in the future maybe it should introduce a new config option, to let site admin decide how to guess the AppURL.
|
// So we introduced "UseHostHeader" option, it could be enabled by setting "ROOT_URL" to empty
|
||||||
reqScheme := getRequestScheme(req)
|
reqScheme := getRequestScheme(req)
|
||||||
if reqScheme == "" {
|
if reqScheme == "" {
|
||||||
|
// if no reverse proxy header, try to use "Host" header for absolute URL
|
||||||
|
if setting.UseHostHeader && req.Host != "" {
|
||||||
|
return util.Iif(req.TLS == nil, "http://", "https://") + req.Host
|
||||||
|
}
|
||||||
|
// fall back to default AppURL
|
||||||
return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/")
|
return strings.TrimSuffix(setting.AppURL, setting.AppSubURL+"/")
|
||||||
}
|
}
|
||||||
// X-Forwarded-Host has many problems: non-standard, not well-defined (X-Forwarded-Port or not), conflicts with Host header.
|
// X-Forwarded-Host has many problems: non-standard, not well-defined (X-Forwarded-Port or not), conflicts with Host header.
|
||||||
|
@ -5,6 +5,7 @@ package httplib
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -39,6 +40,25 @@ func TestIsRelativeURL(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGuessCurrentHostURL(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.AppURL, "http://cfg-host/sub/")()
|
||||||
|
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
||||||
|
defer test.MockVariableValue(&setting.UseHostHeader, false)()
|
||||||
|
|
||||||
|
ctx := t.Context()
|
||||||
|
assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx))
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{Host: "localhost:3000"})
|
||||||
|
assert.Equal(t, "http://cfg-host", GuessCurrentHostURL(ctx))
|
||||||
|
|
||||||
|
defer test.MockVariableValue(&setting.UseHostHeader, true)()
|
||||||
|
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{Host: "http-host:3000"})
|
||||||
|
assert.Equal(t, "http://http-host:3000", GuessCurrentHostURL(ctx))
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, RequestContextKey, &http.Request{Host: "http-host", TLS: &tls.ConnectionState{}})
|
||||||
|
assert.Equal(t, "https://http-host", GuessCurrentHostURL(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
func TestMakeAbsoluteURL(t *testing.T) {
|
func TestMakeAbsoluteURL(t *testing.T) {
|
||||||
defer test.MockVariableValue(&setting.Protocol, "http")()
|
defer test.MockVariableValue(&setting.Protocol, "http")()
|
||||||
defer test.MockVariableValue(&setting.AppURL, "http://cfg-host/sub/")()
|
defer test.MockVariableValue(&setting.AppURL, "http://cfg-host/sub/")()
|
||||||
|
@ -71,7 +71,8 @@ var globalVars = sync.OnceValue(func() *globalVarsType {
|
|||||||
// it is still accepted by the CommonMark specification, as well as the HTML5 spec:
|
// it is still accepted by the CommonMark specification, as well as the HTML5 spec:
|
||||||
// http://spec.commonmark.org/0.28/#email-address
|
// http://spec.commonmark.org/0.28/#email-address
|
||||||
// https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail)
|
// https://html.spec.whatwg.org/multipage/input.html#e-mail-state-(type%3Demail)
|
||||||
v.emailRegex = regexp.MustCompile("(?:\\s|^|\\(|\\[)([a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)(?:\\s|$|\\)|\\]|;|,|\\?|!|\\.(\\s|$))")
|
// At the moment, we use stricter rule for rendering purpose: only allow the "name" part starting after the word boundary
|
||||||
|
v.emailRegex = regexp.MustCompile(`\b([-\w.!#$%&'*+/=?^{|}~]*@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9]{2,}(?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+)\b`)
|
||||||
|
|
||||||
// emojiShortCodeRegex find emoji by alias like :smile:
|
// emojiShortCodeRegex find emoji by alias like :smile:
|
||||||
v.emojiShortCodeRegex = regexp.MustCompile(`:[-+\w]+:`)
|
v.emojiShortCodeRegex = regexp.MustCompile(`:[-+\w]+:`)
|
||||||
|
@ -3,7 +3,11 @@
|
|||||||
|
|
||||||
package markup
|
package markup
|
||||||
|
|
||||||
import "golang.org/x/net/html"
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/html"
|
||||||
|
)
|
||||||
|
|
||||||
// emailAddressProcessor replaces raw email addresses with a mailto: link.
|
// emailAddressProcessor replaces raw email addresses with a mailto: link.
|
||||||
func emailAddressProcessor(ctx *RenderContext, node *html.Node) {
|
func emailAddressProcessor(ctx *RenderContext, node *html.Node) {
|
||||||
@ -14,6 +18,14 @@ func emailAddressProcessor(ctx *RenderContext, node *html.Node) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nextByte byte
|
||||||
|
if len(node.Data) > m[3] {
|
||||||
|
nextByte = node.Data[m[3]]
|
||||||
|
}
|
||||||
|
if strings.IndexByte(":/", nextByte) != -1 {
|
||||||
|
// for cases: "git@gitea.com:owner/repo.git", "https://git@gitea.com/owner/repo.git"
|
||||||
|
return
|
||||||
|
}
|
||||||
mail := node.Data[m[2]:m[3]]
|
mail := node.Data[m[2]:m[3]]
|
||||||
replaceContent(node, m[2], m[3], createLink(ctx, "mailto:"+mail, mail, "" /*mailto*/))
|
replaceContent(node, m[2], m[3], createLink(ctx, "mailto:"+mail, mail, "" /*mailto*/))
|
||||||
node = node.NextSibling.NextSibling
|
node = node.NextSibling.NextSibling
|
||||||
|
@ -225,10 +225,10 @@ func TestRender_email(t *testing.T) {
|
|||||||
test := func(input, expected string) {
|
test := func(input, expected string) {
|
||||||
res, err := markup.RenderString(markup.NewTestRenderContext().WithRelativePath("a.md"), input)
|
res, err := markup.RenderString(markup.NewTestRenderContext().WithRelativePath("a.md"), input)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res))
|
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res), "input: %s", input)
|
||||||
}
|
}
|
||||||
// Text that should be turned into email link
|
|
||||||
|
|
||||||
|
// Text that should be turned into email link
|
||||||
test(
|
test(
|
||||||
"info@gitea.com",
|
"info@gitea.com",
|
||||||
`<p><a href="mailto:info@gitea.com" rel="nofollow">info@gitea.com</a></p>`)
|
`<p><a href="mailto:info@gitea.com" rel="nofollow">info@gitea.com</a></p>`)
|
||||||
@ -260,28 +260,48 @@ func TestRender_email(t *testing.T) {
|
|||||||
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>?
|
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>?
|
||||||
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>!</p>`)
|
<a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>!</p>`)
|
||||||
|
|
||||||
|
// match GitHub behavior
|
||||||
|
test("email@domain@domain.com", `<p>email@<a href="mailto:domain@domain.com" rel="nofollow">domain@domain.com</a></p>`)
|
||||||
|
|
||||||
|
// match GitHub behavior
|
||||||
|
test(`"info@gitea.com"`, `<p>"<a href="mailto:info@gitea.com" rel="nofollow">info@gitea.com</a>"</p>`)
|
||||||
|
|
||||||
// Test that should *not* be turned into email links
|
// Test that should *not* be turned into email links
|
||||||
test(
|
|
||||||
"\"info@gitea.com\"",
|
|
||||||
`<p>"info@gitea.com"</p>`)
|
|
||||||
test(
|
test(
|
||||||
"/home/gitea/mailstore/info@gitea/com",
|
"/home/gitea/mailstore/info@gitea/com",
|
||||||
`<p>/home/gitea/mailstore/info@gitea/com</p>`)
|
`<p>/home/gitea/mailstore/info@gitea/com</p>`)
|
||||||
test(
|
test(
|
||||||
"git@try.gitea.io:go-gitea/gitea.git",
|
"git@try.gitea.io:go-gitea/gitea.git",
|
||||||
`<p>git@try.gitea.io:go-gitea/gitea.git</p>`)
|
`<p>git@try.gitea.io:go-gitea/gitea.git</p>`)
|
||||||
|
test(
|
||||||
|
"https://foo:bar@gitea.io",
|
||||||
|
`<p><a href="https://foo:bar@gitea.io" rel="nofollow">https://foo:bar@gitea.io</a></p>`)
|
||||||
test(
|
test(
|
||||||
"gitea@3",
|
"gitea@3",
|
||||||
`<p>gitea@3</p>`)
|
`<p>gitea@3</p>`)
|
||||||
test(
|
test(
|
||||||
"gitea@gmail.c",
|
"gitea@gmail.c",
|
||||||
`<p>gitea@gmail.c</p>`)
|
`<p>gitea@gmail.c</p>`)
|
||||||
test(
|
|
||||||
"email@domain@domain.com",
|
|
||||||
`<p>email@domain@domain.com</p>`)
|
|
||||||
test(
|
test(
|
||||||
"email@domain..com",
|
"email@domain..com",
|
||||||
`<p>email@domain..com</p>`)
|
`<p>email@domain..com</p>`)
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
input, expected string
|
||||||
|
}{
|
||||||
|
// match GitHub behavior
|
||||||
|
{"?a@d.zz", `<p>?<a href="mailto:a@d.zz" rel="nofollow">a@d.zz</a></p>`},
|
||||||
|
{"*a@d.zz", `<p>*<a href="mailto:a@d.zz" rel="nofollow">a@d.zz</a></p>`},
|
||||||
|
{"~a@d.zz", `<p>~<a href="mailto:a@d.zz" rel="nofollow">a@d.zz</a></p>`},
|
||||||
|
|
||||||
|
// the following cases don't match GitHub behavior, but they are valid email addresses ...
|
||||||
|
// maybe we should reduce the candidate characters for the "name" part in the future
|
||||||
|
{"a*a@d.zz", `<p><a href="mailto:a*a@d.zz" rel="nofollow">a*a@d.zz</a></p>`},
|
||||||
|
{"a~a@d.zz", `<p><a href="mailto:a~a@d.zz" rel="nofollow">a~a@d.zz</a></p>`},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
test(c.input, c.expected)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRender_emoji(t *testing.T) {
|
func TestRender_emoji(t *testing.T) {
|
||||||
|
@ -86,20 +86,15 @@ func (r *GlodmarkRender) highlightingRenderer(w util.BufWriter, c highlighting.C
|
|||||||
preClasses += " is-loading"
|
preClasses += " is-loading"
|
||||||
}
|
}
|
||||||
|
|
||||||
err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `<pre class="%s">`, preClasses)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// include language-x class as part of commonmark spec, "chroma" class is used to highlight the code
|
// include language-x class as part of commonmark spec, "chroma" class is used to highlight the code
|
||||||
// the "display" class is used by "js/markup/math.ts" to render the code element as a block
|
// the "display" class is used by "js/markup/math.ts" to render the code element as a block
|
||||||
// the "math.ts" strictly depends on the structure: <pre class="code-block is-loading"><code class="language-math display">...</code></pre>
|
// the "math.ts" strictly depends on the structure: <pre class="code-block is-loading"><code class="language-math display">...</code></pre>
|
||||||
err = r.ctx.RenderInternal.FormatWithSafeAttrs(w, `<code class="chroma language-%s display">`, languageStr)
|
err := r.ctx.RenderInternal.FormatWithSafeAttrs(w, `<div class="code-block-container code-overflow-scroll"><pre class="%s"><code class="chroma language-%s display">`, preClasses, languageStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, err := w.WriteString("</code></pre>")
|
_, err := w.WriteString("</code></pre></div>")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -46,25 +46,37 @@ var (
|
|||||||
// AppURL is the Application ROOT_URL. It always has a '/' suffix
|
// AppURL is the Application ROOT_URL. It always has a '/' suffix
|
||||||
// It maps to ini:"ROOT_URL"
|
// It maps to ini:"ROOT_URL"
|
||||||
AppURL string
|
AppURL string
|
||||||
// AppSubURL represents the sub-url mounting point for gitea. It is either "" or starts with '/' and ends without '/', such as '/{subpath}'.
|
|
||||||
|
// AppSubURL represents the sub-url mounting point for gitea, parsed from "ROOT_URL"
|
||||||
|
// It is either "" or starts with '/' and ends without '/', such as '/{sub-path}'.
|
||||||
// This value is empty if site does not have sub-url.
|
// This value is empty if site does not have sub-url.
|
||||||
AppSubURL string
|
AppSubURL string
|
||||||
// UseSubURLPath makes Gitea handle requests with sub-path like "/sub-path/owner/repo/...", to make it easier to debug sub-path related problems without a reverse proxy.
|
|
||||||
|
// UseSubURLPath makes Gitea handle requests with sub-path like "/sub-path/owner/repo/...",
|
||||||
|
// to make it easier to debug sub-path related problems without a reverse proxy.
|
||||||
UseSubURLPath bool
|
UseSubURLPath bool
|
||||||
|
|
||||||
|
// UseHostHeader makes Gitea prefer to use the "Host" request header for construction of absolute URLs.
|
||||||
|
UseHostHeader bool
|
||||||
|
|
||||||
// AppDataPath is the default path for storing data.
|
// AppDataPath is the default path for storing data.
|
||||||
// It maps to ini:"APP_DATA_PATH" in [server] and defaults to AppWorkPath + "/data"
|
// It maps to ini:"APP_DATA_PATH" in [server] and defaults to AppWorkPath + "/data"
|
||||||
AppDataPath string
|
AppDataPath string
|
||||||
|
|
||||||
// LocalURL is the url for locally running applications to contact Gitea. It always has a '/' suffix
|
// LocalURL is the url for locally running applications to contact Gitea. It always has a '/' suffix
|
||||||
// It maps to ini:"LOCAL_ROOT_URL" in [server]
|
// It maps to ini:"LOCAL_ROOT_URL" in [server]
|
||||||
LocalURL string
|
LocalURL string
|
||||||
// AssetVersion holds a opaque value that is used for cache-busting assets
|
|
||||||
|
// AssetVersion holds an opaque value that is used for cache-busting assets
|
||||||
AssetVersion string
|
AssetVersion string
|
||||||
|
|
||||||
appTempPathInternal string // the temporary path for the app, it is only an internal variable, do not use it, always use AppDataTempDir
|
// appTempPathInternal is the temporary path for the app, it is only an internal variable
|
||||||
|
// DO NOT use it directly, always use AppDataTempDir
|
||||||
|
appTempPathInternal string
|
||||||
|
|
||||||
Protocol Scheme
|
Protocol Scheme
|
||||||
UseProxyProtocol bool // `ini:"USE_PROXY_PROTOCOL"`
|
UseProxyProtocol bool
|
||||||
ProxyProtocolTLSBridging bool //`ini:"PROXY_PROTOCOL_TLS_BRIDGING"`
|
ProxyProtocolTLSBridging bool
|
||||||
ProxyProtocolHeaderTimeout time.Duration
|
ProxyProtocolHeaderTimeout time.Duration
|
||||||
ProxyProtocolAcceptUnknown bool
|
ProxyProtocolAcceptUnknown bool
|
||||||
Domain string
|
Domain string
|
||||||
@ -181,13 +193,14 @@ func loadServerFrom(rootCfg ConfigProvider) {
|
|||||||
EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
|
EnableAcme = sec.Key("ENABLE_LETSENCRYPT").MustBool(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
Protocol = HTTP
|
|
||||||
protocolCfg := sec.Key("PROTOCOL").String()
|
protocolCfg := sec.Key("PROTOCOL").String()
|
||||||
if protocolCfg != "https" && EnableAcme {
|
if protocolCfg != "https" && EnableAcme {
|
||||||
log.Fatal("ACME could only be used with HTTPS protocol")
|
log.Fatal("ACME could only be used with HTTPS protocol")
|
||||||
}
|
}
|
||||||
|
|
||||||
switch protocolCfg {
|
switch protocolCfg {
|
||||||
|
case "", "http":
|
||||||
|
Protocol = HTTP
|
||||||
case "https":
|
case "https":
|
||||||
Protocol = HTTPS
|
Protocol = HTTPS
|
||||||
if EnableAcme {
|
if EnableAcme {
|
||||||
@ -243,7 +256,7 @@ func loadServerFrom(rootCfg ConfigProvider) {
|
|||||||
case "unix":
|
case "unix":
|
||||||
log.Warn("unix PROTOCOL value is deprecated, please use http+unix")
|
log.Warn("unix PROTOCOL value is deprecated, please use http+unix")
|
||||||
fallthrough
|
fallthrough
|
||||||
case "http+unix":
|
default: // "http+unix"
|
||||||
Protocol = HTTPUnix
|
Protocol = HTTPUnix
|
||||||
}
|
}
|
||||||
UnixSocketPermissionRaw := sec.Key("UNIX_SOCKET_PERMISSION").MustString("666")
|
UnixSocketPermissionRaw := sec.Key("UNIX_SOCKET_PERMISSION").MustString("666")
|
||||||
@ -256,6 +269,8 @@ func loadServerFrom(rootCfg ConfigProvider) {
|
|||||||
if !filepath.IsAbs(HTTPAddr) {
|
if !filepath.IsAbs(HTTPAddr) {
|
||||||
HTTPAddr = filepath.Join(AppWorkPath, HTTPAddr)
|
HTTPAddr = filepath.Join(AppWorkPath, HTTPAddr)
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
log.Fatal("Invalid PROTOCOL %q", Protocol)
|
||||||
}
|
}
|
||||||
UseProxyProtocol = sec.Key("USE_PROXY_PROTOCOL").MustBool(false)
|
UseProxyProtocol = sec.Key("USE_PROXY_PROTOCOL").MustBool(false)
|
||||||
ProxyProtocolTLSBridging = sec.Key("PROXY_PROTOCOL_TLS_BRIDGING").MustBool(false)
|
ProxyProtocolTLSBridging = sec.Key("PROXY_PROTOCOL_TLS_BRIDGING").MustBool(false)
|
||||||
@ -268,12 +283,16 @@ func loadServerFrom(rootCfg ConfigProvider) {
|
|||||||
PerWritePerKbTimeout = sec.Key("PER_WRITE_PER_KB_TIMEOUT").MustDuration(PerWritePerKbTimeout)
|
PerWritePerKbTimeout = sec.Key("PER_WRITE_PER_KB_TIMEOUT").MustDuration(PerWritePerKbTimeout)
|
||||||
|
|
||||||
defaultAppURL := string(Protocol) + "://" + Domain + ":" + HTTPPort
|
defaultAppURL := string(Protocol) + "://" + Domain + ":" + HTTPPort
|
||||||
AppURL = sec.Key("ROOT_URL").MustString(defaultAppURL)
|
AppURL = sec.Key("ROOT_URL").String()
|
||||||
|
if AppURL == "" {
|
||||||
|
UseHostHeader = true
|
||||||
|
AppURL = defaultAppURL
|
||||||
|
}
|
||||||
|
|
||||||
// Check validity of AppURL
|
// Check validity of AppURL
|
||||||
appURL, err := url.Parse(AppURL)
|
appURL, err := url.Parse(AppURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Invalid ROOT_URL '%s': %s", AppURL, err)
|
log.Fatal("Invalid ROOT_URL %q: %s", AppURL, err)
|
||||||
}
|
}
|
||||||
// Remove default ports from AppURL.
|
// Remove default ports from AppURL.
|
||||||
// (scheme-based URL normalization, RFC 3986 section 6.2.3)
|
// (scheme-based URL normalization, RFC 3986 section 6.2.3)
|
||||||
@ -309,13 +328,15 @@ func loadServerFrom(rootCfg ConfigProvider) {
|
|||||||
defaultLocalURL = AppURL
|
defaultLocalURL = AppURL
|
||||||
case FCGIUnix:
|
case FCGIUnix:
|
||||||
defaultLocalURL = AppURL
|
defaultLocalURL = AppURL
|
||||||
default:
|
case HTTP, HTTPS:
|
||||||
defaultLocalURL = string(Protocol) + "://"
|
defaultLocalURL = string(Protocol) + "://"
|
||||||
if HTTPAddr == "0.0.0.0" {
|
if HTTPAddr == "0.0.0.0" {
|
||||||
defaultLocalURL += net.JoinHostPort("localhost", HTTPPort) + "/"
|
defaultLocalURL += net.JoinHostPort("localhost", HTTPPort) + "/"
|
||||||
} else {
|
} else {
|
||||||
defaultLocalURL += net.JoinHostPort(HTTPAddr, HTTPPort) + "/"
|
defaultLocalURL += net.JoinHostPort(HTTPAddr, HTTPPort) + "/"
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
|
log.Fatal("Invalid PROTOCOL %q", Protocol)
|
||||||
}
|
}
|
||||||
LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(defaultLocalURL)
|
LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(defaultLocalURL)
|
||||||
LocalURL = strings.TrimRight(LocalURL, "/") + "/"
|
LocalURL = strings.TrimRight(LocalURL, "/") + "/"
|
||||||
|
@ -133,3 +133,26 @@ type ActionWorkflowJob struct {
|
|||||||
// swagger:strfmt date-time
|
// swagger:strfmt date-time
|
||||||
CompletedAt time.Time `json:"completed_at,omitempty"`
|
CompletedAt time.Time `json:"completed_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ActionRunnerLabel represents a Runner Label
|
||||||
|
type ActionRunnerLabel struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionRunner represents a Runner
|
||||||
|
type ActionRunner struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
Busy bool `json:"busy"`
|
||||||
|
Ephemeral bool `json:"ephemeral"`
|
||||||
|
Labels []*ActionRunnerLabel `json:"labels"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ActionRunnersResponse returns Runners
|
||||||
|
type ActionRunnersResponse struct {
|
||||||
|
Entries []*ActionRunner `json:"runners"`
|
||||||
|
TotalCount int64 `json:"total_count"`
|
||||||
|
}
|
||||||
|
@ -117,6 +117,7 @@ files=ファイル
|
|||||||
|
|
||||||
error=エラー
|
error=エラー
|
||||||
error404=アクセスしようとしたページは<strong>存在しない</strong>か、閲覧が<strong>許可されていません</strong>。
|
error404=アクセスしようとしたページは<strong>存在しない</strong>か、閲覧が<strong>許可されていません</strong>。
|
||||||
|
error503=サーバーはリクエストを完了できませんでした。 後でもう一度お試しください。
|
||||||
go_back=戻る
|
go_back=戻る
|
||||||
invalid_data=無効なデータ: %v
|
invalid_data=無効なデータ: %v
|
||||||
|
|
||||||
@ -730,6 +731,8 @@ public_profile=公開プロフィール
|
|||||||
biography_placeholder=自己紹介してください!(Markdownを使うことができます)
|
biography_placeholder=自己紹介してください!(Markdownを使うことができます)
|
||||||
location_placeholder=おおよその場所を他の人と共有
|
location_placeholder=おおよその場所を他の人と共有
|
||||||
profile_desc=あなたのプロフィールが他のユーザーにどのように表示されるかを制御します。あなたのプライマリメールアドレスは、通知、パスワードの回復、WebベースのGit操作に使用されます。
|
profile_desc=あなたのプロフィールが他のユーザーにどのように表示されるかを制御します。あなたのプライマリメールアドレスは、通知、パスワードの回復、WebベースのGit操作に使用されます。
|
||||||
|
password_username_disabled=ユーザー名の変更は許可されていません。詳細はサイト管理者にお問い合わせください。
|
||||||
|
password_full_name_disabled=フルネームの変更は許可されていません。詳細はサイト管理者にお問い合わせください。
|
||||||
full_name=フルネーム
|
full_name=フルネーム
|
||||||
website=Webサイト
|
website=Webサイト
|
||||||
location=場所
|
location=場所
|
||||||
@ -924,6 +927,9 @@ permission_not_set=設定なし
|
|||||||
permission_no_access=アクセス不可
|
permission_no_access=アクセス不可
|
||||||
permission_read=読み取り
|
permission_read=読み取り
|
||||||
permission_write=読み取りと書き込み
|
permission_write=読み取りと書き込み
|
||||||
|
permission_anonymous_read=匿名の読み込み
|
||||||
|
permission_everyone_read=全員の読み込み
|
||||||
|
permission_everyone_write=全員の書き込み
|
||||||
access_token_desc=選択したトークン権限に応じて、関連する<a %s>API</a>ルートのみに許可が制限されます。 詳細は<a %s>ドキュメント</a>を参照してください。
|
access_token_desc=選択したトークン権限に応じて、関連する<a %s>API</a>ルートのみに許可が制限されます。 詳細は<a %s>ドキュメント</a>を参照してください。
|
||||||
at_least_one_permission=トークンを作成するには、少なくともひとつの許可を選択する必要があります
|
at_least_one_permission=トークンを作成するには、少なくともひとつの許可を選択する必要があります
|
||||||
permissions_list=許可:
|
permissions_list=許可:
|
||||||
@ -1136,6 +1142,7 @@ transfer.no_permission_to_reject=この移転を拒否する権限がありま
|
|||||||
|
|
||||||
desc.private=プライベート
|
desc.private=プライベート
|
||||||
desc.public=公開
|
desc.public=公開
|
||||||
|
desc.public_access=公開アクセス
|
||||||
desc.template=テンプレート
|
desc.template=テンプレート
|
||||||
desc.internal=内部
|
desc.internal=内部
|
||||||
desc.archived=アーカイブ
|
desc.archived=アーカイブ
|
||||||
@ -1544,6 +1551,7 @@ issues.filter_project=プロジェクト
|
|||||||
issues.filter_project_all=すべてのプロジェクト
|
issues.filter_project_all=すべてのプロジェクト
|
||||||
issues.filter_project_none=プロジェクトなし
|
issues.filter_project_none=プロジェクトなし
|
||||||
issues.filter_assignee=担当者
|
issues.filter_assignee=担当者
|
||||||
|
issues.filter_assignee_no_assignee=担当者なし
|
||||||
issues.filter_assignee_any_assignee=担当者あり
|
issues.filter_assignee_any_assignee=担当者あり
|
||||||
issues.filter_poster=作成者
|
issues.filter_poster=作成者
|
||||||
issues.filter_user_placeholder=ユーザーを検索
|
issues.filter_user_placeholder=ユーザーを検索
|
||||||
@ -1647,6 +1655,8 @@ issues.label_archived_filter=アーカイブされたラベルを表示
|
|||||||
issues.label_archive_tooltip=アーカイブされたラベルは、ラベルによる検索時のサジェストからデフォルトで除外されます。
|
issues.label_archive_tooltip=アーカイブされたラベルは、ラベルによる検索時のサジェストからデフォルトで除外されます。
|
||||||
issues.label_exclusive_desc=ラベル名を <code>スコープ/アイテム</code> の形にすることで、他の <code>スコープ/</code> ラベルと排他的になります。
|
issues.label_exclusive_desc=ラベル名を <code>スコープ/アイテム</code> の形にすることで、他の <code>スコープ/</code> ラベルと排他的になります。
|
||||||
issues.label_exclusive_warning=イシューやプルリクエストのラベル編集では、競合するスコープ付きラベルは解除されます。
|
issues.label_exclusive_warning=イシューやプルリクエストのラベル編集では、競合するスコープ付きラベルは解除されます。
|
||||||
|
issues.label_exclusive_order=ソート順
|
||||||
|
issues.label_exclusive_order_tooltip=同じスコープ内の排他的なラベルは、この数値順にソートされます。
|
||||||
issues.label_count=ラベル %d件
|
issues.label_count=ラベル %d件
|
||||||
issues.label_open_issues=オープン中のイシュー %d件
|
issues.label_open_issues=オープン中のイシュー %d件
|
||||||
issues.label_edit=編集
|
issues.label_edit=編集
|
||||||
@ -2129,6 +2139,12 @@ contributors.contribution_type.deletions=削除
|
|||||||
settings=設定
|
settings=設定
|
||||||
settings.desc=設定では、リポジトリの設定を管理することができます。
|
settings.desc=設定では、リポジトリの設定を管理することができます。
|
||||||
settings.options=リポジトリ
|
settings.options=リポジトリ
|
||||||
|
settings.public_access=公開アクセス
|
||||||
|
settings.public_access_desc=外部からの訪問者のアクセス権限について、このリポジトリのデフォルト設定を上書きします。
|
||||||
|
settings.public_access.docs.not_set=設定なし: 公開アクセス権限はありません。訪問者の権限は、リポジトリの公開範囲とメンバーの権限に従います。
|
||||||
|
settings.public_access.docs.anonymous_read=匿名の読み込み: ログインしていないユーザーは読み取り権限でユニットにアクセスできます。
|
||||||
|
settings.public_access.docs.everyone_read=全員の読み込み: すべてのログインユーザーは読み取り権限でユニットにアクセスできます。イシュー/プルリクエストユニットの読み取り権限は、ユーザーが新しいイシュー/プルリクエストを作成できることを意味します。
|
||||||
|
settings.public_access.docs.everyone_write=全員の書き込み: すべてのログインユーザーに書き込み権限があります。Wikiユニットのみがこの権限をサポートします。
|
||||||
settings.collaboration=共同作業者
|
settings.collaboration=共同作業者
|
||||||
settings.collaboration.admin=管理者
|
settings.collaboration.admin=管理者
|
||||||
settings.collaboration.write=書き込み
|
settings.collaboration.write=書き込み
|
||||||
@ -2719,6 +2735,7 @@ branch.restore_success=ブランチ "%s" を復元しました。
|
|||||||
branch.restore_failed=ブランチ "%s" の復元に失敗しました。
|
branch.restore_failed=ブランチ "%s" の復元に失敗しました。
|
||||||
branch.protected_deletion_failed=ブランチ "%s" は保護されています。 削除できません。
|
branch.protected_deletion_failed=ブランチ "%s" は保護されています。 削除できません。
|
||||||
branch.default_deletion_failed=ブランチ "%s" はデフォルトブランチです。 削除できません。
|
branch.default_deletion_failed=ブランチ "%s" はデフォルトブランチです。 削除できません。
|
||||||
|
branch.default_branch_not_exist=デフォルトブランチ "%s" がありません。
|
||||||
branch.restore=ブランチ "%s" の復元
|
branch.restore=ブランチ "%s" の復元
|
||||||
branch.download=ブランチ "%s" をダウンロード
|
branch.download=ブランチ "%s" をダウンロード
|
||||||
branch.rename=ブランチ名 "%s" を変更
|
branch.rename=ブランチ名 "%s" を変更
|
||||||
|
@ -290,7 +290,24 @@ func DownloadManifest(ctx *context.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-6
|
// formFileOptionalReadCloser returns (nil, nil) if the formKey is not present.
|
||||||
|
func formFileOptionalReadCloser(ctx *context.Context, formKey string) (io.ReadCloser, error) {
|
||||||
|
multipartFile, _, err := ctx.Req.FormFile(formKey)
|
||||||
|
if err != nil && !errors.Is(err, http.ErrMissingFile) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if multipartFile != nil {
|
||||||
|
return multipartFile, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
content := ctx.Req.FormValue(formKey)
|
||||||
|
if content == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return io.NopCloser(strings.NewReader(content)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UploadPackageFile refers to https://github.com/swiftlang/swift-package-manager/blob/main/Documentation/PackageRegistry/Registry.md#endpoint-6
|
||||||
func UploadPackageFile(ctx *context.Context) {
|
func UploadPackageFile(ctx *context.Context) {
|
||||||
packageScope := ctx.PathParam("scope")
|
packageScope := ctx.PathParam("scope")
|
||||||
packageName := ctx.PathParam("name")
|
packageName := ctx.PathParam("name")
|
||||||
@ -304,9 +321,9 @@ func UploadPackageFile(ctx *context.Context) {
|
|||||||
|
|
||||||
packageVersion := v.Core().String()
|
packageVersion := v.Core().String()
|
||||||
|
|
||||||
file, _, err := ctx.Req.FormFile("source-archive")
|
file, err := formFileOptionalReadCloser(ctx, "source-archive")
|
||||||
if err != nil {
|
if file == nil || err != nil {
|
||||||
apiError(ctx, http.StatusBadRequest, err)
|
apiError(ctx, http.StatusBadRequest, "unable to read source-archive file")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
@ -318,10 +335,13 @@ func UploadPackageFile(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
defer buf.Close()
|
defer buf.Close()
|
||||||
|
|
||||||
var mr io.Reader
|
mr, err := formFileOptionalReadCloser(ctx, "metadata")
|
||||||
metadata := ctx.Req.FormValue("metadata")
|
if err != nil {
|
||||||
if metadata != "" {
|
apiError(ctx, http.StatusBadRequest, "unable to read metadata file")
|
||||||
mr = strings.NewReader(metadata)
|
return
|
||||||
|
}
|
||||||
|
if mr != nil {
|
||||||
|
defer mr.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
pck, err := swift_module.ParsePackage(buf, buf.Size(), mr)
|
pck, err := swift_module.ParsePackage(buf, buf.Size(), mr)
|
||||||
|
@ -24,3 +24,81 @@ func GetRegistrationToken(ctx *context.APIContext) {
|
|||||||
|
|
||||||
shared.GetRegistrationToken(ctx, 0, 0)
|
shared.GetRegistrationToken(ctx, 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateRegistrationToken returns the token to register global runners
|
||||||
|
func CreateRegistrationToken(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /admin/actions/runners/registration-token admin adminCreateRunnerRegistrationToken
|
||||||
|
// ---
|
||||||
|
// summary: Get an global actions runner registration token
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/RegistrationToken"
|
||||||
|
|
||||||
|
shared.GetRegistrationToken(ctx, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRunners get all runners
|
||||||
|
func ListRunners(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /admin/actions/runners admin getAdminRunners
|
||||||
|
// ---
|
||||||
|
// summary: Get all runners
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/definitions/ActionRunnersResponse"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
shared.ListRunners(ctx, 0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRunner get an global runner
|
||||||
|
func GetRunner(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /admin/actions/runners/{runner_id} admin getAdminRunner
|
||||||
|
// ---
|
||||||
|
// summary: Get an global runner
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: runner_id
|
||||||
|
// in: path
|
||||||
|
// description: id of the runner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/definitions/ActionRunner"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
shared.GetRunner(ctx, 0, 0, ctx.PathParamInt64("runner_id"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRunner delete an global runner
|
||||||
|
func DeleteRunner(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /admin/actions/runners/{runner_id} admin deleteAdminRunner
|
||||||
|
// ---
|
||||||
|
// summary: Delete an global runner
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: runner_id
|
||||||
|
// in: path
|
||||||
|
// description: id of the runner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// description: runner has been deleted
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
shared.DeleteRunner(ctx, 0, 0, ctx.PathParamInt64("runner_id"))
|
||||||
|
}
|
||||||
|
@ -912,7 +912,11 @@ func Routes() *web.Router {
|
|||||||
})
|
})
|
||||||
|
|
||||||
m.Group("/runners", func() {
|
m.Group("/runners", func() {
|
||||||
|
m.Get("", reqToken(), reqChecker, act.ListRunners)
|
||||||
m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken)
|
m.Get("/registration-token", reqToken(), reqChecker, act.GetRegistrationToken)
|
||||||
|
m.Post("/registration-token", reqToken(), reqChecker, act.CreateRegistrationToken)
|
||||||
|
m.Get("/{runner_id}", reqToken(), reqChecker, act.GetRunner)
|
||||||
|
m.Delete("/{runner_id}", reqToken(), reqChecker, act.DeleteRunner)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -1043,7 +1047,11 @@ func Routes() *web.Router {
|
|||||||
})
|
})
|
||||||
|
|
||||||
m.Group("/runners", func() {
|
m.Group("/runners", func() {
|
||||||
|
m.Get("", reqToken(), user.ListRunners)
|
||||||
m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
|
m.Get("/registration-token", reqToken(), user.GetRegistrationToken)
|
||||||
|
m.Post("/registration-token", reqToken(), user.CreateRegistrationToken)
|
||||||
|
m.Get("/{runner_id}", reqToken(), user.GetRunner)
|
||||||
|
m.Delete("/{runner_id}", reqToken(), user.DeleteRunner)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1689,6 +1697,12 @@ func Routes() *web.Router {
|
|||||||
Patch(bind(api.EditHookOption{}), admin.EditHook).
|
Patch(bind(api.EditHookOption{}), admin.EditHook).
|
||||||
Delete(admin.DeleteHook)
|
Delete(admin.DeleteHook)
|
||||||
})
|
})
|
||||||
|
m.Group("/actions/runners", func() {
|
||||||
|
m.Get("", admin.ListRunners)
|
||||||
|
m.Post("/registration-token", admin.CreateRegistrationToken)
|
||||||
|
m.Get("/{runner_id}", admin.GetRunner)
|
||||||
|
m.Delete("/{runner_id}", admin.DeleteRunner)
|
||||||
|
})
|
||||||
m.Group("/runners", func() {
|
m.Group("/runners", func() {
|
||||||
m.Get("/registration-token", admin.GetRegistrationToken)
|
m.Get("/registration-token", admin.GetRegistrationToken)
|
||||||
})
|
})
|
||||||
|
@ -190,6 +190,27 @@ func (Action) GetRegistrationToken(ctx *context.APIContext) {
|
|||||||
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
|
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://docs.github.com/en/rest/actions/self-hosted-runners?apiVersion=2022-11-28#create-a-registration-token-for-an-organization
|
||||||
|
// CreateRegistrationToken returns the token to register org runners
|
||||||
|
func (Action) CreateRegistrationToken(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /orgs/{org}/actions/runners/registration-token organization orgCreateRunnerRegistrationToken
|
||||||
|
// ---
|
||||||
|
// summary: Get an organization's actions runner registration token
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of the organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/RegistrationToken"
|
||||||
|
|
||||||
|
shared.GetRegistrationToken(ctx, ctx.Org.Organization.ID, 0)
|
||||||
|
}
|
||||||
|
|
||||||
// ListVariables list org-level variables
|
// ListVariables list org-level variables
|
||||||
func (Action) ListVariables(ctx *context.APIContext) {
|
func (Action) ListVariables(ctx *context.APIContext) {
|
||||||
// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
|
// swagger:operation GET /orgs/{org}/actions/variables organization getOrgVariablesList
|
||||||
@ -470,6 +491,85 @@ func (Action) UpdateVariable(ctx *context.APIContext) {
|
|||||||
ctx.Status(http.StatusNoContent)
|
ctx.Status(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListRunners get org-level runners
|
||||||
|
func (Action) ListRunners(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /orgs/{org}/actions/runners organization getOrgRunners
|
||||||
|
// ---
|
||||||
|
// summary: Get org-level runners
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of the organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/definitions/ActionRunnersResponse"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
shared.ListRunners(ctx, ctx.Org.Organization.ID, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRunner get an org-level runner
|
||||||
|
func (Action) GetRunner(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /orgs/{org}/actions/runners/{runner_id} organization getOrgRunner
|
||||||
|
// ---
|
||||||
|
// summary: Get an org-level runner
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of the organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: runner_id
|
||||||
|
// in: path
|
||||||
|
// description: id of the runner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/definitions/ActionRunner"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
shared.GetRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRunner delete an org-level runner
|
||||||
|
func (Action) DeleteRunner(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /orgs/{org}/actions/runners/{runner_id} organization deleteOrgRunner
|
||||||
|
// ---
|
||||||
|
// summary: Delete an org-level runner
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: org
|
||||||
|
// in: path
|
||||||
|
// description: name of the organization
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: runner_id
|
||||||
|
// in: path
|
||||||
|
// description: id of the runner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// description: runner has been deleted
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
shared.DeleteRunner(ctx, ctx.Org.Organization.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||||
|
}
|
||||||
|
|
||||||
var _ actions_service.API = new(Action)
|
var _ actions_service.API = new(Action)
|
||||||
|
|
||||||
// Action implements actions_service.API
|
// Action implements actions_service.API
|
||||||
|
@ -183,7 +183,7 @@ func (Action) DeleteSecret(ctx *context.APIContext) {
|
|||||||
// required: true
|
// required: true
|
||||||
// responses:
|
// responses:
|
||||||
// "204":
|
// "204":
|
||||||
// description: delete one secret of the organization
|
// description: delete one secret of the repository
|
||||||
// "400":
|
// "400":
|
||||||
// "$ref": "#/responses/error"
|
// "$ref": "#/responses/error"
|
||||||
// "404":
|
// "404":
|
||||||
@ -531,6 +531,125 @@ func (Action) GetRegistrationToken(ctx *context.APIContext) {
|
|||||||
shared.GetRegistrationToken(ctx, 0, ctx.Repo.Repository.ID)
|
shared.GetRegistrationToken(ctx, 0, ctx.Repo.Repository.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateRegistrationToken returns the token to register repo runners
|
||||||
|
func (Action) CreateRegistrationToken(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /repos/{owner}/{repo}/actions/runners/registration-token repository repoCreateRunnerRegistrationToken
|
||||||
|
// ---
|
||||||
|
// summary: Get a repository's actions runner registration token
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/RegistrationToken"
|
||||||
|
|
||||||
|
shared.GetRegistrationToken(ctx, 0, ctx.Repo.Repository.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRunners get repo-level runners
|
||||||
|
func (Action) ListRunners(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/actions/runners repository getRepoRunners
|
||||||
|
// ---
|
||||||
|
// summary: Get repo-level runners
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/definitions/ActionRunnersResponse"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
shared.ListRunners(ctx, 0, ctx.Repo.Repository.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRunner get an repo-level runner
|
||||||
|
func (Action) GetRunner(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /repos/{owner}/{repo}/actions/runners/{runner_id} repository getRepoRunner
|
||||||
|
// ---
|
||||||
|
// summary: Get an repo-level runner
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: runner_id
|
||||||
|
// in: path
|
||||||
|
// description: id of the runner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/definitions/ActionRunner"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
shared.GetRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRunner delete an repo-level runner
|
||||||
|
func (Action) DeleteRunner(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /repos/{owner}/{repo}/actions/runners/{runner_id} repository deleteRepoRunner
|
||||||
|
// ---
|
||||||
|
// summary: Delete an repo-level runner
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: runner_id
|
||||||
|
// in: path
|
||||||
|
// description: id of the runner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// description: runner has been deleted
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
shared.DeleteRunner(ctx, 0, ctx.Repo.Repository.ID, ctx.PathParamInt64("runner_id"))
|
||||||
|
}
|
||||||
|
|
||||||
var _ actions_service.API = new(Action)
|
var _ actions_service.API = new(Action)
|
||||||
|
|
||||||
// Action implements actions_service.API
|
// Action implements actions_service.API
|
||||||
|
@ -193,7 +193,7 @@ func getWikiPage(ctx *context.APIContext, wikiName wiki_service.WebPath) *api.Wi
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get commit count - wiki revisions
|
// get commit count - wiki revisions
|
||||||
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
|
commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
|
||||||
|
|
||||||
// Get last change information.
|
// Get last change information.
|
||||||
lastCommit, err := wikiRepo.GetCommitByPath(pageFilename)
|
lastCommit, err := wikiRepo.GetCommitByPath(pageFilename)
|
||||||
@ -432,7 +432,7 @@ func ListPageRevisions(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get commit count - wiki revisions
|
// get commit count - wiki revisions
|
||||||
commitsCount, _ := wikiRepo.FileCommitsCount("master", pageFilename)
|
commitsCount, _ := wikiRepo.FileCommitsCount(ctx.Repo.Repository.DefaultWikiBranch, pageFilename)
|
||||||
|
|
||||||
page := ctx.FormInt("page")
|
page := ctx.FormInt("page")
|
||||||
if page <= 1 {
|
if page <= 1 {
|
||||||
@ -442,7 +442,7 @@ func ListPageRevisions(ctx *context.APIContext) {
|
|||||||
// get Commit Count
|
// get Commit Count
|
||||||
commitsHistory, err := wikiRepo.CommitsByFileAndRange(
|
commitsHistory, err := wikiRepo.CommitsByFileAndRange(
|
||||||
git.CommitsByFileAndRangeOptions{
|
git.CommitsByFileAndRangeOptions{
|
||||||
Revision: "master",
|
Revision: ctx.Repo.Repository.DefaultWikiBranch,
|
||||||
File: pageFilename,
|
File: pageFilename,
|
||||||
Page: page,
|
Page: page,
|
||||||
})
|
})
|
||||||
@ -486,7 +486,7 @@ func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit)
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
commit, err := wikiRepo.GetBranchCommit("master")
|
commit, err := wikiRepo.GetBranchCommit(ctx.Repo.Repository.DefaultWikiBranch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if git.IsErrNotExist(err) {
|
if git.IsErrNotExist(err) {
|
||||||
ctx.APIErrorNotFound(err)
|
ctx.APIErrorNotFound(err)
|
||||||
|
@ -8,8 +8,13 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
actions_model "code.gitea.io/gitea/models/actions"
|
actions_model "code.gitea.io/gitea/models/actions"
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/convert"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegistrationToken is response related to registration token
|
// RegistrationToken is response related to registration token
|
||||||
@ -30,3 +35,84 @@ func GetRegistrationToken(ctx *context.APIContext, ownerID, repoID int64) {
|
|||||||
|
|
||||||
ctx.JSON(http.StatusOK, RegistrationToken{Token: token.Token})
|
ctx.JSON(http.StatusOK, RegistrationToken{Token: token.Token})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListRunners lists runners for api route validated ownerID and repoID
|
||||||
|
// ownerID == 0 and repoID == 0 means all runners including global runners, does not appear in sql where clause
|
||||||
|
// ownerID == 0 and repoID != 0 means all runners for the given repo
|
||||||
|
// ownerID != 0 and repoID == 0 means all runners for the given user/org
|
||||||
|
// ownerID != 0 and repoID != 0 undefined behavior
|
||||||
|
// Access rights are checked at the API route level
|
||||||
|
func ListRunners(ctx *context.APIContext, ownerID, repoID int64) {
|
||||||
|
if ownerID != 0 && repoID != 0 {
|
||||||
|
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||||
|
}
|
||||||
|
runners, total, err := db.FindAndCount[actions_model.ActionRunner](ctx, &actions_model.FindRunnerOptions{
|
||||||
|
OwnerID: ownerID,
|
||||||
|
RepoID: repoID,
|
||||||
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
res := new(api.ActionRunnersResponse)
|
||||||
|
res.TotalCount = total
|
||||||
|
|
||||||
|
res.Entries = make([]*api.ActionRunner, len(runners))
|
||||||
|
for i, runner := range runners {
|
||||||
|
res.Entries[i] = convert.ToActionRunner(ctx, runner)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, &res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRunner get the runner for api route validated ownerID and repoID
|
||||||
|
// ownerID == 0 and repoID == 0 means any runner including global runners
|
||||||
|
// ownerID == 0 and repoID != 0 means any runner for the given repo
|
||||||
|
// ownerID != 0 and repoID == 0 means any runner for the given user/org
|
||||||
|
// ownerID != 0 and repoID != 0 undefined behavior
|
||||||
|
// Access rights are checked at the API route level
|
||||||
|
func GetRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
||||||
|
if ownerID != 0 && repoID != 0 {
|
||||||
|
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||||
|
}
|
||||||
|
runner, err := actions_model.GetRunnerByID(ctx, runnerID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorNotFound(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !runner.EditableInContext(ownerID, repoID) {
|
||||||
|
ctx.APIErrorNotFound("No permission to get this runner")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToActionRunner(ctx, runner))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRunner deletes the runner for api route validated ownerID and repoID
|
||||||
|
// ownerID == 0 and repoID == 0 means any runner including global runners
|
||||||
|
// ownerID == 0 and repoID != 0 means any runner for the given repo
|
||||||
|
// ownerID != 0 and repoID == 0 means any runner for the given user/org
|
||||||
|
// ownerID != 0 and repoID != 0 undefined behavior
|
||||||
|
// Access rights are checked at the API route level
|
||||||
|
func DeleteRunner(ctx *context.APIContext, ownerID, repoID, runnerID int64) {
|
||||||
|
if ownerID != 0 && repoID != 0 {
|
||||||
|
setting.PanicInDevOrTesting("ownerID and repoID should not be both set")
|
||||||
|
}
|
||||||
|
runner, err := actions_model.GetRunnerByID(ctx, runnerID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !runner.EditableInContext(ownerID, repoID) {
|
||||||
|
ctx.APIErrorNotFound("No permission to delete this runner")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = actions_model.DeleteRunner(ctx, runner.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.APIErrorInternal(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
@ -457,6 +457,20 @@ type swaggerRepoArtifact struct {
|
|||||||
Body api.ActionArtifact `json:"body"`
|
Body api.ActionArtifact `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RunnerList
|
||||||
|
// swagger:response RunnerList
|
||||||
|
type swaggerRunnerList struct {
|
||||||
|
// in:body
|
||||||
|
Body api.ActionRunnersResponse `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Runner
|
||||||
|
// swagger:response Runner
|
||||||
|
type swaggerRunner struct {
|
||||||
|
// in:body
|
||||||
|
Body api.ActionRunner `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
// swagger:response Compare
|
// swagger:response Compare
|
||||||
type swaggerCompare struct {
|
type swaggerCompare struct {
|
||||||
// in:body
|
// in:body
|
||||||
|
@ -24,3 +24,81 @@ func GetRegistrationToken(ctx *context.APIContext) {
|
|||||||
|
|
||||||
shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0)
|
shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateRegistrationToken returns the token to register user runners
|
||||||
|
func CreateRegistrationToken(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /user/actions/runners/registration-token user userCreateRunnerRegistrationToken
|
||||||
|
// ---
|
||||||
|
// summary: Get an user's actions runner registration token
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/RegistrationToken"
|
||||||
|
|
||||||
|
shared.GetRegistrationToken(ctx, ctx.Doer.ID, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListRunners get user-level runners
|
||||||
|
func ListRunners(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /user/actions/runners user getUserRunners
|
||||||
|
// ---
|
||||||
|
// summary: Get user-level runners
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/definitions/ActionRunnersResponse"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
shared.ListRunners(ctx, ctx.Doer.ID, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRunner get an user-level runner
|
||||||
|
func GetRunner(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /user/actions/runners/{runner_id} user getUserRunner
|
||||||
|
// ---
|
||||||
|
// summary: Get an user-level runner
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: runner_id
|
||||||
|
// in: path
|
||||||
|
// description: id of the runner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/definitions/ActionRunner"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
shared.GetRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRunner delete an user-level runner
|
||||||
|
func DeleteRunner(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /user/actions/runners/{runner_id} user deleteUserRunner
|
||||||
|
// ---
|
||||||
|
// summary: Delete an user-level runner
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: runner_id
|
||||||
|
// in: path
|
||||||
|
// description: id of the runner
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// description: runner has been deleted
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
shared.DeleteRunner(ctx, ctx.Doer.ID, 0, ctx.PathParamInt64("runner_id"))
|
||||||
|
}
|
||||||
|
@ -76,6 +76,7 @@ func TestShadowPassword(t *testing.T) {
|
|||||||
func TestSelfCheckPost(t *testing.T) {
|
func TestSelfCheckPost(t *testing.T) {
|
||||||
defer test.MockVariableValue(&setting.AppURL, "http://config/sub/")()
|
defer test.MockVariableValue(&setting.AppURL, "http://config/sub/")()
|
||||||
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
||||||
|
defer test.MockVariableValue(&setting.UseHostHeader, false)()
|
||||||
|
|
||||||
ctx, resp := contexttest.MockContext(t, "GET http://host/sub/admin/self_check?location_origin=http://frontend")
|
ctx, resp := contexttest.MockContext(t, "GET http://host/sub/admin/self_check?location_origin=http://frontend")
|
||||||
SelfCheckPost(ctx)
|
SelfCheckPost(ctx)
|
||||||
|
@ -181,6 +181,7 @@ func setMergeTarget(ctx *context.Context, pull *issues_model.PullRequest) {
|
|||||||
|
|
||||||
// GetPullDiffStats get Pull Requests diff stats
|
// GetPullDiffStats get Pull Requests diff stats
|
||||||
func GetPullDiffStats(ctx *context.Context) {
|
func GetPullDiffStats(ctx *context.Context) {
|
||||||
|
// FIXME: this getPullInfo seems to be a duplicate call with other route handlers
|
||||||
issue, ok := getPullInfo(ctx)
|
issue, ok := getPullInfo(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
@ -188,21 +189,19 @@ func GetPullDiffStats(ctx *context.Context) {
|
|||||||
pull := issue.PullRequest
|
pull := issue.PullRequest
|
||||||
|
|
||||||
mergeBaseCommitID := GetMergedBaseCommitID(ctx, issue)
|
mergeBaseCommitID := GetMergedBaseCommitID(ctx, issue)
|
||||||
|
|
||||||
if mergeBaseCommitID == "" {
|
if mergeBaseCommitID == "" {
|
||||||
ctx.NotFound(nil)
|
return // no merge base, do nothing, do not stop the route handler, see below
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// do not report 500 server error to end users if error occurs, otherwise a PR missing ref won't be able to view.
|
||||||
headCommitID, err := ctx.Repo.GitRepo.GetRefCommitID(pull.GetGitRefName())
|
headCommitID, err := ctx.Repo.GitRepo.GetRefCommitID(pull.GetGitRefName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetRefCommitID", err)
|
log.Error("Failed to GetRefCommitID: %v, repo: %v", err, ctx.Repo.Repository.FullName())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
diffShortStat, err := gitdiff.GetDiffShortStat(ctx.Repo.GitRepo, mergeBaseCommitID, headCommitID)
|
diffShortStat, err := gitdiff.GetDiffShortStat(ctx.Repo.GitRepo, mergeBaseCommitID, headCommitID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("GetDiffShortStat", err)
|
log.Error("Failed to GetDiffShortStat: %v, repo: %v", err, ctx.Repo.Repository.FullName())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +197,7 @@ func RunnersEdit(ctx *context.Context) {
|
|||||||
ctx.ServerError("LoadAttributes", err)
|
ctx.ServerError("LoadAttributes", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !runner.Editable(ownerID, repoID) {
|
if !runner.EditableInContext(ownerID, repoID) {
|
||||||
err = errors.New("no permission to edit this runner")
|
err = errors.New("no permission to edit this runner")
|
||||||
ctx.NotFound(err)
|
ctx.NotFound(err)
|
||||||
return
|
return
|
||||||
@ -250,7 +250,7 @@ func RunnersEditPost(ctx *context.Context) {
|
|||||||
ctx.ServerError("RunnerDetailsEditPost.GetRunnerByID", err)
|
ctx.ServerError("RunnerDetailsEditPost.GetRunnerByID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !runner.Editable(ownerID, repoID) {
|
if !runner.EditableInContext(ownerID, repoID) {
|
||||||
ctx.NotFound(util.NewPermissionDeniedErrorf("no permission to edit this runner"))
|
ctx.NotFound(util.NewPermissionDeniedErrorf("no permission to edit this runner"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -304,7 +304,7 @@ func RunnerDeletePost(ctx *context.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !runner.Editable(rCtx.OwnerID, rCtx.RepoID) {
|
if !runner.EditableInContext(rCtx.OwnerID, rCtx.RepoID) {
|
||||||
ctx.NotFound(util.NewPermissionDeniedErrorf("no permission to delete this runner"))
|
ctx.NotFound(util.NewPermissionDeniedErrorf("no permission to delete this runner"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -25,4 +25,12 @@ type API interface {
|
|||||||
UpdateVariable(*context.APIContext)
|
UpdateVariable(*context.APIContext)
|
||||||
// GetRegistrationToken get registration token
|
// GetRegistrationToken get registration token
|
||||||
GetRegistrationToken(*context.APIContext)
|
GetRegistrationToken(*context.APIContext)
|
||||||
|
// CreateRegistrationToken get registration token
|
||||||
|
CreateRegistrationToken(*context.APIContext)
|
||||||
|
// ListRunners list runners
|
||||||
|
ListRunners(*context.APIContext)
|
||||||
|
// GetRunner get a runner
|
||||||
|
GetRunner(*context.APIContext)
|
||||||
|
// DeleteRunner delete runner
|
||||||
|
DeleteRunner(*context.APIContext)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,8 @@ import (
|
|||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
asymkey_service "code.gitea.io/gitea/services/asymkey"
|
||||||
"code.gitea.io/gitea/services/gitdiff"
|
"code.gitea.io/gitea/services/gitdiff"
|
||||||
|
|
||||||
|
runnerv1 "code.gitea.io/actions-proto-go/runner/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ToEmail convert models.EmailAddress to api.Email
|
// ToEmail convert models.EmailAddress to api.Email
|
||||||
@ -252,6 +254,30 @@ func ToActionArtifact(repo *repo_model.Repository, art *actions_model.ActionArti
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToActionRunner(ctx context.Context, runner *actions_model.ActionRunner) *api.ActionRunner {
|
||||||
|
status := runner.Status()
|
||||||
|
apiStatus := "offline"
|
||||||
|
if runner.IsOnline() {
|
||||||
|
apiStatus = "online"
|
||||||
|
}
|
||||||
|
labels := make([]*api.ActionRunnerLabel, len(runner.AgentLabels))
|
||||||
|
for i, label := range runner.AgentLabels {
|
||||||
|
labels[i] = &api.ActionRunnerLabel{
|
||||||
|
ID: int64(i),
|
||||||
|
Name: label,
|
||||||
|
Type: "custom",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &api.ActionRunner{
|
||||||
|
ID: runner.ID,
|
||||||
|
Name: runner.Name,
|
||||||
|
Status: apiStatus,
|
||||||
|
Busy: status == runnerv1.RunnerStatus_RUNNER_STATUS_ACTIVE,
|
||||||
|
Ephemeral: runner.Ephemeral,
|
||||||
|
Labels: labels,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
|
// ToVerification convert a git.Commit.Signature to an api.PayloadCommitVerification
|
||||||
func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification {
|
func ToVerification(ctx context.Context, c *git.Commit) *api.PayloadCommitVerification {
|
||||||
verif := asymkey_service.ParseCommitWithSignature(ctx, c)
|
verif := asymkey_service.ParseCommitWithSignature(ctx, c)
|
||||||
|
@ -6,7 +6,7 @@ package feed
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strings"
|
||||||
|
|
||||||
activities_model "code.gitea.io/gitea/models/activities"
|
activities_model "code.gitea.io/gitea/models/activities"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
@ -14,15 +14,10 @@ import (
|
|||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/cache"
|
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
func userFeedCacheKey(userID int64) string {
|
|
||||||
return fmt.Sprintf("user_feed_%d", userID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetFeedsForDashboard(ctx context.Context, opts activities_model.GetFeedsOptions) (activities_model.ActionList, int, error) {
|
func GetFeedsForDashboard(ctx context.Context, opts activities_model.GetFeedsOptions) (activities_model.ActionList, int, error) {
|
||||||
opts.DontCount = opts.RequestedTeam == nil && opts.Date == ""
|
opts.DontCount = opts.RequestedTeam == nil && opts.Date == ""
|
||||||
results, cnt, err := activities_model.GetFeeds(ctx, opts)
|
results, cnt, err := activities_model.GetFeeds(ctx, opts)
|
||||||
@ -40,7 +35,18 @@ func GetFeeds(ctx context.Context, opts activities_model.GetFeedsOptions) (activ
|
|||||||
// * Organization action: UserID=100 (the repo's org), ActUserID=1
|
// * Organization action: UserID=100 (the repo's org), ActUserID=1
|
||||||
// * Watcher action: UserID=20 (a user who is watching a repo), ActUserID=1
|
// * Watcher action: UserID=20 (a user who is watching a repo), ActUserID=1
|
||||||
func notifyWatchers(ctx context.Context, act *activities_model.Action, watchers []*repo_model.Watch, permCode, permIssue, permPR []bool) error {
|
func notifyWatchers(ctx context.Context, act *activities_model.Action, watchers []*repo_model.Watch, permCode, permIssue, permPR []bool) error {
|
||||||
// Add feed for actioner.
|
// MySQL has TEXT length limit 65535.
|
||||||
|
// Sometimes the content is "field1|field2|field3", sometimes the content is JSON (ActionMirrorSyncPush, ActionCommitRepo, ActionPushTag, etc...)
|
||||||
|
if left, right := util.EllipsisDisplayStringX(act.Content, 65535); right != "" {
|
||||||
|
if strings.HasPrefix(act.Content, `{"`) && strings.HasSuffix(act.Content, `}`) {
|
||||||
|
// FIXME: at the moment we can do nothing if the content is JSON and it is too long
|
||||||
|
act.Content = "{}"
|
||||||
|
} else {
|
||||||
|
act.Content = left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add feed for actor.
|
||||||
act.UserID = act.ActUserID
|
act.UserID = act.ActUserID
|
||||||
if err := db.Insert(ctx, act); err != nil {
|
if err := db.Insert(ctx, act); err != nil {
|
||||||
return fmt.Errorf("insert new actioner: %w", err)
|
return fmt.Errorf("insert new actioner: %w", err)
|
||||||
@ -76,24 +82,18 @@ func notifyWatchers(ctx context.Context, act *activities_model.Action, watchers
|
|||||||
if !permPR[i] {
|
if !permPR[i] {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.Insert(ctx, act); err != nil {
|
if err := db.Insert(ctx, act); err != nil {
|
||||||
return fmt.Errorf("insert new action: %w", err)
|
return fmt.Errorf("insert new action: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
total, err := activities_model.CountUserFeeds(ctx, act.UserID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("count user feeds: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = cache.GetCache().Put(userFeedCacheKey(act.UserID), strconv.FormatInt(total, 10), setting.CacheService.TTLSeconds())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyWatchersActions creates batch of actions for every watcher.
|
// NotifyWatchers creates batch of actions for every watcher.
|
||||||
func NotifyWatchers(ctx context.Context, acts ...*activities_model.Action) error {
|
func NotifyWatchers(ctx context.Context, acts ...*activities_model.Action) error {
|
||||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||||
if len(acts) == 0 {
|
if len(acts) == 0 {
|
||||||
|
@ -9,16 +9,16 @@
|
|||||||
<a class="silenced" href="#">silenced</a>
|
<a class="silenced" href="#">silenced</a>
|
||||||
</div>
|
</div>
|
||||||
<h1>Button</h1>
|
<h1>Button</h1>
|
||||||
<div>
|
".ui.button" styles:
|
||||||
Style:
|
<div class="flex-text-block tw-gap-4">
|
||||||
<label><input type="checkbox" name="button-style-compact" value="compact">compact</label>
|
<label class="gt-checkbox"><input type="radio" name="button-style-size" value="">(normal)</label>
|
||||||
<label><input type="radio" name="button-style-size" value="">(normal)</label>
|
<label class="gt-checkbox"><input type="radio" name="button-style-size" value="small">small</label>
|
||||||
<label><input type="radio" name="button-style-size" value="tiny">tiny</label>
|
<label class="gt-checkbox"><input type="radio" name="button-style-size" value="tiny">tiny</label>
|
||||||
<label><input type="radio" name="button-style-size" value="mini">mini</label>
|
<label class="gt-checkbox"><input type="radio" name="button-style-size" value="mini">mini</label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="flex-text-block tw-gap-4">
|
||||||
State:
|
<label class="gt-checkbox"><input type="checkbox" name="button-style-compact" value="compact">compact</label>
|
||||||
<label><input type="checkbox" name="button-state-disabled" value="disabled">disabled</label>
|
<label class="gt-checkbox"><input type="checkbox" name="button-state-disabled" value="disabled">disabled</label>
|
||||||
</div>
|
</div>
|
||||||
<div id="devtest-button-samples">
|
<div id="devtest-button-samples">
|
||||||
<ul class="button-sample-groups">
|
<ul class="button-sample-groups">
|
||||||
|
71
templates/devtest/markup-render.tmpl
Normal file
71
templates/devtest/markup-render.tmpl
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
{{template "devtest/devtest-header"}}
|
||||||
|
<div class="page-content devtest ui container">
|
||||||
|
{{$longCode := "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"}}
|
||||||
|
<div class="tw-flex">
|
||||||
|
<div class="tw-w-[50%] tw-p-4">
|
||||||
|
<div class="markup render-content">
|
||||||
|
Inline <code>code</code> content
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
<div class="markup render-content">
|
||||||
|
<p>content before</p>
|
||||||
|
<pre><code>Very long line with no code block or container: {{$longCode}}</code></pre>
|
||||||
|
<p>content after</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
<div class="markup render-content">
|
||||||
|
<p>content before</p>
|
||||||
|
<div class="code-block-container code-overflow-wrap">
|
||||||
|
<pre class="code-block"><code>Very long line with wrap: {{$longCode}}</code></pre>
|
||||||
|
</div>
|
||||||
|
<p>content after</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
<div class="markup render-content">
|
||||||
|
<p>content before</p>
|
||||||
|
<div class="code-block-container code-overflow-scroll">
|
||||||
|
<pre class="code-block"><code>Short line in scroll container</code></pre>
|
||||||
|
</div>
|
||||||
|
<div class="code-block-container code-overflow-scroll">
|
||||||
|
<pre class="code-block"><code>Very long line with scroll: {{$longCode}}</code></pre>
|
||||||
|
</div>
|
||||||
|
<p>content after</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tw-w-[50%] tw-p-4">
|
||||||
|
<div class="markup render-content">
|
||||||
|
<p>content before</p>
|
||||||
|
<div class="code-block-container">
|
||||||
|
<pre class="code-block"><code class="language-math">
|
||||||
|
\lim\limits_{n\rightarrow\infty}{\left(1+\frac{1}{n}\right)^n}
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
<p>content after</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
<div class="markup render-content">
|
||||||
|
<p>content before</p>
|
||||||
|
<div class="code-block-container">
|
||||||
|
<pre class="code-block"><code class="language-mermaid is-loading">
|
||||||
|
graph LR
|
||||||
|
A[Square Rect] -- Link text --> B((Circle))
|
||||||
|
A --> C(Round Rect)
|
||||||
|
B --> D{Rhombus}
|
||||||
|
C --> D
|
||||||
|
</code></pre>
|
||||||
|
</div>
|
||||||
|
<p>content after</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "devtest/devtest-footer"}}
|
@ -4,7 +4,7 @@
|
|||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
{{template "base/alert" .}}
|
{{template "base/alert" .}}
|
||||||
{{if .IsOrganizationOwner}}
|
{{if .IsOrganizationOwner}}
|
||||||
<div class="tw-text-right">
|
<div class="flex-text-block tw-justify-end">
|
||||||
<a class="ui primary button" href="{{.OrgLink}}/teams/new">{{svg "octicon-plus"}} {{ctx.Locale.Tr "org.create_new_team"}}</a>
|
<a class="ui primary button" href="{{.OrgLink}}/teams/new">{{svg "octicon-plus"}} {{ctx.Locale.Tr "org.create_new_team"}}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{{if and $.CanWriteProjects (not $.Repository.IsArchived)}}
|
{{if and $.CanWriteProjects (not $.Repository.IsArchived)}}
|
||||||
<div class="tw-flex tw-justify-between tw-mb-4">
|
<div class="flex-text-block tw-justify-between tw-mb-4">
|
||||||
<div class="small-menu-items ui compact tiny menu list-header-toggle">
|
<div class="small-menu-items ui compact tiny menu list-header-toggle">
|
||||||
<a class="item{{if not .IsShowClosed}} active{{end}}" href="?state=open&q={{$.Keyword}}">
|
<a class="item{{if not .IsShowClosed}} active{{end}}" href="?state=open&q={{$.Keyword}}">
|
||||||
{{svg "octicon-project-symlink" 16 "tw-mr-2"}}
|
{{svg "octicon-project-symlink" 16 "tw-mr-2"}}
|
||||||
@ -10,9 +10,7 @@
|
|||||||
{{ctx.Locale.PrettyNumber .ClosedCount}} {{ctx.Locale.Tr "repo.issues.closed_title"}}
|
{{ctx.Locale.PrettyNumber .ClosedCount}} {{ctx.Locale.Tr "repo.issues.closed_title"}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-text-right">
|
<a class="ui small primary button" href="{{$.Link}}/new">{{ctx.Locale.Tr "repo.projects.new"}}</a>
|
||||||
<a class="ui small primary button" href="{{$.Link}}/new">{{ctx.Locale.Tr "repo.projects.new"}}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="tw-text-right">
|
<div class="flex-text-block tw-justify-end">
|
||||||
<a class="ui cancel button" href="{{$.CancelLink}}">
|
<a class="ui cancel button" href="{{$.CancelLink}}">
|
||||||
{{ctx.Locale.Tr "repo.milestones.cancel"}}
|
{{ctx.Locale.Tr "repo.milestones.cancel"}}
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<button class="ui primary button js-btn-clone-panel">
|
<button class="ui compact primary button js-btn-clone-panel">
|
||||||
{{svg "octicon-code" 16}}
|
{{svg "octicon-code" 16}}
|
||||||
<span>{{ctx.Locale.Tr "repo.code"}}</span>
|
<span>{{ctx.Locale.Tr "repo.code"}}</span>
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
{{if .PageIsPullFiles}}
|
{{if .PageIsPullFiles}}
|
||||||
<div id="diff-commit-select" data-issuelink="{{$.Issue.Link}}" data-queryparams="?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated={{$.ShowOutdatedComments}}" data-filter_changes_by_commit="{{ctx.Locale.Tr "repo.pulls.filter_changes_by_commit"}}">
|
<div id="diff-commit-select" data-issuelink="{{$.Issue.Link}}" data-queryparams="?style={{if $.IsSplitStyle}}split{{else}}unified{{end}}&whitespace={{$.WhitespaceBehavior}}&show-outdated={{$.ShowOutdatedComments}}" data-filter_changes_by_commit="{{ctx.Locale.Tr "repo.pulls.filter_changes_by_commit"}}">
|
||||||
{{/* the following will be replaced by vue component, but this avoids any loading artifacts till the vue component is initialized */}}
|
{{/* the following will be replaced by vue component, but this avoids any loading artifacts till the vue component is initialized */}}
|
||||||
<div class="ui jump dropdown basic button custom">
|
<div class="ui jump dropdown tiny basic button custom">
|
||||||
{{svg "octicon-git-commit"}}
|
{{svg "octicon-git-commit"}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -223,6 +223,7 @@
|
|||||||
{{if and (not $.Repository.IsArchived) (not .DiffNotAvailable)}}
|
{{if and (not $.Repository.IsArchived) (not .DiffNotAvailable)}}
|
||||||
<template id="issue-comment-editor-template">
|
<template id="issue-comment-editor-template">
|
||||||
<form class="ui form comment">
|
<form class="ui form comment">
|
||||||
|
<div class="field">
|
||||||
{{template "shared/combomarkdowneditor" (dict
|
{{template "shared/combomarkdowneditor" (dict
|
||||||
"CustomInit" true
|
"CustomInit" true
|
||||||
"MarkdownPreviewInRepo" $.Repository
|
"MarkdownPreviewInRepo" $.Repository
|
||||||
@ -230,12 +231,13 @@
|
|||||||
"TextareaName" "content"
|
"TextareaName" "content"
|
||||||
"DropzoneParentContainer" ".ui.form"
|
"DropzoneParentContainer" ".ui.form"
|
||||||
)}}
|
)}}
|
||||||
|
</div>
|
||||||
{{if .IsAttachmentEnabled}}
|
{{if .IsAttachmentEnabled}}
|
||||||
<div class="field">
|
<div class="field">
|
||||||
{{template "repo/upload" .}}
|
{{template "repo/upload" .}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="tw-text-right edit buttons">
|
<div class="field flex-text-block tw-justify-end">
|
||||||
<button class="ui cancel button">{{ctx.Locale.Tr "repo.issues.cancel"}}</button>
|
<button class="ui cancel button">{{ctx.Locale.Tr "repo.issues.cancel"}}</button>
|
||||||
<button class="ui primary button">{{ctx.Locale.Tr "repo.issues.save"}}</button>
|
<button class="ui primary button">{{ctx.Locale.Tr "repo.issues.save"}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<div class="field footer">
|
<div class="field footer">
|
||||||
<div class="tw-text-right">
|
<div class="flex-text-block tw-justify-end">
|
||||||
{{if $.reply}}
|
{{if $.reply}}
|
||||||
<button class="ui submit primary tiny button btn-reply" type="submit">{{ctx.Locale.Tr "repo.diff.comment.reply"}}</button>
|
<button class="ui submit primary tiny button btn-reply" type="submit">{{ctx.Locale.Tr "repo.diff.comment.reply"}}</button>
|
||||||
<input type="hidden" name="reply" value="{{$.reply}}">
|
<input type="hidden" name="reply" value="{{$.reply}}">
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
{{$referenceUrl := printf "%s#%s" $.Issue.Link $comment.HashTag}}
|
{{$referenceUrl := printf "%s#%s" $.Issue.Link $comment.HashTag}}
|
||||||
<div class="conversation-holder" data-path="{{$comment.TreePath}}" data-side="{{if lt $comment.Line 0}}left{{else}}right{{end}}" data-idx="{{$comment.UnsignedLine}}">
|
<div class="conversation-holder" data-path="{{$comment.TreePath}}" data-side="{{if lt $comment.Line 0}}left{{else}}right{{end}}" data-idx="{{$comment.UnsignedLine}}">
|
||||||
{{if $resolved}}
|
{{if $resolved}}
|
||||||
<div class="ui attached header resolved-placeholder tw-flex tw-items-center tw-justify-between">
|
<div class="resolved-placeholder">
|
||||||
<div class="ui grey text tw-flex tw-items-center tw-flex-wrap tw-gap-1">
|
<div class="flex-text-block tw-flex-wrap grey text">
|
||||||
{{svg "octicon-check" 16 "icon tw-mr-1"}}
|
{{svg "octicon-check"}}
|
||||||
<b>{{$resolveDoer.Name}}</b> {{ctx.Locale.Tr "repo.issues.review.resolved_by"}}
|
<b>{{$resolveDoer.Name}}</b> {{ctx.Locale.Tr "repo.issues.review.resolved_by"}}
|
||||||
{{if $invalid}}
|
{{if $invalid}}
|
||||||
<!--
|
<!--
|
||||||
@ -22,35 +22,33 @@
|
|||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-flex tw-items-center tw-gap-2">
|
<div class="flex-text-block">
|
||||||
<button id="show-outdated-{{$comment.ID}}" data-comment="{{$comment.ID}}" class="ui tiny labeled button show-outdated tw-flex tw-items-center">
|
<button id="show-outdated-{{$comment.ID}}" data-comment="{{$comment.ID}}" class="btn tiny show-outdated">
|
||||||
{{svg "octicon-unfold" 16 "tw-mr-2"}}
|
{{svg "octicon-unfold" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.issues.review.show_resolved"}}
|
||||||
{{ctx.Locale.Tr "repo.issues.review.show_resolved"}}
|
|
||||||
</button>
|
</button>
|
||||||
<button id="hide-outdated-{{$comment.ID}}" data-comment="{{$comment.ID}}" class="ui tiny labeled button hide-outdated tw-flex tw-items-center tw-hidden">
|
<button id="hide-outdated-{{$comment.ID}}" data-comment="{{$comment.ID}}" class="btn tiny hide-outdated tw-hidden">
|
||||||
{{svg "octicon-fold" 16 "tw-mr-2"}}
|
{{svg "octicon-fold" 16 "tw-mr-2"}}{{ctx.Locale.Tr "repo.issues.review.hide_resolved"}}
|
||||||
{{ctx.Locale.Tr "repo.issues.review.hide_resolved"}}
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<div id="code-comments-{{$comment.ID}}" class="field comment-code-cloud {{if $resolved}}tw-hidden{{end}}">
|
<div id="code-comments-{{$comment.ID}}" class="field comment-code-cloud {{if $resolved}}tw-hidden{{end}}">
|
||||||
<div class="comment-list">
|
<div class="comment-list">
|
||||||
<ui class="ui comments">
|
<div class="ui comments">
|
||||||
{{template "repo/diff/comments" dict "root" $ "comments" .comments}}
|
{{template "repo/diff/comments" dict "root" $ "comments" .comments}}
|
||||||
</ui>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-flex tw-justify-end tw-items-center tw-gap-2 tw-mt-2 tw-flex-wrap">
|
<div class="flex-text-block tw-mt-2 tw-flex-wrap tw-justify-end">
|
||||||
<div class="ui buttons">
|
<div class="ui buttons">
|
||||||
<button class="ui icon tiny basic button previous-conversation">
|
<button class="ui icon tiny basic button previous-conversation">
|
||||||
{{svg "octicon-arrow-up" 12 "icon"}} {{ctx.Locale.Tr "repo.issues.previous"}}
|
{{svg "octicon-arrow-up" 12}} {{ctx.Locale.Tr "repo.issues.previous"}}
|
||||||
</button>
|
</button>
|
||||||
<button class="ui icon tiny basic button next-conversation">
|
<button class="ui icon tiny basic button next-conversation">
|
||||||
{{svg "octicon-arrow-down" 12 "icon"}} {{ctx.Locale.Tr "repo.issues.next"}}
|
{{svg "octicon-arrow-down" 12}} {{ctx.Locale.Tr "repo.issues.next"}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{{if and $.CanMarkConversation $hasReview (not $isReviewPending)}}
|
{{if and $.CanMarkConversation $hasReview (not $isReviewPending)}}
|
||||||
<button class="ui icon tiny basic button resolve-conversation tw-mr-0" data-origin="diff" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{$comment.ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation">
|
<button class="ui icon tiny basic button resolve-conversation" data-origin="diff" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{$comment.ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation">
|
||||||
{{if $resolved}}
|
{{if $resolved}}
|
||||||
{{ctx.Locale.Tr "repo.issues.review.un_resolve_conversation"}}
|
{{ctx.Locale.Tr "repo.issues.review.un_resolve_conversation"}}
|
||||||
{{else}}
|
{{else}}
|
||||||
@ -59,8 +57,8 @@
|
|||||||
</button>
|
</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if and $.SignedUserID (not $.Repository.IsArchived)}}
|
{{if and $.SignedUserID (not $.Repository.IsArchived)}}
|
||||||
<button class="comment-form-reply ui primary tiny labeled icon button tw-mr-0">
|
<button class="comment-form-reply ui primary icon tiny button">
|
||||||
{{svg "octicon-reply" 16 "reply icon tw-mr-1"}}{{ctx.Locale.Tr "repo.diff.comment.reply"}}
|
{{svg "octicon-reply" 12}}{{ctx.Locale.Tr "repo.diff.comment.reply"}}
|
||||||
</button>
|
</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -45,8 +45,8 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="flow-color-monochrome" class="ui labelled icon button{{if eq .Mode "monochrome"}} active{{end}}" title="{{ctx.Locale.Tr "repo.commit_graph.monochrome"}}">{{svg "material-invert-colors" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.commit_graph.monochrome"}}</button>
|
<button id="flow-color-monochrome" class="ui icon button{{if eq .Mode "monochrome"}} active{{end}}" title="{{ctx.Locale.Tr "repo.commit_graph.monochrome"}}">{{svg "material-invert-colors" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.commit_graph.monochrome"}}</button>
|
||||||
<button id="flow-color-colored" class="ui labelled icon button{{if ne .Mode "monochrome"}} active{{end}}" title="{{ctx.Locale.Tr "repo.commit_graph.color"}}">{{svg "material-palette" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.commit_graph.color"}}</button>
|
<button id="flow-color-colored" class="ui icon button{{if ne .Mode "monochrome"}} active{{end}}" title="{{ctx.Locale.Tr "repo.commit_graph.color"}}">{{svg "material-palette" 16 "tw-mr-1"}}{{ctx.Locale.Tr "repo.commit_graph.color"}}</button>
|
||||||
</div>
|
</div>
|
||||||
</h2>
|
</h2>
|
||||||
<div class="ui dividing"></div>
|
<div class="ui dividing"></div>
|
||||||
|
@ -17,18 +17,18 @@
|
|||||||
{{if eq $refGroup "pull"}}
|
{{if eq $refGroup "pull"}}
|
||||||
{{if or (not $.HidePRRefs) (SliceUtils.Contains $.SelectedBranches .Name)}}
|
{{if or (not $.HidePRRefs) (SliceUtils.Contains $.SelectedBranches .Name)}}
|
||||||
<!-- it's intended to use issues not pulls, if it's a pull you will get redirected -->
|
<!-- it's intended to use issues not pulls, if it's a pull you will get redirected -->
|
||||||
<a class="ui labelled basic tiny button" href="{{$.RepoLink}}/{{if $.Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypePullRequests}}pulls{{else}}issues{{end}}/{{.ShortName|PathEscape}}">
|
<a class="ui basic tiny button" href="{{$.RepoLink}}/{{if $.Repository.UnitEnabled ctx ctx.Consts.RepoUnitTypePullRequests}}pulls{{else}}issues{{end}}/{{.ShortName|PathEscape}}">
|
||||||
{{svg "octicon-git-pull-request"}} #{{.ShortName}}
|
{{svg "octicon-git-pull-request"}} #{{.ShortName}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{else if eq $refGroup "tags"}}
|
{{else if eq $refGroup "tags"}}
|
||||||
{{- template "repo/tag/name" dict "RepoLink" $.Repository.Link "TagName" .ShortName -}}
|
{{- template "repo/tag/name" dict "RepoLink" $.Repository.Link "TagName" .ShortName -}}
|
||||||
{{else if eq $refGroup "remotes"}}
|
{{else if eq $refGroup "remotes"}}
|
||||||
<a class="ui labelled basic tiny button" href="{{$.RepoLink}}/src/commit/{{$commit.Rev|PathEscape}}">
|
<a class="ui basic tiny button" href="{{$.RepoLink}}/src/commit/{{$commit.Rev|PathEscape}}">
|
||||||
{{svg "octicon-cross-reference"}} {{.ShortName}}
|
{{svg "octicon-cross-reference"}} {{.ShortName}}
|
||||||
</a>
|
</a>
|
||||||
{{else if eq $refGroup "heads"}}
|
{{else if eq $refGroup "heads"}}
|
||||||
<a class="ui labelled basic tiny button" href="{{$.RepoLink}}/src/branch/{{.ShortName|PathEscape}}">
|
<a class="ui basic tiny button" href="{{$.RepoLink}}/src/branch/{{.ShortName|PathEscape}}">
|
||||||
{{svg "octicon-git-branch"}} {{.ShortName}}
|
{{svg "octicon-git-branch"}} {{.ShortName}}
|
||||||
</a>
|
</a>
|
||||||
{{else}}
|
{{else}}
|
||||||
|
@ -38,20 +38,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{if not (or .IsBeingCreated .IsBroken)}}
|
{{if not (or .IsBeingCreated .IsBroken)}}
|
||||||
<div class="repo-buttons">
|
<div class="flex-text-block tw-flex-wrap">
|
||||||
{{if $.RepoTransfer}}
|
{{if $.RepoTransfer}}
|
||||||
<form method="post" action="{{$.RepoLink}}/action/accept_transfer?redirect_to={{$.RepoLink}}">
|
<form method="post" action="{{$.RepoLink}}/action/accept_transfer?redirect_to={{$.RepoLink}}">
|
||||||
{{$.CsrfTokenHtml}}
|
{{$.CsrfTokenHtml}}
|
||||||
<div data-tooltip-content="{{if $.CanUserAcceptOrRejectTransfer}}{{ctx.Locale.Tr "repo.transfer.accept_desc" $.RepoTransfer.Recipient.DisplayName}}{{else}}{{ctx.Locale.Tr "repo.transfer.no_permission_to_accept"}}{{end}}">
|
<div class="flex-text-inline" data-tooltip-content="{{if $.CanUserAcceptOrRejectTransfer}}{{ctx.Locale.Tr "repo.transfer.accept_desc" $.RepoTransfer.Recipient.DisplayName}}{{else}}{{ctx.Locale.Tr "repo.transfer.no_permission_to_accept"}}{{end}}">
|
||||||
<button type="submit" class="ui basic button {{if $.CanUserAcceptOrRejectTransfer}}primary {{end}} ok small"{{if not $.CanUserAcceptOrRejectTransfer}} disabled{{end}}>
|
<button type="submit" class="ui compact small basic button {{if $.CanUserAcceptOrRejectTransfer}}primary {{end}} ok small"{{if not $.CanUserAcceptOrRejectTransfer}} disabled{{end}}>
|
||||||
{{ctx.Locale.Tr "repo.transfer.accept"}}
|
{{ctx.Locale.Tr "repo.transfer.accept"}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<form method="post" action="{{$.RepoLink}}/action/reject_transfer?redirect_to={{$.RepoLink}}">
|
<form method="post" action="{{$.RepoLink}}/action/reject_transfer?redirect_to={{$.RepoLink}}">
|
||||||
{{$.CsrfTokenHtml}}
|
{{$.CsrfTokenHtml}}
|
||||||
<div data-tooltip-content="{{if $.CanUserAcceptOrRejectTransfer}}{{ctx.Locale.Tr "repo.transfer.reject_desc" $.RepoTransfer.Recipient.DisplayName}}{{else}}{{ctx.Locale.Tr "repo.transfer.no_permission_to_reject"}}{{end}}">
|
<div class="flex-text-inline" data-tooltip-content="{{if $.CanUserAcceptOrRejectTransfer}}{{ctx.Locale.Tr "repo.transfer.reject_desc" $.RepoTransfer.Recipient.DisplayName}}{{else}}{{ctx.Locale.Tr "repo.transfer.no_permission_to_reject"}}{{end}}">
|
||||||
<button type="submit" class="ui basic button {{if $.CanUserAcceptOrRejectTransfer}}red {{end}}ok small"{{if not $.CanUserAcceptOrRejectTransfer}} disabled{{end}}>
|
<button type="submit" class="ui compact small basic button {{if $.CanUserAcceptOrRejectTransfer}}red {{end}}ok small"{{if not $.CanUserAcceptOrRejectTransfer}} disabled{{end}}>
|
||||||
{{ctx.Locale.Tr "repo.transfer.reject"}}
|
{{ctx.Locale.Tr "repo.transfer.reject"}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,10 +3,10 @@
|
|||||||
{{template "repo/header" .}}
|
{{template "repo/header" .}}
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
{{template "base/alert" .}}
|
{{template "base/alert" .}}
|
||||||
<div class="tw-flex">
|
<div class="flex-text-block tw-flex-wrap tw-mb-2">
|
||||||
<h1 class="tw-mb-2">{{.Milestone.Name}}</h1>
|
<h1 class="tw-flex-1 tw-m-0">{{.Milestone.Name}}</h1>
|
||||||
{{if not .Repository.IsArchived}}
|
{{if not .Repository.IsArchived}}
|
||||||
<div class="tw-text-right tw-flex-1">
|
<div>
|
||||||
{{if or .CanWriteIssues .CanWritePulls}}
|
{{if or .CanWriteIssues .CanWritePulls}}
|
||||||
{{if .Milestone.IsClosed}}
|
{{if .Milestone.IsClosed}}
|
||||||
<a class="ui primary basic button link-action" href data-url="{{$.RepoLink}}/milestones/{{.MilestoneID}}/open">{{ctx.Locale.Tr "repo.milestones.open"}}
|
<a class="ui primary basic button link-action" href data-url="{{$.RepoLink}}/milestones/{{.MilestoneID}}/open">{{ctx.Locale.Tr "repo.milestones.open"}}
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
"TextareaPlaceholder" (ctx.Locale.Tr "repo.milestones.desc")
|
"TextareaPlaceholder" (ctx.Locale.Tr "repo.milestones.desc")
|
||||||
)}}
|
)}}
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-text-right">
|
<div class="flex-text-block tw-justify-end">
|
||||||
{{if .PageIsEditMilestone}}
|
{{if .PageIsEditMilestone}}
|
||||||
<a class="ui primary basic button" href="{{.RepoLink}}/milestones">
|
<a class="ui primary basic button" href="{{.RepoLink}}/milestones">
|
||||||
{{ctx.Locale.Tr "repo.milestones.cancel"}}
|
{{ctx.Locale.Tr "repo.milestones.cancel"}}
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
{{else}}
|
{{else}}
|
||||||
{{template "repo/issue/comment_tab" .}}
|
{{template "repo/issue/comment_tab" .}}
|
||||||
{{end}}
|
{{end}}
|
||||||
<div class="tw-text-right">
|
<div class="flex-text-block tw-justify-end">
|
||||||
<button class="ui primary button">
|
<button class="ui primary button">
|
||||||
{{if .PageIsComparePull}}
|
{{if .PageIsComparePull}}
|
||||||
{{ctx.Locale.Tr "repo.pulls.create"}}
|
{{ctx.Locale.Tr "repo.pulls.create"}}
|
||||||
|
@ -83,7 +83,7 @@
|
|||||||
{{template "repo/issue/comment_tab" .}}
|
{{template "repo/issue/comment_tab" .}}
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<div class="field footer">
|
<div class="field footer">
|
||||||
<div class="tw-text-right">
|
<div class="flex-text-block tw-justify-end">
|
||||||
{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .DisableStatusChange)}}
|
{{if and (or .HasIssuesOrPullsWritePermission .IsIssuePoster) (not .DisableStatusChange)}}
|
||||||
{{if .Issue.IsClosed}}
|
{{if .Issue.IsClosed}}
|
||||||
<button id="status-button" class="ui primary basic button" data-status="{{ctx.Locale.Tr "repo.issues.reopen_issue"}}" data-status-and-comment="{{ctx.Locale.Tr "repo.issues.reopen_comment_issue"}}" name="status" value="reopen">
|
<button id="status-button" class="ui primary basic button" data-status="{{ctx.Locale.Tr "repo.issues.reopen_issue"}}" data-status-and-comment="{{ctx.Locale.Tr "repo.issues.reopen_comment_issue"}}" name="status" value="reopen">
|
||||||
@ -157,7 +157,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<div class="tw-text-right edit">
|
<div class="flex-text-block tw-justify-end">
|
||||||
<button type="button" class="ui cancel button">{{ctx.Locale.Tr "repo.issues.cancel"}}</button>
|
<button type="button" class="ui cancel button">{{ctx.Locale.Tr "repo.issues.cancel"}}</button>
|
||||||
<button type="submit" class="ui primary button">{{ctx.Locale.Tr "repo.issues.save"}}</button>
|
<button type="submit" class="ui primary button">{{ctx.Locale.Tr "repo.issues.save"}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{if or $invalid $resolved}}
|
{{if or $invalid $resolved}}
|
||||||
<button id="show-outdated-{{$comment.ID}}" data-comment="{{$comment.ID}}" class="{{if not $resolved}}tw-hidden {{end}}ui compact labeled button show-outdated tw-flex tw-items-center">
|
<button id="show-outdated-{{$comment.ID}}" data-comment="{{$comment.ID}}" class="{{if not $resolved}}tw-hidden{{end}} btn tiny show-outdated">
|
||||||
{{svg "octicon-unfold" 16 "tw-mr-2"}}
|
{{svg "octicon-unfold" 16 "tw-mr-2"}}
|
||||||
{{if $resolved}}
|
{{if $resolved}}
|
||||||
{{ctx.Locale.Tr "repo.issues.review.show_resolved"}}
|
{{ctx.Locale.Tr "repo.issues.review.show_resolved"}}
|
||||||
@ -25,7 +25,7 @@
|
|||||||
{{ctx.Locale.Tr "repo.issues.review.show_outdated"}}
|
{{ctx.Locale.Tr "repo.issues.review.show_outdated"}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</button>
|
</button>
|
||||||
<button id="hide-outdated-{{$comment.ID}}" data-comment="{{$comment.ID}}" class="{{if $resolved}}tw-hidden {{end}}ui compact labeled button hide-outdated tw-flex tw-items-center">
|
<button id="hide-outdated-{{$comment.ID}}" data-comment="{{$comment.ID}}" class="{{if $resolved}}tw-hidden {{end}} btn tiny hide-outdated">
|
||||||
{{svg "octicon-fold" 16 "tw-mr-2"}}
|
{{svg "octicon-fold" 16 "tw-mr-2"}}
|
||||||
{{if $resolved}}
|
{{if $resolved}}
|
||||||
{{ctx.Locale.Tr "repo.issues.review.hide_resolved"}}
|
{{ctx.Locale.Tr "repo.issues.review.hide_resolved"}}
|
||||||
@ -109,7 +109,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="code-comment-buttons tw-flex tw-items-center tw-flex-wrap tw-mt-2 tw-mb-1 tw-mx-2">
|
<div class="flex-text-block tw-flex-wrap tw-my-2">
|
||||||
<div class="tw-flex-1">
|
<div class="tw-flex-1">
|
||||||
{{if $resolved}}
|
{{if $resolved}}
|
||||||
<div class="ui grey text">
|
<div class="ui grey text">
|
||||||
@ -118,7 +118,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
<div class="code-comment-buttons-buttons">
|
<div class="flex-text-block">
|
||||||
{{if and $.CanMarkConversation $hasReview (not $isReviewPending)}}
|
{{if and $.CanMarkConversation $hasReview (not $isReviewPending)}}
|
||||||
<button class="ui tiny basic button resolve-conversation" data-origin="timeline" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{$comment.ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation">
|
<button class="ui tiny basic button resolve-conversation" data-origin="timeline" data-action="{{if not $resolved}}Resolve{{else}}UnResolve{{end}}" data-comment-id="{{$comment.ID}}" data-update-url="{{$.RepoLink}}/issues/resolve_conversation">
|
||||||
{{if $resolved}}
|
{{if $resolved}}
|
||||||
@ -129,8 +129,8 @@
|
|||||||
</button>
|
</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if and $.SignedUserID (not $.Repository.IsArchived)}}
|
{{if and $.SignedUserID (not $.Repository.IsArchived)}}
|
||||||
<button class="comment-form-reply ui primary tiny labeled icon button tw-ml-1 tw-mr-0">
|
<button class="comment-form-reply ui primary icon tiny button">
|
||||||
{{svg "octicon-reply" 16 "reply icon tw-mr-1"}}{{ctx.Locale.Tr "repo.diff.comment.reply"}}
|
{{svg "octicon-reply" 12}}{{ctx.Locale.Tr "repo.diff.comment.reply"}}
|
||||||
</button>
|
</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
<label><strong>{{ctx.Locale.Tr "repo.issues.reference_issue.body"}}</strong></label>
|
<label><strong>{{ctx.Locale.Tr "repo.issues.reference_issue.body"}}</strong></label>
|
||||||
<textarea name="content"></textarea>
|
<textarea name="content"></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="tw-text-right">
|
<div class="flex-text-block tw-justify-end">
|
||||||
<button class="ui primary button">{{ctx.Locale.Tr "repo.issues.create"}}</button>
|
<button class="ui primary button">{{ctx.Locale.Tr "repo.issues.create"}}</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<h2 class="ui header activity-header">
|
<h2 class="ui header activity-header">
|
||||||
<span>{{DateUtils.AbsoluteLong .DateFrom}} - {{DateUtils.AbsoluteLong .DateUntil}}</span>
|
<span>{{DateUtils.AbsoluteLong .DateFrom}} - {{DateUtils.AbsoluteLong .DateUntil}}</span>
|
||||||
<!-- Period -->
|
<!-- Period -->
|
||||||
<div class="ui floating dropdown jump filter">
|
<div class="ui floating dropdown jump">
|
||||||
<div class="ui basic compact button">
|
<div class="ui basic compact button">
|
||||||
{{ctx.Locale.Tr "repo.activity.period.filter_label"}} <strong>{{.PeriodText}}</strong>
|
{{ctx.Locale.Tr "repo.activity.period.filter_label"}} <strong>{{.PeriodText}}</strong>
|
||||||
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
</div>
|
</div>
|
||||||
<div class="menu">
|
<div class="left menu">
|
||||||
<a class="{{if eq .Period "daily"}}active {{end}}item" href="{{$.RepoLink}}/activity/daily">{{ctx.Locale.Tr "repo.activity.period.daily"}}</a>
|
<a class="{{if eq .Period "daily"}}active {{end}}item" href="{{$.RepoLink}}/activity/daily">{{ctx.Locale.Tr "repo.activity.period.daily"}}</a>
|
||||||
<a class="{{if eq .Period "halfweekly"}}active {{end}}item" href="{{$.RepoLink}}/activity/halfweekly">{{ctx.Locale.Tr "repo.activity.period.halfweekly"}}</a>
|
<a class="{{if eq .Period "halfweekly"}}active {{end}}item" href="{{$.RepoLink}}/activity/halfweekly">{{ctx.Locale.Tr "repo.activity.period.halfweekly"}}</a>
|
||||||
<a class="{{if eq .Period "weekly"}}active {{end}}item" href="{{$.RepoLink}}/activity/weekly">{{ctx.Locale.Tr "repo.activity.period.weekly"}}</a>
|
<a class="{{if eq .Period "weekly"}}active {{end}}item" href="{{$.RepoLink}}/activity/weekly">{{ctx.Locale.Tr "repo.activity.period.weekly"}}</a>
|
||||||
|
@ -100,7 +100,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<span class="help">{{ctx.Locale.Tr "repo.release.prerelease_helper"}}</span>
|
<span class="help">{{ctx.Locale.Tr "repo.release.prerelease_helper"}}</span>
|
||||||
<div class="divider tw-mt-0"></div>
|
<div class="divider tw-mt-0"></div>
|
||||||
<div class="tw-flex tw-justify-end">
|
<div class="flex-text-block tw-justify-end">
|
||||||
{{if .PageIsEditRelease}}
|
{{if .PageIsEditRelease}}
|
||||||
<a class="ui small button" href="{{.RepoLink}}/releases">
|
<a class="ui small button" href="{{.RepoLink}}/releases">
|
||||||
{{ctx.Locale.Tr "repo.release.cancel"}}
|
{{ctx.Locale.Tr "repo.release.cancel"}}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
{{$canReadCode := $.Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
|
{{$canReadCode := $.Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
|
||||||
|
|
||||||
{{if $canReadReleases}}
|
{{if $canReadReleases}}
|
||||||
<div class="tw-flex">
|
<div class="flex-text-block">
|
||||||
<div class="tw-flex-1 tw-flex tw-items-center">
|
<div class="tw-flex-1 tw-flex tw-items-center">
|
||||||
<h2 class="ui compact small menu small-menu-items">
|
<h2 class="ui compact small menu small-menu-items">
|
||||||
<a class="{{if and .PageIsReleaseList (not .PageIsSingleTag)}}active {{end}}item" href="{{.RepoLink}}/releases">{{ctx.Locale.PrettyNumber .NumReleases}} {{ctx.Locale.TrN .NumReleases "repo.release" "repo.releases"}}</a>
|
<a class="{{if and .PageIsReleaseList (not .PageIsSingleTag)}}active {{end}}item" href="{{.RepoLink}}/releases">{{ctx.Locale.PrettyNumber .NumReleases}} {{ctx.Locale.TrN .NumReleases "repo.release" "repo.releases"}}</a>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<form hx-boost="true" hx-target="this" method="post" action="{{$.RepoLink}}/action/{{if $.IsStaringRepo}}unstar{{else}}star{{end}}">
|
<form class="flex-text-inline" hx-boost="true" hx-target="this" method="post" action="{{$.RepoLink}}/action/{{if $.IsStaringRepo}}unstar{{else}}star{{end}}">
|
||||||
<div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.star_guest_user"}}"{{end}}>
|
<div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.star_guest_user"}}"{{end}}>
|
||||||
{{$buttonText := ctx.Locale.Tr "repo.star"}}
|
{{$buttonText := ctx.Locale.Tr "repo.star"}}
|
||||||
{{if $.IsStaringRepo}}{{$buttonText = ctx.Locale.Tr "repo.unstar"}}{{end}}
|
{{if $.IsStaringRepo}}{{$buttonText = ctx.Locale.Tr "repo.unstar"}}{{end}}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<a class="ui label basic tiny button{{if .IsRelease}} primary{{end}}" href="{{.RepoLink}}/src/tag/{{.TagName|PathEscape}}">
|
<a class="ui basic label tw-p-1 {{if .IsRelease}}primary{{end}}" href="{{.RepoLink}}/src/tag/{{.TagName|PathEscape}}">
|
||||||
{{svg "octicon-tag"}} {{.TagName}}
|
{{svg "octicon-tag"}} {{.TagName}}
|
||||||
</a>
|
</a>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<form hx-boost="true" hx-target="this" method="post" action="{{$.RepoLink}}/action/{{if $.IsWatchingRepo}}unwatch{{else}}watch{{end}}">
|
<form class="flex-text-inline" hx-boost="true" hx-target="this" method="post" action="{{$.RepoLink}}/action/{{if $.IsWatchingRepo}}unwatch{{else}}watch{{end}}">
|
||||||
<div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.watch_guest_user"}}"{{end}}>
|
<div class="ui labeled button" {{if not $.IsSigned}}data-tooltip-content="{{ctx.Locale.Tr "repo.watch_guest_user"}}"{{end}}>
|
||||||
{{$buttonText := ctx.Locale.Tr "repo.watch"}}
|
{{$buttonText := ctx.Locale.Tr "repo.watch"}}
|
||||||
{{if $.IsWatchingRepo}}{{$buttonText = ctx.Locale.Tr "repo.unwatch"}}{{end}}
|
{{if $.IsWatchingRepo}}{{$buttonText = ctx.Locale.Tr "repo.unwatch"}}{{end}}
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
<input name="message" aria-label="{{ctx.Locale.Tr "repo.wiki.default_commit_message"}}" placeholder="{{ctx.Locale.Tr "repo.wiki.default_commit_message"}}">
|
<input name="message" aria-label="{{ctx.Locale.Tr "repo.wiki.default_commit_message"}}" placeholder="{{ctx.Locale.Tr "repo.wiki.default_commit_message"}}">
|
||||||
</div>
|
</div>
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
<div class="tw-text-right">
|
<div class="flex-text-block tw-justify-end">
|
||||||
<a class="ui basic cancel button" href="{{.Link}}">{{ctx.Locale.Tr "cancel"}}</a>
|
<a class="ui basic cancel button" href="{{.Link}}">{{ctx.Locale.Tr "cancel"}}</a>
|
||||||
<button class="ui primary button">{{ctx.Locale.Tr "repo.wiki.save_page"}}</button>
|
<button class="ui primary button">{{ctx.Locale.Tr "repo.wiki.save_page"}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -3,18 +3,18 @@
|
|||||||
{{template "repo/header" .}}
|
{{template "repo/header" .}}
|
||||||
{{$title := .title}}
|
{{$title := .title}}
|
||||||
<div class="ui container">
|
<div class="ui container">
|
||||||
<div class="ui stackable grid">
|
<div class="ui dividing header flex-text-block tw-flex-wrap tw-justify-between">
|
||||||
<div class="ui eight wide column">
|
<div class="flex-text-block">
|
||||||
<div class="ui header">
|
<a class="ui basic button tw-px-3" title="{{ctx.Locale.Tr "repo.wiki.back_to_wiki"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}">{{svg "octicon-home"}}</a>
|
||||||
<a class="file-revisions-btn ui basic button" title="{{ctx.Locale.Tr "repo.wiki.back_to_wiki"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}">{{if .revision}}<span>{{.revision}}</span> {{end}}{{svg "octicon-home"}}</a>
|
<div class="tw-flex-1 gt-ellipsis">
|
||||||
{{$title}}
|
{{$title}}
|
||||||
<div class="ui sub header tw-break-anywhere">
|
<div class="ui sub header gt-ellipsis">
|
||||||
{{$timeSince := DateUtils.TimeSince .Author.When}}
|
{{$timeSince := DateUtils.TimeSince .Author.When}}
|
||||||
{{ctx.Locale.Tr "repo.wiki.last_commit_info" .Author.Name $timeSince}}
|
{{ctx.Locale.Tr "repo.wiki.last_commit_info" .Author.Name $timeSince}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="ui eight wide column tw-text-right">
|
<div>
|
||||||
{{template "repo/clone_panel" .}}
|
{{template "repo/clone_panel" .}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -33,7 +33,7 @@
|
|||||||
<div class="ui dividing header">
|
<div class="ui dividing header">
|
||||||
<div class="flex-text-block tw-flex-wrap tw-justify-end">
|
<div class="flex-text-block tw-flex-wrap tw-justify-end">
|
||||||
<div class="flex-text-block tw-flex-1 tw-min-w-[300px]">
|
<div class="flex-text-block tw-flex-1 tw-min-w-[300px]">
|
||||||
<a class="file-revisions-btn ui basic button" title="{{ctx.Locale.Tr "repo.wiki.file_revision"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_revision" >{{if .CommitCount}}<span>{{.CommitCount}}</span> {{end}}{{svg "octicon-history"}}</a>
|
<a class="ui basic button tw-px-3 tw-gap-3" title="{{ctx.Locale.Tr "repo.wiki.file_revision"}}" href="{{.RepoLink}}/wiki/{{.PageURL}}?action=_revision" >{{if .CommitCount}}<span>{{.CommitCount}}</span> {{end}}{{svg "octicon-history"}}</a>
|
||||||
<div class="tw-flex-1 gt-ellipsis">
|
<div class="tw-flex-1 gt-ellipsis">
|
||||||
{{$title}}
|
{{$title}}
|
||||||
<div class="ui sub header gt-ellipsis">
|
<div class="ui sub header gt-ellipsis">
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
<div class="header">
|
<div class="header">
|
||||||
Registration Token
|
Registration Token
|
||||||
</div>
|
</div>
|
||||||
<div class="ui input">
|
<div class="ui action input">
|
||||||
<input type="text" value="{{.RegistrationToken}}" readonly>
|
<input type="text" value="{{.RegistrationToken}}" readonly>
|
||||||
<button class="ui basic label button" aria-label="{{ctx.Locale.Tr "copy"}}" data-clipboard-text="{{.RegistrationToken}}">
|
<button class="ui basic label button" aria-label="{{ctx.Locale.Tr "copy"}}" data-clipboard-text="{{.RegistrationToken}}">
|
||||||
{{svg "octicon-copy" 14}}
|
{{svg "octicon-copy" 14}}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{{- /* we do not need to set for/id here, global aria init code will add them automatically */ -}}
|
{{- /* we do not need to set for/id here, global aria init code will add them automatically */ -}}
|
||||||
<label>{{.LabelText}}</label>
|
<label>{{.LabelText}}</label>
|
||||||
<input class="avatar-file-with-cropper" name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp">
|
<input class="avatar-file-with-cropper" name="avatar" type="file" accept="image/png,image/jpeg,image/gif,image/webp" data-global-init="initAvatarUploader">
|
||||||
{{- /* the cropper-panel must be next sibling of the input "avatar" */ -}}
|
{{- /* the cropper-panel must be next sibling of the input "avatar" */ -}}
|
||||||
<div class="cropper-panel tw-hidden">
|
<div class="cropper-panel tw-hidden">
|
||||||
<div class="tw-my-2">{{ctx.Locale.Tr "settings.cropper_prompt"}}</div>
|
<div class="tw-my-2">{{ctx.Locale.Tr "settings.cropper_prompt"}}</div>
|
||||||
|
582
templates/swagger/v1_json.tmpl
generated
582
templates/swagger/v1_json.tmpl
generated
@ -75,6 +75,108 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/admin/actions/runners": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "Get all runners",
|
||||||
|
"operationId": "getAdminRunners",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/definitions/ActionRunnersResponse"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/admin/actions/runners/registration-token": {
|
||||||
|
"post": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "Get an global actions runner registration token",
|
||||||
|
"operationId": "adminCreateRunnerRegistrationToken",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/RegistrationToken"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/admin/actions/runners/{runner_id}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "Get an global runner",
|
||||||
|
"operationId": "getAdminRunner",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the runner",
|
||||||
|
"name": "runner_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/definitions/ActionRunner"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"admin"
|
||||||
|
],
|
||||||
|
"summary": "Delete an global runner",
|
||||||
|
"operationId": "deleteAdminRunner",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the runner",
|
||||||
|
"name": "runner_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "runner has been deleted"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/admin/cron": {
|
"/admin/cron": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -1697,6 +1799,38 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/orgs/{org}/actions/runners": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"organization"
|
||||||
|
],
|
||||||
|
"summary": "Get org-level runners",
|
||||||
|
"operationId": "getOrgRunners",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the organization",
|
||||||
|
"name": "org",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/definitions/ActionRunnersResponse"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/orgs/{org}/actions/runners/registration-token": {
|
"/orgs/{org}/actions/runners/registration-token": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -1721,6 +1855,106 @@
|
|||||||
"$ref": "#/responses/RegistrationToken"
|
"$ref": "#/responses/RegistrationToken"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"organization"
|
||||||
|
],
|
||||||
|
"summary": "Get an organization's actions runner registration token",
|
||||||
|
"operationId": "orgCreateRunnerRegistrationToken",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the organization",
|
||||||
|
"name": "org",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/RegistrationToken"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/orgs/{org}/actions/runners/{runner_id}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"organization"
|
||||||
|
],
|
||||||
|
"summary": "Get an org-level runner",
|
||||||
|
"operationId": "getOrgRunner",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the organization",
|
||||||
|
"name": "org",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the runner",
|
||||||
|
"name": "runner_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/definitions/ActionRunner"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"organization"
|
||||||
|
],
|
||||||
|
"summary": "Delete an org-level runner",
|
||||||
|
"operationId": "deleteOrgRunner",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the organization",
|
||||||
|
"name": "org",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the runner",
|
||||||
|
"name": "runner_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "runner has been deleted"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/orgs/{org}/actions/secrets": {
|
"/orgs/{org}/actions/secrets": {
|
||||||
@ -4331,6 +4565,45 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/repos/{owner}/{repo}/actions/runners": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Get repo-level runners",
|
||||||
|
"operationId": "getRepoRunners",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/definitions/ActionRunnersResponse"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/repos/{owner}/{repo}/actions/runners/registration-token": {
|
"/repos/{owner}/{repo}/actions/runners/registration-token": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -4362,6 +4635,127 @@
|
|||||||
"$ref": "#/responses/RegistrationToken"
|
"$ref": "#/responses/RegistrationToken"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Get a repository's actions runner registration token",
|
||||||
|
"operationId": "repoCreateRunnerRegistrationToken",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/RegistrationToken"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/repos/{owner}/{repo}/actions/runners/{runner_id}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Get an repo-level runner",
|
||||||
|
"operationId": "getRepoRunner",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the runner",
|
||||||
|
"name": "runner_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/definitions/ActionRunner"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"repository"
|
||||||
|
],
|
||||||
|
"summary": "Delete an repo-level runner",
|
||||||
|
"operationId": "deleteRepoRunner",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "owner of the repo",
|
||||||
|
"name": "owner",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "name of the repo",
|
||||||
|
"name": "repo",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the runner",
|
||||||
|
"name": "runner_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "runner has been deleted"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/repos/{owner}/{repo}/actions/runs/{run}/artifacts": {
|
"/repos/{owner}/{repo}/actions/runs/{run}/artifacts": {
|
||||||
@ -4559,7 +4953,7 @@
|
|||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"204": {
|
"204": {
|
||||||
"description": "delete one secret of the organization"
|
"description": "delete one secret of the repository"
|
||||||
},
|
},
|
||||||
"400": {
|
"400": {
|
||||||
"$ref": "#/responses/error"
|
"$ref": "#/responses/error"
|
||||||
@ -16869,6 +17263,29 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/user/actions/runners": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Get user-level runners",
|
||||||
|
"operationId": "getUserRunners",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/definitions/ActionRunnersResponse"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/user/actions/runners/registration-token": {
|
"/user/actions/runners/registration-token": {
|
||||||
"get": {
|
"get": {
|
||||||
"produces": [
|
"produces": [
|
||||||
@ -16884,6 +17301,83 @@
|
|||||||
"$ref": "#/responses/RegistrationToken"
|
"$ref": "#/responses/RegistrationToken"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Get an user's actions runner registration token",
|
||||||
|
"operationId": "userCreateRunnerRegistrationToken",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/RegistrationToken"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/user/actions/runners/{runner_id}": {
|
||||||
|
"get": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Get an user-level runner",
|
||||||
|
"operationId": "getUserRunner",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the runner",
|
||||||
|
"name": "runner_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/definitions/ActionRunner"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"user"
|
||||||
|
],
|
||||||
|
"summary": "Delete an user-level runner",
|
||||||
|
"operationId": "deleteUserRunner",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "id of the runner",
|
||||||
|
"name": "runner_id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "runner has been deleted"
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"$ref": "#/responses/error"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"$ref": "#/responses/notFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/user/actions/secrets/{secretname}": {
|
"/user/actions/secrets/{secretname}": {
|
||||||
@ -19377,6 +19871,80 @@
|
|||||||
},
|
},
|
||||||
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
},
|
},
|
||||||
|
"ActionRunner": {
|
||||||
|
"description": "ActionRunner represents a Runner",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"busy": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "Busy"
|
||||||
|
},
|
||||||
|
"ephemeral": {
|
||||||
|
"type": "boolean",
|
||||||
|
"x-go-name": "Ephemeral"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "ID"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/ActionRunnerLabel"
|
||||||
|
},
|
||||||
|
"x-go-name": "Labels"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Name"
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Status"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
|
"ActionRunnerLabel": {
|
||||||
|
"description": "ActionRunnerLabel represents a Runner Label",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "ID"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Name"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string",
|
||||||
|
"x-go-name": "Type"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
|
"ActionRunnersResponse": {
|
||||||
|
"description": "ActionRunnersResponse returns Runners",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"runners": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/ActionRunner"
|
||||||
|
},
|
||||||
|
"x-go-name": "Entries"
|
||||||
|
},
|
||||||
|
"total_count": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"x-go-name": "TotalCount"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"x-go-package": "code.gitea.io/gitea/modules/structs"
|
||||||
|
},
|
||||||
"ActionTask": {
|
"ActionTask": {
|
||||||
"description": "ActionTask represents a ActionTask",
|
"description": "ActionTask represents a ActionTask",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -27409,6 +27977,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Runner": {
|
||||||
|
"description": "Runner",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ActionRunner"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RunnerList": {
|
||||||
|
"description": "RunnerList",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/ActionRunnersResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
"SearchResults": {
|
"SearchResults": {
|
||||||
"description": "SearchResults",
|
"description": "SearchResults",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{{if .SignedUser.CanCreateOrganization}}
|
{{if .SignedUser.CanCreateOrganization}}
|
||||||
<a class="item" href="{{AppSubUrl}}/org/create">
|
<a class="item" href="{{AppSubUrl}}/org/create">
|
||||||
{{svg "octicon-plus"}} {{ctx.Locale.Tr "new_org"}}
|
{{svg "octicon-plus" 16 "tw-ml-1 tw-mr-5"}}{{ctx.Locale.Tr "new_org"}}
|
||||||
</a>
|
</a>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
@ -77,7 +77,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{if .ContextUser.IsOrganization}}
|
{{if .ContextUser.IsOrganization}}
|
||||||
<div class="right menu">
|
<div class="right menu tw-flex-wrap tw-justify-end">
|
||||||
<a class="{{if .PageIsNews}}active {{end}}item tw-ml-auto" href="{{.ContextUser.DashboardLink}}{{if .Team}}/{{PathEscape .Team.Name}}{{end}}">
|
<a class="{{if .PageIsNews}}active {{end}}item tw-ml-auto" href="{{.ContextUser.DashboardLink}}{{if .Team}}/{{PathEscape .Team.Name}}{{end}}">
|
||||||
{{svg "octicon-rss"}} {{ctx.Locale.Tr "activities"}}
|
{{svg "octicon-rss"}} {{ctx.Locale.Tr "activities"}}
|
||||||
</a>
|
</a>
|
||||||
@ -98,7 +98,7 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<a class="ui primary basic button" href="{{.ContextUser.HomeLink}}" title="{{ctx.Locale.Tr "home.view_home" .ContextUser.Name}}">
|
<a class="ui primary basic button" href="{{.ContextUser.HomeLink}}" title="{{ctx.Locale.Tr "home.view_home" .ContextUser.Name}}">
|
||||||
{{ctx.Locale.Tr "home.view_home" (.ContextUser.ShortName 40)}}
|
{{ctx.Locale.Tr "home.view_home" (.ContextUser.ShortName 20)}}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
332
tests/integration/api_actions_runner_test.go
Normal file
332
tests/integration/api_actions_runner_test.go
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
// Copyright 2025 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAPIActionsRunner(t *testing.T) {
|
||||||
|
t.Run("AdminRunner", testActionsRunnerAdmin)
|
||||||
|
t.Run("UserRunner", testActionsRunnerUser)
|
||||||
|
t.Run("OwnerRunner", testActionsRunnerOwner)
|
||||||
|
t.Run("RepoRunner", testActionsRunnerRepo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testActionsRunnerAdmin(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
adminUsername := "user1"
|
||||||
|
token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
|
||||||
|
req := NewRequest(t, "POST", "/api/v1/admin/actions/runners/registration-token").AddTokenAuth(token)
|
||||||
|
tokenResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
var registrationToken struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
DecodeJSON(t, tokenResp, ®istrationToken)
|
||||||
|
assert.NotEmpty(t, registrationToken.Token)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", "/api/v1/admin/actions/runners").AddTokenAuth(token)
|
||||||
|
runnerListResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
runnerList := api.ActionRunnersResponse{}
|
||||||
|
DecodeJSON(t, runnerListResp, &runnerList)
|
||||||
|
|
||||||
|
assert.Len(t, runnerList.Entries, 4)
|
||||||
|
|
||||||
|
idx := slices.IndexFunc(runnerList.Entries, func(e *api.ActionRunner) bool { return e.ID == 34349 })
|
||||||
|
require.NotEqual(t, -1, idx)
|
||||||
|
expectedRunner := runnerList.Entries[idx]
|
||||||
|
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Name)
|
||||||
|
assert.False(t, expectedRunner.Ephemeral)
|
||||||
|
assert.Len(t, expectedRunner.Labels, 2)
|
||||||
|
assert.Equal(t, "runner_to_be_deleted", expectedRunner.Labels[0].Name)
|
||||||
|
assert.Equal(t, "linux", expectedRunner.Labels[1].Name)
|
||||||
|
|
||||||
|
// Verify all returned runners can be requested and deleted
|
||||||
|
for _, runnerEntry := range runnerList.Entries {
|
||||||
|
// Verify get the runner by id
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
|
||||||
|
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
runner := api.ActionRunner{}
|
||||||
|
DecodeJSON(t, runnerResp, &runner)
|
||||||
|
|
||||||
|
assert.Equal(t, runnerEntry.Name, runner.Name)
|
||||||
|
assert.Equal(t, runnerEntry.ID, runner.ID)
|
||||||
|
assert.Equal(t, runnerEntry.Ephemeral, runner.Ephemeral)
|
||||||
|
assert.ElementsMatch(t, runnerEntry.Labels, runner.Labels)
|
||||||
|
|
||||||
|
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
// Verify runner deletion
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/admin/actions/runners/%d", runnerEntry.ID)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testActionsRunnerUser(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
userUsername := "user1"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteUser)
|
||||||
|
req := NewRequest(t, "POST", "/api/v1/user/actions/runners/registration-token").AddTokenAuth(token)
|
||||||
|
tokenResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
var registrationToken struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
DecodeJSON(t, tokenResp, ®istrationToken)
|
||||||
|
assert.NotEmpty(t, registrationToken.Token)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", "/api/v1/user/actions/runners").AddTokenAuth(token)
|
||||||
|
runnerListResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
runnerList := api.ActionRunnersResponse{}
|
||||||
|
DecodeJSON(t, runnerListResp, &runnerList)
|
||||||
|
|
||||||
|
assert.Len(t, runnerList.Entries, 1)
|
||||||
|
assert.Equal(t, "runner_to_be_deleted-user", runnerList.Entries[0].Name)
|
||||||
|
assert.Equal(t, int64(34346), runnerList.Entries[0].ID)
|
||||||
|
assert.False(t, runnerList.Entries[0].Ephemeral)
|
||||||
|
assert.Len(t, runnerList.Entries[0].Labels, 2)
|
||||||
|
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
|
||||||
|
assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name)
|
||||||
|
|
||||||
|
// Verify get the runner by id
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||||
|
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
runner := api.ActionRunner{}
|
||||||
|
DecodeJSON(t, runnerResp, &runner)
|
||||||
|
|
||||||
|
assert.Equal(t, "runner_to_be_deleted-user", runner.Name)
|
||||||
|
assert.Equal(t, int64(34346), runner.ID)
|
||||||
|
assert.False(t, runner.Ephemeral)
|
||||||
|
assert.Len(t, runner.Labels, 2)
|
||||||
|
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||||
|
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||||
|
|
||||||
|
// Verify delete the runner by id
|
||||||
|
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
// Verify runner deletion
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testActionsRunnerOwner(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
t.Run("GetRunner", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
|
||||||
|
// Verify get the runner by id with read scope
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
|
||||||
|
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
runner := api.ActionRunner{}
|
||||||
|
DecodeJSON(t, runnerResp, &runner)
|
||||||
|
|
||||||
|
assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
|
||||||
|
assert.Equal(t, int64(34347), runner.ID)
|
||||||
|
assert.False(t, runner.Ephemeral)
|
||||||
|
assert.Len(t, runner.Labels, 2)
|
||||||
|
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||||
|
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Access", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteOrganization)
|
||||||
|
req := NewRequest(t, "POST", "/api/v1/orgs/org3/actions/runners/registration-token").AddTokenAuth(token)
|
||||||
|
tokenResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
var registrationToken struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
DecodeJSON(t, tokenResp, ®istrationToken)
|
||||||
|
assert.NotEmpty(t, registrationToken.Token)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", "/api/v1/orgs/org3/actions/runners").AddTokenAuth(token)
|
||||||
|
runnerListResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
runnerList := api.ActionRunnersResponse{}
|
||||||
|
DecodeJSON(t, runnerListResp, &runnerList)
|
||||||
|
|
||||||
|
assert.Len(t, runnerList.Entries, 1)
|
||||||
|
assert.Equal(t, "runner_to_be_deleted-org", runnerList.Entries[0].Name)
|
||||||
|
assert.Equal(t, int64(34347), runnerList.Entries[0].ID)
|
||||||
|
assert.False(t, runnerList.Entries[0].Ephemeral)
|
||||||
|
assert.Len(t, runnerList.Entries[0].Labels, 2)
|
||||||
|
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
|
||||||
|
assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name)
|
||||||
|
|
||||||
|
// Verify get the runner by id
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||||
|
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
runner := api.ActionRunner{}
|
||||||
|
DecodeJSON(t, runnerResp, &runner)
|
||||||
|
|
||||||
|
assert.Equal(t, "runner_to_be_deleted-org", runner.Name)
|
||||||
|
assert.Equal(t, int64(34347), runner.ID)
|
||||||
|
assert.False(t, runner.Ephemeral)
|
||||||
|
assert.Len(t, runner.Labels, 2)
|
||||||
|
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||||
|
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||||
|
|
||||||
|
// Verify delete the runner by id
|
||||||
|
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
// Verify runner deletion
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DeleteReadScopeForbidden", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
|
||||||
|
|
||||||
|
// Verify delete the runner by id is forbidden with read scope
|
||||||
|
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("GetRepoScopeForbidden", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
// Verify get the runner by id with read scope
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34347)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("GetAdminRunner", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
|
||||||
|
// Verify get a runner by id of different entity is not found
|
||||||
|
// runner.EditableInContext(ownerID, repoID) false
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34349)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DeleteAdminRunner", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteOrganization)
|
||||||
|
// Verify delete a runner by id of different entity is not found
|
||||||
|
// runner.EditableInContext(ownerID, repoID) false
|
||||||
|
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/orgs/org3/actions/runners/%d", 34349)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testActionsRunnerRepo(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
t.Run("GetRunner", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
// Verify get the runner by id with read scope
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
|
||||||
|
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
runner := api.ActionRunner{}
|
||||||
|
DecodeJSON(t, runnerResp, &runner)
|
||||||
|
|
||||||
|
assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
|
||||||
|
assert.Equal(t, int64(34348), runner.ID)
|
||||||
|
assert.False(t, runner.Ephemeral)
|
||||||
|
assert.Len(t, runner.Labels, 2)
|
||||||
|
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||||
|
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Access", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
req := NewRequest(t, "POST", "/api/v1/repos/user2/repo1/actions/runners/registration-token").AddTokenAuth(token)
|
||||||
|
tokenResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
var registrationToken struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
|
DecodeJSON(t, tokenResp, ®istrationToken)
|
||||||
|
assert.NotEmpty(t, registrationToken.Token)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/actions/runners").AddTokenAuth(token)
|
||||||
|
runnerListResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
runnerList := api.ActionRunnersResponse{}
|
||||||
|
DecodeJSON(t, runnerListResp, &runnerList)
|
||||||
|
|
||||||
|
assert.Len(t, runnerList.Entries, 1)
|
||||||
|
assert.Equal(t, "runner_to_be_deleted-repo1", runnerList.Entries[0].Name)
|
||||||
|
assert.Equal(t, int64(34348), runnerList.Entries[0].ID)
|
||||||
|
assert.False(t, runnerList.Entries[0].Ephemeral)
|
||||||
|
assert.Len(t, runnerList.Entries[0].Labels, 2)
|
||||||
|
assert.Equal(t, "runner_to_be_deleted", runnerList.Entries[0].Labels[0].Name)
|
||||||
|
assert.Equal(t, "linux", runnerList.Entries[0].Labels[1].Name)
|
||||||
|
|
||||||
|
// Verify get the runner by id
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||||
|
runnerResp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
runner := api.ActionRunner{}
|
||||||
|
DecodeJSON(t, runnerResp, &runner)
|
||||||
|
|
||||||
|
assert.Equal(t, "runner_to_be_deleted-repo1", runner.Name)
|
||||||
|
assert.Equal(t, int64(34348), runner.ID)
|
||||||
|
assert.False(t, runner.Ephemeral)
|
||||||
|
assert.Len(t, runner.Labels, 2)
|
||||||
|
assert.Equal(t, "runner_to_be_deleted", runner.Labels[0].Name)
|
||||||
|
assert.Equal(t, "linux", runner.Labels[1].Name)
|
||||||
|
|
||||||
|
// Verify delete the runner by id
|
||||||
|
req = NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
|
|
||||||
|
// Verify runner deletion
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", runnerList.Entries[0].ID)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DeleteReadScopeForbidden", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
// Verify delete the runner by id is forbidden with read scope
|
||||||
|
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("GetOrganizationScopeForbidden", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadOrganization)
|
||||||
|
// Verify get the runner by id with read scope
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34348)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("GetAdminRunnerNotFound", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
// Verify get a runner by id of different entity is not found
|
||||||
|
// runner.EditableInContext(ownerID, repoID) false
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("DeleteAdminRunnerNotFound", func(t *testing.T) {
|
||||||
|
userUsername := "user2"
|
||||||
|
token := getUserToken(t, userUsername, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
// Verify delete a runner by id of different entity is not found
|
||||||
|
// runner.EditableInContext(ownerID, repoID) false
|
||||||
|
req := NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/repos/user2/repo1/actions/runners/%d", 34349)).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
})
|
||||||
|
}
|
@ -23,6 +23,7 @@ import (
|
|||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPackageSwift(t *testing.T) {
|
func TestPackageSwift(t *testing.T) {
|
||||||
@ -34,6 +35,7 @@ func TestPackageSwift(t *testing.T) {
|
|||||||
packageName := "test_package"
|
packageName := "test_package"
|
||||||
packageID := packageScope + "." + packageName
|
packageID := packageScope + "." + packageName
|
||||||
packageVersion := "1.0.3"
|
packageVersion := "1.0.3"
|
||||||
|
packageVersion2 := "1.0.4"
|
||||||
packageAuthor := "KN4CK3R"
|
packageAuthor := "KN4CK3R"
|
||||||
packageDescription := "Gitea Test Package"
|
packageDescription := "Gitea Test Package"
|
||||||
packageRepositoryURL := "https://gitea.io/gitea/gitea"
|
packageRepositoryURL := "https://gitea.io/gitea/gitea"
|
||||||
@ -183,6 +185,94 @@ func TestPackageSwift(t *testing.T) {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("UploadMultipart", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
uploadPackage := func(t *testing.T, url string, expectedStatus int, sr io.Reader, metadata string) {
|
||||||
|
var body bytes.Buffer
|
||||||
|
mpw := multipart.NewWriter(&body)
|
||||||
|
|
||||||
|
// Read the source archive content
|
||||||
|
sourceContent, err := io.ReadAll(sr)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
mpw.WriteField("source-archive", string(sourceContent))
|
||||||
|
|
||||||
|
if metadata != "" {
|
||||||
|
mpw.WriteField("metadata", metadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
mpw.Close()
|
||||||
|
|
||||||
|
req := NewRequestWithBody(t, "PUT", url, &body).
|
||||||
|
SetHeader("Content-Type", mpw.FormDataContentType()).
|
||||||
|
SetHeader("Accept", swift_router.AcceptJSON).
|
||||||
|
AddBasicAuth(user.Name)
|
||||||
|
MakeRequest(t, req, expectedStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
createArchive := func(files map[string]string) *bytes.Buffer {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
zw := zip.NewWriter(&buf)
|
||||||
|
for filename, content := range files {
|
||||||
|
w, _ := zw.Create(filename)
|
||||||
|
w.Write([]byte(content))
|
||||||
|
}
|
||||||
|
zw.Close()
|
||||||
|
return &buf
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadURL := fmt.Sprintf("%s/%s/%s/%s", url, packageScope, packageName, packageVersion2)
|
||||||
|
|
||||||
|
req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{}))
|
||||||
|
MakeRequest(t, req, http.StatusUnauthorized)
|
||||||
|
|
||||||
|
// Test with metadata as form field
|
||||||
|
uploadPackage(
|
||||||
|
t,
|
||||||
|
uploadURL,
|
||||||
|
http.StatusCreated,
|
||||||
|
createArchive(map[string]string{
|
||||||
|
"Package.swift": contentManifest1,
|
||||||
|
"Package@swift-5.6.swift": contentManifest2,
|
||||||
|
}),
|
||||||
|
`{"name":"`+packageName+`","version":"`+packageVersion2+`","description":"`+packageDescription+`","codeRepository":"`+packageRepositoryURL+`","author":{"givenName":"`+packageAuthor+`"},"repositoryURLs":["`+packageRepositoryURL+`"]}`,
|
||||||
|
)
|
||||||
|
|
||||||
|
pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeSwift)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
require.Len(t, pvs, 2) // ATTENTION: many subtests are unable to run separately, they depend on the results of previous tests
|
||||||
|
thisPackageVersion := pvs[0]
|
||||||
|
pd, err := packages.GetPackageDescriptor(db.DefaultContext, thisPackageVersion)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, pd.SemVer)
|
||||||
|
assert.Equal(t, packageID, pd.Package.Name)
|
||||||
|
assert.Equal(t, packageVersion2, pd.Version.Version)
|
||||||
|
assert.IsType(t, &swift_module.Metadata{}, pd.Metadata)
|
||||||
|
metadata := pd.Metadata.(*swift_module.Metadata)
|
||||||
|
assert.Equal(t, packageDescription, metadata.Description)
|
||||||
|
assert.Len(t, metadata.Manifests, 2)
|
||||||
|
assert.Equal(t, contentManifest1, metadata.Manifests[""].Content)
|
||||||
|
assert.Equal(t, contentManifest2, metadata.Manifests["5.6"].Content)
|
||||||
|
assert.Len(t, pd.VersionProperties, 1)
|
||||||
|
assert.Equal(t, packageRepositoryURL, pd.VersionProperties.GetByName(swift_module.PropertyRepositoryURL))
|
||||||
|
|
||||||
|
pfs, err := packages.GetFilesByVersionID(db.DefaultContext, thisPackageVersion.ID)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Len(t, pfs, 1)
|
||||||
|
assert.Equal(t, fmt.Sprintf("%s-%s.zip", packageName, packageVersion2), pfs[0].Name)
|
||||||
|
assert.True(t, pfs[0].IsLead)
|
||||||
|
|
||||||
|
uploadPackage(
|
||||||
|
t,
|
||||||
|
uploadURL,
|
||||||
|
http.StatusConflict,
|
||||||
|
createArchive(map[string]string{
|
||||||
|
"Package.swift": contentManifest1,
|
||||||
|
}),
|
||||||
|
"",
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("Download", func(t *testing.T) {
|
t.Run("Download", func(t *testing.T) {
|
||||||
defer tests.PrintCurrentTest(t)()
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
@ -211,7 +301,7 @@ func TestPackageSwift(t *testing.T) {
|
|||||||
SetHeader("Accept", swift_router.AcceptJSON)
|
SetHeader("Accept", swift_router.AcceptJSON)
|
||||||
resp := MakeRequest(t, req, http.StatusOK)
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
versionURL := setting.AppURL + url[1:] + fmt.Sprintf("/%s/%s/%s", packageScope, packageName, packageVersion)
|
versionURL := setting.AppURL + url[1:] + fmt.Sprintf("/%s/%s/%s", packageScope, packageName, packageVersion2)
|
||||||
|
|
||||||
assert.Equal(t, "1", resp.Header().Get("Content-Version"))
|
assert.Equal(t, "1", resp.Header().Get("Content-Version"))
|
||||||
assert.Equal(t, fmt.Sprintf(`<%s>; rel="latest-version"`, versionURL), resp.Header().Get("Link"))
|
assert.Equal(t, fmt.Sprintf(`<%s>; rel="latest-version"`, versionURL), resp.Header().Get("Link"))
|
||||||
@ -221,9 +311,9 @@ func TestPackageSwift(t *testing.T) {
|
|||||||
var result *swift_router.EnumeratePackageVersionsResponse
|
var result *swift_router.EnumeratePackageVersionsResponse
|
||||||
DecodeJSON(t, resp, &result)
|
DecodeJSON(t, resp, &result)
|
||||||
|
|
||||||
assert.Len(t, result.Releases, 1)
|
assert.Len(t, result.Releases, 2)
|
||||||
assert.Contains(t, result.Releases, packageVersion)
|
assert.Contains(t, result.Releases, packageVersion2)
|
||||||
assert.Equal(t, versionURL, result.Releases[packageVersion].URL)
|
assert.Equal(t, versionURL, result.Releases[packageVersion2].URL)
|
||||||
|
|
||||||
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s.json", url, packageScope, packageName)).
|
req = NewRequest(t, "GET", fmt.Sprintf("%s/%s/%s.json", url, packageScope, packageName)).
|
||||||
AddBasicAuth(user.Name)
|
AddBasicAuth(user.Name)
|
||||||
|
@ -224,6 +224,7 @@ progress::-moz-progress-bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.unselectable,
|
.unselectable,
|
||||||
|
.btn,
|
||||||
.button,
|
.button,
|
||||||
.lines-num,
|
.lines-num,
|
||||||
.lines-commit,
|
.lines-commit,
|
||||||
@ -1037,12 +1038,13 @@ table th[data-sortt-desc] .svg {
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ellipsis-button {
|
.ui.button.ellipsis-button {
|
||||||
padding: 0 5px 8px !important;
|
padding: 0 5px 8px;
|
||||||
display: inline-block !important;
|
display: inline-block;
|
||||||
font-weight: var(--font-weight-semibold) !important;
|
font-weight: var(--font-weight-semibold);
|
||||||
line-height: 6px !important;
|
line-height: 8px;
|
||||||
vertical-align: middle !important;
|
vertical-align: middle;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.precolors {
|
.precolors {
|
||||||
|
@ -116,6 +116,7 @@
|
|||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#git-graph-container #graph-raw-list {
|
#git-graph-container #graph-raw-list {
|
||||||
|
@ -1,8 +1,3 @@
|
|||||||
.markup .code-block,
|
|
||||||
.markup .mermaid-block {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markup .code-copy {
|
.markup .code-copy {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
@ -28,8 +23,8 @@
|
|||||||
background: var(--color-secondary-dark-1) !important;
|
background: var(--color-secondary-dark-1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markup .code-block:hover .code-copy,
|
.markup .code-block-container:hover .code-copy,
|
||||||
.markup .mermaid-block:hover .code-copy {
|
.markup .code-block:hover .code-copy {
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
animation: fadein 0.2s both;
|
animation: fadein 0.2s both;
|
||||||
}
|
}
|
||||||
|
@ -443,13 +443,25 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.markup pre > code {
|
.markup pre > code {
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markup .code-block,
|
||||||
|
.markup .code-block-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markup .code-block-container.code-overflow-wrap pre > code {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
overflow-wrap: anywhere;
|
}
|
||||||
background: transparent;
|
|
||||||
border: 0;
|
.markup .code-block-container.code-overflow-scroll pre {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.markup .code-block-container.code-overflow-scroll pre > code {
|
||||||
|
white-space: pre;
|
||||||
|
overflow-wrap: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markup .highlight {
|
.markup .highlight {
|
||||||
@ -470,16 +482,11 @@
|
|||||||
word-break: normal;
|
word-break: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.markup pre {
|
|
||||||
word-wrap: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markup pre code,
|
.markup pre code,
|
||||||
.markup pre tt {
|
.markup pre tt {
|
||||||
display: inline;
|
display: inline;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: inherit;
|
line-height: inherit;
|
||||||
word-wrap: normal;
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
@ -522,20 +529,6 @@
|
|||||||
margin: 0 0.25em;
|
margin: 0 0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-revisions-btn {
|
|
||||||
display: block;
|
|
||||||
float: left;
|
|
||||||
margin-bottom: 2px !important;
|
|
||||||
padding: 11px !important;
|
|
||||||
margin-right: 10px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-revisions-btn i {
|
|
||||||
-webkit-touch-callout: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.markup-content-iframe {
|
.markup-content-iframe {
|
||||||
display: block;
|
display: block;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -1,20 +1,15 @@
|
|||||||
/* based on Fomantic UI checkbox module, with just the parts extracted that we use. If you find any
|
|
||||||
unused rules here after refactoring, please remove them. */
|
|
||||||
|
|
||||||
.ui.button {
|
.ui.button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: inline-block;
|
display: inline-flex;
|
||||||
min-height: 1em;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
vertical-align: baseline;
|
|
||||||
font-family: var(--fonts-regular);
|
font-family: var(--fonts-regular);
|
||||||
margin: 0 0.25em 0 0;
|
margin: 0 0.25em 0 0;
|
||||||
padding: 0.78571429em 1.5em;
|
|
||||||
font-weight: var(--font-weight-normal);
|
font-weight: var(--font-weight-normal);
|
||||||
|
font-size: 1rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
border-radius: 0.28571429rem;
|
border-radius: var(--border-radius);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -58,12 +53,13 @@
|
|||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* there is no "ui labeled icon button" support" because it is not used */
|
||||||
.ui.labeled.button:not(.icon) {
|
.ui.labeled.button:not(.icon) {
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
background: none;
|
background: none;
|
||||||
padding: 0 !important;
|
padding: 0;
|
||||||
border: none;
|
border: none;
|
||||||
|
min-height: unset;
|
||||||
}
|
}
|
||||||
.ui.labeled.button > .button {
|
.ui.labeled.button > .button {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@ -102,47 +98,60 @@
|
|||||||
margin: 0 -0.21428571em 0 0.42857143em;
|
margin: 0 -0.21428571em 0 0.42857143em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* reference sizes (not exactly at the moment): normal: padding-x=21, height=38 ; compact: padding-x=15, height=32 */
|
||||||
|
.ui.button { /* stylelint-disable-line no-duplicate-selectors */
|
||||||
|
min-height: 38px;
|
||||||
|
padding: 0.57em /* around 8px */ 1.43em /* around 20px */;
|
||||||
|
}
|
||||||
.ui.compact.buttons .button,
|
.ui.compact.buttons .button,
|
||||||
.ui.compact.button {
|
.ui.compact.button {
|
||||||
padding: 0.58928571em 1.125em;
|
padding: 0.42em /* around 8px */ 1.07em /* around 15px */;
|
||||||
|
min-height: 32px;
|
||||||
}
|
}
|
||||||
.ui.compact.icon.buttons .button,
|
.ui.compact.icon.buttons .button,
|
||||||
.ui.compact.icon.button {
|
.ui.compact.icon.button {
|
||||||
padding: 0.58928571em;
|
padding: 0.57em /* around 8px */;
|
||||||
}
|
|
||||||
.ui.compact.labeled.icon.button {
|
|
||||||
padding: 0.58928571em 3.69642857em;
|
|
||||||
}
|
|
||||||
.ui.compact.labeled.icon.button > .icon {
|
|
||||||
padding: 0.58928571em 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.buttons .button,
|
/* reference size: mini: padding-x=16, height=30 ; compact: padding-x=12, height=26 */
|
||||||
.ui.button {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
.ui.mini.buttons .dropdown,
|
.ui.mini.buttons .dropdown,
|
||||||
.ui.mini.buttons .dropdown .menu > .item,
|
.ui.mini.buttons .dropdown .menu > .item,
|
||||||
.ui.mini.buttons .button,
|
.ui.mini.buttons .button,
|
||||||
.ui.ui.ui.ui.mini.button {
|
.ui.ui.ui.ui.mini.button {
|
||||||
font-size: 0.78571429rem;
|
font-size: 11px;
|
||||||
|
min-height: 30px;
|
||||||
}
|
}
|
||||||
|
.ui.ui.ui.ui.mini.button.compact {
|
||||||
|
min-height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reference size: tiny: padding-x=18, height=32 ; compact: padding-x=13, height=28 */
|
||||||
.ui.tiny.buttons .dropdown,
|
.ui.tiny.buttons .dropdown,
|
||||||
.ui.tiny.buttons .dropdown .menu > .item,
|
.ui.tiny.buttons .dropdown .menu > .item,
|
||||||
.ui.tiny.buttons .button,
|
.ui.tiny.buttons .button,
|
||||||
.ui.ui.ui.ui.tiny.button {
|
.ui.ui.ui.ui.tiny.button {
|
||||||
font-size: 0.85714286rem;
|
font-size: 12px;
|
||||||
|
min-height: 32px;
|
||||||
}
|
}
|
||||||
|
.ui.ui.ui.ui.tiny.button.compact {
|
||||||
|
min-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* reference size: small: padding-x=19, height=34 ; compact: padding-x=14, height=30 */
|
||||||
.ui.small.buttons .dropdown,
|
.ui.small.buttons .dropdown,
|
||||||
.ui.small.buttons .dropdown .menu > .item,
|
.ui.small.buttons .dropdown .menu > .item,
|
||||||
.ui.small.buttons .button,
|
.ui.small.buttons .button,
|
||||||
.ui.ui.ui.ui.small.button {
|
.ui.ui.ui.ui.small.button {
|
||||||
font-size: 0.92857143rem;
|
font-size: 13px;
|
||||||
|
min-height: 34px;
|
||||||
|
}
|
||||||
|
.ui.ui.ui.ui.small.button.compact {
|
||||||
|
min-height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.icon.buttons .button,
|
.ui.icon.buttons .button,
|
||||||
.ui.icon.button:not(.compact) {
|
.ui.icon.button:not(.compact) {
|
||||||
padding: 0.78571429em;
|
padding: 0.57em;
|
||||||
}
|
}
|
||||||
.ui.icon.buttons .button > .icon,
|
.ui.icon.buttons .button > .icon,
|
||||||
.ui.icon.button > .icon {
|
.ui.icon.button > .icon {
|
||||||
@ -152,12 +161,12 @@
|
|||||||
|
|
||||||
.ui.basic.buttons .button,
|
.ui.basic.buttons .button,
|
||||||
.ui.basic.button {
|
.ui.basic.button {
|
||||||
border-radius: 0.28571429rem;
|
border-radius: var(--border-radius);
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
.ui.basic.buttons {
|
.ui.basic.buttons {
|
||||||
border: 1px solid var(--color-secondary);
|
border: 1px solid var(--color-secondary);
|
||||||
border-radius: 0.28571429rem;
|
border-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
.ui.basic.buttons .button {
|
.ui.basic.buttons .button {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
@ -188,29 +197,6 @@
|
|||||||
background: var(--color-active);
|
background: var(--color-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.labeled.icon.button {
|
|
||||||
position: relative;
|
|
||||||
padding-left: 4.07142857em !important;
|
|
||||||
padding-right: 1.5em !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.labeled.icon.button > .icon {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
height: 100%;
|
|
||||||
line-height: 1;
|
|
||||||
border-radius: 0;
|
|
||||||
border-top-left-radius: inherit;
|
|
||||||
border-bottom-left-radius: inherit;
|
|
||||||
text-align: center;
|
|
||||||
animation: none;
|
|
||||||
padding: 0.78571429em 0;
|
|
||||||
margin: 0;
|
|
||||||
width: 2.57142857em;
|
|
||||||
background: var(--color-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.button.toggle.active {
|
.ui.button.toggle.active {
|
||||||
background-color: var(--color-green);
|
background-color: var(--color-green);
|
||||||
color: var(--color-white);
|
color: var(--color-white);
|
||||||
@ -366,6 +352,14 @@ a.btn:hover {
|
|||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn.tiny {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn.small {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
/* By default, Fomantic UI doesn't support "bordered" buttons group, but Gitea would like to use it.
|
/* By default, Fomantic UI doesn't support "bordered" buttons group, but Gitea would like to use it.
|
||||||
And the default buttons always have borders now (not the same as Fomantic UI's default buttons, see above).
|
And the default buttons always have borders now (not the same as Fomantic UI's default buttons, see above).
|
||||||
It needs some tricks to tweak the left/right borders with active state */
|
It needs some tricks to tweak the left/right borders with active state */
|
||||||
@ -379,12 +373,12 @@ It needs some tricks to tweak the left/right borders with active state */
|
|||||||
.ui.buttons .button:first-child {
|
.ui.buttons .button:first-child {
|
||||||
border-left: none;
|
border-left: none;
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
border-top-left-radius: 0.28571429rem;
|
border-top-left-radius: var(--border-radius);
|
||||||
border-bottom-left-radius: 0.28571429rem;
|
border-bottom-left-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
.ui.buttons .button:last-child {
|
.ui.buttons .button:last-child {
|
||||||
border-top-right-radius: 0.28571429rem;
|
border-top-right-radius: var(--border-radius);
|
||||||
border-bottom-right-radius: 0.28571429rem;
|
border-bottom-right-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui.buttons .button:hover {
|
.ui.buttons .button:hover {
|
||||||
@ -414,10 +408,3 @@ It needs some tricks to tweak the left/right borders with active state */
|
|||||||
.ui.buttons .button.active + .button {
|
.ui.buttons .button.active + .button {
|
||||||
border-left: none;
|
border-left: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* apply the vertical padding of .compact to non-compact buttons when they contain a svg as they
|
|
||||||
would otherwise appear too large. Seen on "RSS Feed" button on repo releases tab. */
|
|
||||||
.ui.small.button:not(.compact):has(.svg) {
|
|
||||||
padding-top: 0.58928571em;
|
|
||||||
padding-bottom: 0.58928571em;
|
|
||||||
}
|
|
||||||
|
@ -92,6 +92,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tippy-box[data-theme="menu"] .item:focus {
|
.tippy-box[data-theme="menu"] .item:focus {
|
||||||
|
background: var(--color-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box[data-theme="menu"] .item.active {
|
||||||
background: var(--color-active);
|
background: var(--color-active);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1629,21 +1629,17 @@ td .commit-summary {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo-button-row-left {
|
.repo-button-row-left {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo-button-row .button {
|
.repo-button-row .ui.button {
|
||||||
padding: 6px 10px !important;
|
|
||||||
height: 30px;
|
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
min-height: 30px;
|
||||||
|
|
||||||
.repo-button-row .button.dropdown:not(.icon) {
|
|
||||||
padding-right: 22px !important; /* normal buttons have !important paddings, so we need to override it for dropdown (Add File) icons */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tbody.commit-list {
|
tbody.commit-list {
|
||||||
@ -1788,12 +1784,12 @@ tbody.commit-list {
|
|||||||
.resolved-placeholder {
|
.resolved-placeholder {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 14px !important;
|
justify-content: space-between;
|
||||||
padding: 8px !important;
|
margin: 4px;
|
||||||
font-weight: var(--font-weight-normal) !important;
|
padding: 8px;
|
||||||
border: 1px solid var(--color-secondary) !important;
|
border: 1px solid var(--color-secondary);
|
||||||
border-radius: var(--border-radius) !important;
|
border-radius: var(--border-radius);
|
||||||
margin: 4px !important;
|
background: var(--color-box-header);
|
||||||
}
|
}
|
||||||
|
|
||||||
.resolved-placeholder + .comment-code-cloud {
|
.resolved-placeholder + .comment-code-cloud {
|
||||||
@ -2221,10 +2217,11 @@ tbody.commit-list {
|
|||||||
max-width: min(400px, 90vw);
|
max-width: min(400px, 90vw);
|
||||||
}
|
}
|
||||||
|
|
||||||
.branch-selector-dropdown .branch-dropdown-button {
|
.branch-selector-dropdown .ui.button.branch-dropdown-button {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
max-width: 340px;
|
max-width: 340px;
|
||||||
line-height: var(--line-height-default);
|
line-height: var(--line-height-default);
|
||||||
|
padding: 0 0.5em 0 0.75em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* FIXME: These media selectors are not ideal (just keep them from old code).
|
/* FIXME: These media selectors are not ideal (just keep them from old code).
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
/* only used by "repo/empty.tmpl" */
|
/* only used by "repo/empty.tmpl" */
|
||||||
.clone-buttons-combo {
|
.clone-buttons-combo {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
.ui.action.input.clone-buttons-combo input {
|
.ui.action.input.clone-buttons-combo input {
|
||||||
border-radius: 0; /* override fomantic border-radius for ".ui.input > input" */
|
border-radius: 0; /* override fomantic border-radius for ".ui.input > input" */
|
||||||
height: 30px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* used by the clone-panel popup */
|
/* used by the clone-panel popup */
|
||||||
|
@ -27,47 +27,3 @@
|
|||||||
.repo-header .flex-item-trailing {
|
.repo-header .flex-item-trailing {
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.repo-buttons {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
word-break: keep-all;
|
|
||||||
gap: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repo-buttons button[disabled] ~ .label {
|
|
||||||
opacity: var(--opacity-disabled);
|
|
||||||
color: var(--color-text-dark);
|
|
||||||
background: var(--color-light-mimic-enabled) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repo-buttons button[disabled] ~ .label:hover {
|
|
||||||
color: var(--color-primary-dark-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.repo-buttons .ui.labeled.button.disabled {
|
|
||||||
pointer-events: inherit !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repo-buttons .ui.labeled.button.disabled > .label {
|
|
||||||
color: var(--color-text-dark);
|
|
||||||
background: var(--color-light-mimic-enabled) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.repo-buttons .ui.labeled.button.disabled > .label:hover {
|
|
||||||
color: var(--color-primary-dark-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.repo-buttons .ui.labeled.button.disabled > .button {
|
|
||||||
pointer-events: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
|
||||||
.repo-buttons .ui.button,
|
|
||||||
.repo-buttons .ui.label {
|
|
||||||
padding-left: 8px;
|
|
||||||
padding-right: 8px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -2,17 +2,15 @@
|
|||||||
color: var(--color-text-dark) !important;
|
color: var(--color-text-dark) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-line-button {
|
.ui.button.code-line-button {
|
||||||
border: 1px solid var(--color-secondary);
|
border: 1px solid var(--color-secondary);
|
||||||
border-radius: var(--border-radius);
|
padding: 1px 4px;
|
||||||
padding: 1px 4px !important;
|
margin: 0;
|
||||||
|
min-height: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
font-family: var(--fonts-regular);
|
left: 6px;
|
||||||
left: 0;
|
|
||||||
transform: translateX(calc(-50% + 6px));
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.code-line-button:hover {
|
.ui.button.code-line-button:hover {
|
||||||
background: var(--color-secondary) !important;
|
background: var(--color-secondary);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
.list-header {
|
.list-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: .5rem;
|
gap: .5rem;
|
||||||
}
|
}
|
||||||
@ -8,9 +8,8 @@
|
|||||||
.list-header-search {
|
.list-header-search {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
align-items: center;
|
align-items: stretch;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
|
||||||
min-width: 200px; /* to enable flexbox wrapping on mobile */
|
min-width: 200px; /* to enable flexbox wrapping on mobile */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,8 @@
|
|||||||
.show-outdated,
|
|
||||||
.hide-outdated {
|
|
||||||
-webkit-touch-callout: none;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
margin-right: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.button.add-code-comment {
|
.ui.button.add-code-comment {
|
||||||
padding: 2px;
|
padding: 2px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: -22px;
|
margin-left: -22px;
|
||||||
|
min-height: 0;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: transform 0.1s ease-in-out;
|
transition: transform 0.1s ease-in-out;
|
||||||
@ -58,11 +51,6 @@
|
|||||||
margin-bottom: 0.5em;
|
margin-bottom: 0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.show-outdated:hover,
|
|
||||||
.hide-outdated:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.comment-code-cloud {
|
.comment-code-cloud {
|
||||||
padding: 0.5rem 1rem !important;
|
padding: 0.5rem 1rem !important;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -1173,11 +1173,6 @@ select.ui.dropdown {
|
|||||||
border-radius: 0.28571429rem !important;
|
border-radius: 0.28571429rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* GITEA-PATCH: gitea also have "right menu" support */
|
|
||||||
.ui.dropdown > .right.menu {
|
|
||||||
left: auto;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
/* Leftward Opening Menu */
|
/* Leftward Opening Menu */
|
||||||
.ui.dropdown > .left.menu {
|
.ui.dropdown > .left.menu {
|
||||||
left: auto !important;
|
left: auto !important;
|
||||||
|
@ -212,7 +212,7 @@ export default defineComponent({
|
|||||||
<div class="ui scrolling dropdown custom diff-commit-selector">
|
<div class="ui scrolling dropdown custom diff-commit-selector">
|
||||||
<button
|
<button
|
||||||
ref="expandBtn"
|
ref="expandBtn"
|
||||||
class="ui basic button"
|
class="ui tiny basic button"
|
||||||
@click.stop="toggleMenu()"
|
@click.stop="toggleMenu()"
|
||||||
:data-tooltip-content="locale.filter_changes_by_commit"
|
:data-tooltip-content="locale.filter_changes_by_commit"
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
|
@ -217,13 +217,13 @@ export default defineComponent({
|
|||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div class="ui dropdown custom branch-selector-dropdown ellipsis-text-items">
|
<div class="ui dropdown custom branch-selector-dropdown ellipsis-text-items">
|
||||||
<div tabindex="0" class="ui button branch-dropdown-button" @click="menuVisible = !menuVisible">
|
<div tabindex="0" class="ui compact button branch-dropdown-button" @click="menuVisible = !menuVisible">
|
||||||
<span class="flex-text-block gt-ellipsis">
|
<span class="flex-text-block gt-ellipsis">
|
||||||
<template v-if="dropdownFixedText">{{ dropdownFixedText }}</template>
|
<template v-if="dropdownFixedText">{{ dropdownFixedText }}</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<svg-icon v-if="currentRefType === 'tag'" name="octicon-tag"/>
|
<svg-icon v-if="currentRefType === 'tag'" name="octicon-tag"/>
|
||||||
<svg-icon v-else name="octicon-git-branch"/>
|
<svg-icon v-else name="octicon-git-branch"/>
|
||||||
<strong ref="dropdownRefName" class="tw-ml-2 tw-inline-block gt-ellipsis">{{ currentRefShortName }}</strong>
|
<strong ref="dropdownRefName" class="tw-inline-block gt-ellipsis">{{ currentRefShortName }}</strong>
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<svg-icon name="octicon-triangle-down" :size="14" class="dropdown icon"/>
|
<svg-icon name="octicon-triangle-down" :size="14" class="dropdown icon"/>
|
||||||
|
@ -353,12 +353,12 @@ export default defineComponent({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<!-- Contribution type -->
|
<!-- Contribution type -->
|
||||||
<div class="ui dropdown jump" id="repo-contributors">
|
<div class="ui floating dropdown jump" id="repo-contributors">
|
||||||
<div class="ui basic compact button tw-mr-0">
|
<div class="ui basic compact button tw-mr-0">
|
||||||
<span class="not-mobile">{{ locale.filterLabel }}</span> <strong>{{ locale.contributionType[type] }}</strong>
|
<span class="not-mobile">{{ locale.filterLabel }}</span> <strong>{{ locale.contributionType[type] }}</strong>
|
||||||
<svg-icon name="octicon-triangle-down" :size="14"/>
|
<svg-icon name="octicon-triangle-down" :size="14"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="right menu">
|
<div class="left menu">
|
||||||
<div :class="['item', {'selected': type === 'commits'}]" data-value="commits">
|
<div :class="['item', {'selected': type === 'commits'}]" data-value="commits">
|
||||||
{{ locale.contributionType.commits }}
|
{{ locale.contributionType.commits }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import {checkAppUrl} from '../common-page.ts';
|
import {checkAppUrl} from '../common-page.ts';
|
||||||
import {hideElem, queryElems, showElem, toggleElem} from '../../utils/dom.ts';
|
import {hideElem, queryElems, showElem, toggleElem} from '../../utils/dom.ts';
|
||||||
import {POST} from '../../modules/fetch.ts';
|
import {POST} from '../../modules/fetch.ts';
|
||||||
import {initAvatarUploaderWithCropper} from '../comp/Cropper.ts';
|
|
||||||
import {fomanticQuery} from '../../modules/fomantic/base.ts';
|
import {fomanticQuery} from '../../modules/fomantic/base.ts';
|
||||||
|
|
||||||
const {appSubUrl} = window.config;
|
const {appSubUrl} = window.config;
|
||||||
@ -23,8 +22,6 @@ export function initAdminCommon(): void {
|
|||||||
initAdminUser();
|
initAdminUser();
|
||||||
initAdminAuthentication();
|
initAdminAuthentication();
|
||||||
initAdminNotice();
|
initAdminNotice();
|
||||||
|
|
||||||
queryElems(document, '.avatar-file-with-cropper', initAvatarUploaderWithCropper);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function initAdminUser() {
|
function initAdminUser() {
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import {initCompLabelEdit} from './comp/LabelEdit.ts';
|
import {initCompLabelEdit} from './comp/LabelEdit.ts';
|
||||||
import {queryElems, toggleElem} from '../utils/dom.ts';
|
import {toggleElem} from '../utils/dom.ts';
|
||||||
import {initAvatarUploaderWithCropper} from './comp/Cropper.ts';
|
|
||||||
|
|
||||||
export function initCommonOrganization() {
|
export function initCommonOrganization() {
|
||||||
if (!document.querySelectorAll('.organization').length) {
|
if (!document.querySelectorAll('.organization').length) {
|
||||||
@ -14,6 +13,4 @@ export function initCommonOrganization() {
|
|||||||
|
|
||||||
// Labels
|
// Labels
|
||||||
initCompLabelEdit('.page-content.organization.settings.labels');
|
initCompLabelEdit('.page-content.organization.settings.labels');
|
||||||
|
|
||||||
queryElems(document, '.avatar-file-with-cropper', initAvatarUploaderWithCropper);
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import {showGlobalErrorMessage} from '../bootstrap.ts';
|
|||||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||||
import {queryElems} from '../utils/dom.ts';
|
import {queryElems} from '../utils/dom.ts';
|
||||||
import {registerGlobalInitFunc, registerGlobalSelectorFunc} from '../modules/observer.ts';
|
import {registerGlobalInitFunc, registerGlobalSelectorFunc} from '../modules/observer.ts';
|
||||||
|
import {initAvatarUploaderWithCropper} from './comp/Cropper.ts';
|
||||||
|
|
||||||
const {appUrl} = window.config;
|
const {appUrl} = window.config;
|
||||||
|
|
||||||
@ -80,6 +81,10 @@ export function initGlobalTabularMenu() {
|
|||||||
fomanticQuery('.ui.menu.tabular:not(.custom) .item').tab();
|
fomanticQuery('.ui.menu.tabular:not(.custom) .item').tab();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function initGlobalAvatarUploader() {
|
||||||
|
registerGlobalInitFunc('initAvatarUploader', initAvatarUploaderWithCropper);
|
||||||
|
}
|
||||||
|
|
||||||
// for performance considerations, it only uses performant syntax
|
// for performance considerations, it only uses performant syntax
|
||||||
function attachInputDirAuto(el: Partial<HTMLInputElement | HTMLTextAreaElement>) {
|
function attachInputDirAuto(el: Partial<HTMLInputElement | HTMLTextAreaElement>) {
|
||||||
if (el.type !== 'hidden' &&
|
if (el.type !== 'hidden' &&
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import {svg} from '../svg.ts';
|
import {svg} from '../svg.ts';
|
||||||
import {createTippy} from '../modules/tippy.ts';
|
import {createTippy} from '../modules/tippy.ts';
|
||||||
import {clippie} from 'clippie';
|
|
||||||
import {toAbsoluteUrl} from '../utils.ts';
|
import {toAbsoluteUrl} from '../utils.ts';
|
||||||
import {addDelegatedEventListener} from '../utils/dom.ts';
|
import {addDelegatedEventListener} from '../utils/dom.ts';
|
||||||
|
|
||||||
@ -43,7 +42,8 @@ function selectRange(range: string): Element {
|
|||||||
if (!copyPermalink) return;
|
if (!copyPermalink) return;
|
||||||
let link = copyPermalink.getAttribute('data-url');
|
let link = copyPermalink.getAttribute('data-url');
|
||||||
link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`;
|
link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`;
|
||||||
copyPermalink.setAttribute('data-url', link);
|
copyPermalink.setAttribute('data-clipboard-text', link);
|
||||||
|
copyPermalink.setAttribute('data-clipboard-text-type', 'url');
|
||||||
};
|
};
|
||||||
|
|
||||||
const rangeFields = range ? range.split('-') : [];
|
const rangeFields = range ? range.split('-') : [];
|
||||||
@ -138,8 +138,4 @@ export function initRepoCodeView() {
|
|||||||
};
|
};
|
||||||
onHashChange();
|
onHashChange();
|
||||||
window.addEventListener('hashchange', onHashChange);
|
window.addEventListener('hashchange', onHashChange);
|
||||||
|
|
||||||
addDelegatedEventListener(document, 'click', '.copy-line-permalink', (el) => {
|
|
||||||
clippie(toAbsoluteUrl(el.getAttribute('data-url')));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import {minimatch} from 'minimatch';
|
|||||||
import {createMonaco} from './codeeditor.ts';
|
import {createMonaco} from './codeeditor.ts';
|
||||||
import {onInputDebounce, queryElems, toggleClass, toggleElem} from '../utils/dom.ts';
|
import {onInputDebounce, queryElems, toggleClass, toggleElem} from '../utils/dom.ts';
|
||||||
import {POST} from '../modules/fetch.ts';
|
import {POST} from '../modules/fetch.ts';
|
||||||
import {initAvatarUploaderWithCropper} from './comp/Cropper.ts';
|
|
||||||
import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts';
|
import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts';
|
||||||
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
import {fomanticQuery} from '../modules/fomantic/base.ts';
|
||||||
|
|
||||||
@ -149,6 +148,4 @@ export function initRepoSettings() {
|
|||||||
initRepoSettingsSearchTeamBox();
|
initRepoSettingsSearchTeamBox();
|
||||||
initRepoSettingsGitHook();
|
initRepoSettingsGitHook();
|
||||||
initRepoSettingsBranchesDrag();
|
initRepoSettingsBranchesDrag();
|
||||||
|
|
||||||
queryElems(document, '.avatar-file-with-cropper', initAvatarUploaderWithCropper);
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import {hideElem, queryElems, showElem} from '../utils/dom.ts';
|
import {hideElem, showElem} from '../utils/dom.ts';
|
||||||
import {initAvatarUploaderWithCropper} from './comp/Cropper.ts';
|
|
||||||
|
|
||||||
export function initUserSettings() {
|
export function initUserSettings() {
|
||||||
if (!document.querySelector('.user.settings.profile')) return;
|
if (!document.querySelector('.user.settings.profile')) return;
|
||||||
|
|
||||||
queryElems(document, '.avatar-file-with-cropper', initAvatarUploaderWithCropper);
|
|
||||||
|
|
||||||
const usernameInput = document.querySelector<HTMLInputElement>('#username');
|
const usernameInput = document.querySelector<HTMLInputElement>('#username');
|
||||||
if (!usernameInput) return;
|
if (!usernameInput) return;
|
||||||
usernameInput.addEventListener('input', function () {
|
usernameInput.addEventListener('input', function () {
|
||||||
|
@ -60,7 +60,7 @@ import {initColorPickers} from './features/colorpicker.ts';
|
|||||||
import {initAdminSelfCheck} from './features/admin/selfcheck.ts';
|
import {initAdminSelfCheck} from './features/admin/selfcheck.ts';
|
||||||
import {initOAuth2SettingsDisableCheckbox} from './features/oauth2-settings.ts';
|
import {initOAuth2SettingsDisableCheckbox} from './features/oauth2-settings.ts';
|
||||||
import {initGlobalFetchAction} from './features/common-fetch-action.ts';
|
import {initGlobalFetchAction} from './features/common-fetch-action.ts';
|
||||||
import {initFootLanguageMenu, initGlobalDropdown, initGlobalInput, initGlobalTabularMenu, initHeadNavbarContentToggle} from './features/common-page.ts';
|
import {initFootLanguageMenu, initGlobalAvatarUploader, initGlobalDropdown, initGlobalInput, initGlobalTabularMenu, initHeadNavbarContentToggle} from './features/common-page.ts';
|
||||||
import {initGlobalButtonClickOnEnter, initGlobalButtons, initGlobalDeleteButton} from './features/common-button.ts';
|
import {initGlobalButtonClickOnEnter, initGlobalButtons, initGlobalDeleteButton} from './features/common-button.ts';
|
||||||
import {initGlobalComboMarkdownEditor, initGlobalEnterQuickSubmit, initGlobalFormDirtyLeaveConfirm} from './features/common-form.ts';
|
import {initGlobalComboMarkdownEditor, initGlobalEnterQuickSubmit, initGlobalFormDirtyLeaveConfirm} from './features/common-form.ts';
|
||||||
import {callInitFunctions} from './modules/init.ts';
|
import {callInitFunctions} from './modules/init.ts';
|
||||||
@ -72,6 +72,7 @@ initSubmitEventPolyfill();
|
|||||||
onDomReady(() => {
|
onDomReady(() => {
|
||||||
const initStartTime = performance.now();
|
const initStartTime = performance.now();
|
||||||
const initPerformanceTracer = callInitFunctions([
|
const initPerformanceTracer = callInitFunctions([
|
||||||
|
initGlobalAvatarUploader,
|
||||||
initGlobalDropdown,
|
initGlobalDropdown,
|
||||||
initGlobalTabularMenu,
|
initGlobalTabularMenu,
|
||||||
initGlobalFetchAction,
|
initGlobalFetchAction,
|
||||||
|
@ -15,6 +15,8 @@ export function initMarkupCodeCopy(elMarkup: HTMLElement): void {
|
|||||||
const btn = makeCodeCopyButton();
|
const btn = makeCodeCopyButton();
|
||||||
// remove final trailing newline introduced during HTML rendering
|
// remove final trailing newline introduced during HTML rendering
|
||||||
btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, ''));
|
btn.setAttribute('data-clipboard-text', el.textContent.replace(/\r?\n$/, ''));
|
||||||
el.after(btn);
|
// we only want to use `.code-block-container` if it exists, no matter `.code-block` exists or not.
|
||||||
|
const btnContainer = el.closest('.code-block-container') ?? el.closest('.code-block');
|
||||||
|
btnContainer.append(btn);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@ export function queryElemChildren<T extends Element>(parent: Element | ParentNod
|
|||||||
}
|
}
|
||||||
|
|
||||||
// it works like parent.querySelectorAll: all descendants are selected
|
// it works like parent.querySelectorAll: all descendants are selected
|
||||||
// in the future, all "queryElems(document, ...)" should be refactored to use a more specific parent
|
// in the future, all "queryElems(document, ...)" should be refactored to use a more specific parent if the targets are not for page-level components.
|
||||||
export function queryElems<T extends HTMLElement>(parent: Element | ParentNode, selector: string, fn?: ElementsCallback<T>): ArrayLikeIterable<T> {
|
export function queryElems<T extends HTMLElement>(parent: Element | ParentNode, selector: string, fn?: ElementsCallback<T>): ArrayLikeIterable<T> {
|
||||||
return applyElemsCallback<T>(parent.querySelectorAll(selector), fn);
|
return applyElemsCallback<T>(parent.querySelectorAll(selector), fn);
|
||||||
}
|
}
|
||||||
@ -360,7 +360,11 @@ export function querySingleVisibleElem<T extends HTMLElement>(parent: Element, s
|
|||||||
export function addDelegatedEventListener<T extends HTMLElement, E extends Event>(parent: Node, type: string, selector: string, listener: (elem: T, e: E) => Promisable<void>, options?: boolean | AddEventListenerOptions) {
|
export function addDelegatedEventListener<T extends HTMLElement, E extends Event>(parent: Node, type: string, selector: string, listener: (elem: T, e: E) => Promisable<void>, options?: boolean | AddEventListenerOptions) {
|
||||||
parent.addEventListener(type, (e: Event) => {
|
parent.addEventListener(type, (e: Event) => {
|
||||||
const elem = (e.target as HTMLElement).closest(selector);
|
const elem = (e.target as HTMLElement).closest(selector);
|
||||||
if (!elem || !parent.contains(elem)) return;
|
// It strictly checks "parent contains the target elem" to avoid side effects of selector running on outside the parent.
|
||||||
|
// Keep in mind that the elem could have been removed from parent by other event handlers before this event handler is called.
|
||||||
|
// For example: tippy popup item, the tippy popup could be hidden and removed from DOM before this.
|
||||||
|
// It is caller's responsibility make sure the elem is still in parent's DOM when this event handler is called.
|
||||||
|
if (!elem || (parent !== document && !parent.contains(elem))) return;
|
||||||
listener(elem as T, e as E);
|
listener(elem as T, e as E);
|
||||||
}, options);
|
}, options);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user