Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Anthony Wang 2022-07-07 12:58:51 -05:00
commit 721b734049
No known key found for this signature in database
GPG key ID: BC96B00AEC5F2D76
380 changed files with 4696 additions and 4229 deletions

View file

@ -17,7 +17,6 @@ else
DIST := dist DIST := dist
DIST_DIRS := $(DIST)/binaries $(DIST)/release DIST_DIRS := $(DIST)/binaries $(DIST)/release
IMPORT := code.gitea.io/gitea IMPORT := code.gitea.io/gitea
export GO111MODULE=on
GO ?= go GO ?= go
SHASUM ?= shasum -a 256 SHASUM ?= shasum -a 256
@ -363,7 +362,7 @@ test\#%:
coverage: coverage:
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' coverage.out > coverage-bodged.out grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' coverage.out > coverage-bodged.out
grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' integration.coverage.out > integration.coverage-bodged.out grep '^\(mode: .*\)\|\(.*:[0-9]\+\.[0-9]\+,[0-9]\+\.[0-9]\+ [0-9]\+ [0-9]\+\)$$' integration.coverage.out > integration.coverage-bodged.out
GO111MODULE=on $(GO) run build/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all || (echo "gocovmerge failed"; echo "integration.coverage.out"; cat integration.coverage.out; echo "coverage.out"; cat coverage.out; exit 1) $(GO) run build/gocovmerge.go integration.coverage-bodged.out coverage-bodged.out > coverage.all || (echo "gocovmerge failed"; echo "integration.coverage.out"; cat integration.coverage.out; echo "coverage.out"; cat coverage.out; exit 1)
.PHONY: unit-test-coverage .PHONY: unit-test-coverage
unit-test-coverage: unit-test-coverage:
@ -754,11 +753,11 @@ update-translations:
.PHONY: generate-license .PHONY: generate-license
generate-license: generate-license:
GO111MODULE=on $(GO) run build/generate-licenses.go $(GO) run build/generate-licenses.go
.PHONY: generate-gitignore .PHONY: generate-gitignore
generate-gitignore: generate-gitignore:
GO111MODULE=on $(GO) run build/generate-gitignores.go $(GO) run build/generate-gitignores.go
.PHONY: generate-images .PHONY: generate-images
generate-images: | node_modules generate-images: | node_modules

View file

@ -11,12 +11,12 @@ Please **DO NOT** file a public issue, instead send your report privately to `se
Due to the sensitive nature of security information, you can use below GPG public key encrypt your mail body. Due to the sensitive nature of security information, you can use below GPG public key encrypt your mail body.
The PGP key is valid until June 24, 2024. The PGP key is valid until June 24, 2024.
Key ID: 6FCD2D5B Key ID: 6FCD2D5B
Key Type: RSA Key Type: RSA
Expires: 6/24/2024 Expires: 6/24/2024
Key Size: 4096/4096 Key Size: 4096/4096
Fingerprint: 3DE0 3D1E 144A 7F06 9359 99DC AAFD 2381 6FCD 2D5B Fingerprint: 3DE0 3D1E 144A 7F06 9359 99DC AAFD 2381 6FCD 2D5B
UserID: Gitea Security <security@gitea.io> UserID: Gitea Security <security@gitea.io>
``` ```

View file

@ -34,6 +34,10 @@ var (
Name: "not-active", Name: "not-active",
Usage: "Deactivate the authentication source.", Usage: "Deactivate the authentication source.",
}, },
cli.BoolFlag{
Name: "active",
Usage: "Activate the authentication source.",
},
cli.StringFlag{ cli.StringFlag{
Name: "security-protocol", Name: "security-protocol",
Usage: "Security protocol name.", Usage: "Security protocol name.",
@ -117,6 +121,10 @@ var (
Name: "synchronize-users", Name: "synchronize-users",
Usage: "Enable user synchronization.", Usage: "Enable user synchronization.",
}, },
cli.BoolFlag{
Name: "disable-synchronize-users",
Usage: "Disable user synchronization.",
},
cli.UintFlag{ cli.UintFlag{
Name: "page-size", Name: "page-size",
Usage: "Search page size.", Usage: "Search page size.",
@ -183,9 +191,15 @@ func parseAuthSource(c *cli.Context, authSource *auth.Source) {
if c.IsSet("not-active") { if c.IsSet("not-active") {
authSource.IsActive = !c.Bool("not-active") authSource.IsActive = !c.Bool("not-active")
} }
if c.IsSet("active") {
authSource.IsActive = c.Bool("active")
}
if c.IsSet("synchronize-users") { if c.IsSet("synchronize-users") {
authSource.IsSyncEnabled = c.Bool("synchronize-users") authSource.IsSyncEnabled = c.Bool("synchronize-users")
} }
if c.IsSet("disable-synchronize-users") {
authSource.IsSyncEnabled = !c.Bool("disable-synchronize-users")
}
} }
// parseLdapConfig assigns values on config according to command line flags. // parseLdapConfig assigns values on config according to command line flags.

View file

@ -858,6 +858,36 @@ func TestUpdateLdapBindDn(t *testing.T) {
}, },
errMsg: "Invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2", errMsg: "Invalid authentication type. expected: LDAP (via BindDN), actual: OAuth2",
}, },
// case 24
{
args: []string{
"ldap-test",
"--id", "24",
"--name", "ldap (via Bind DN) flip 'active' and 'user sync' attributes",
"--active",
"--disable-synchronize-users",
},
id: 24,
existingAuthSource: &auth.Source{
Type: auth.LDAP,
IsActive: false,
IsSyncEnabled: true,
Cfg: &ldap.Source{
Name: "ldap (via Bind DN) flip 'active' and 'user sync' attributes",
Enabled: true,
},
},
authSource: &auth.Source{
Type: auth.LDAP,
Name: "ldap (via Bind DN) flip 'active' and 'user sync' attributes",
IsActive: true,
IsSyncEnabled: false,
Cfg: &ldap.Source{
Name: "ldap (via Bind DN) flip 'active' and 'user sync' attributes",
Enabled: true,
},
},
},
} }
for n, c := range cases { for n, c := range cases {
@ -1221,6 +1251,33 @@ func TestUpdateLdapSimpleAuth(t *testing.T) {
}, },
errMsg: "Invalid authentication type. expected: LDAP (simple auth), actual: PAM", errMsg: "Invalid authentication type. expected: LDAP (simple auth), actual: PAM",
}, },
// case 20
{
args: []string{
"ldap-test",
"--id", "20",
"--name", "ldap (simple auth) flip 'active' attribute",
"--active",
},
id: 20,
existingAuthSource: &auth.Source{
Type: auth.DLDAP,
IsActive: false,
Cfg: &ldap.Source{
Name: "ldap (simple auth) flip 'active' attribute",
Enabled: true,
},
},
authSource: &auth.Source{
Type: auth.DLDAP,
Name: "ldap (simple auth) flip 'active' attribute",
IsActive: true,
Cfg: &ldap.Source{
Name: "ldap (simple auth) flip 'active' attribute",
Enabled: true,
},
},
},
} }
for n, c := range cases { for n, c := range cases {

View file

@ -10,6 +10,7 @@ import (
"strings" "strings"
"code.gitea.io/gitea/modules/convert" "code.gitea.io/gitea/modules/convert"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration" base "code.gitea.io/gitea/modules/migration"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -83,6 +84,11 @@ func runDumpRepository(ctx *cli.Context) error {
return err return err
} }
// migrations.GiteaLocalUploader depends on git module
if err := git.InitSimple(context.Background()); err != nil {
return err
}
log.Info("AppPath: %s", setting.AppPath) log.Info("AppPath: %s", setting.AppPath)
log.Info("AppWorkPath: %s", setting.AppWorkPath) log.Info("AppWorkPath: %s", setting.AppWorkPath)
log.Info("Custom path: %s", setting.CustomPath) log.Info("Custom path: %s", setting.CustomPath)
@ -128,7 +134,9 @@ func runDumpRepository(ctx *cli.Context) error {
} else { } else {
units := strings.Split(ctx.String("units"), ",") units := strings.Split(ctx.String("units"), ",")
for _, unit := range units { for _, unit := range units {
switch strings.ToLower(unit) { switch strings.ToLower(strings.TrimSpace(unit)) {
case "":
continue
case "wiki": case "wiki":
opts.Wiki = true opts.Wiki = true
case "issues": case "issues":
@ -145,6 +153,8 @@ func runDumpRepository(ctx *cli.Context) error {
opts.Comments = true opts.Comments = true
case "pull_requests": case "pull_requests":
opts.PullRequests = true opts.PullRequests = true
default:
return errors.New("invalid unit: " + unit)
} }
} }
} }

View file

@ -7,6 +7,7 @@ package cmd
import ( import (
"errors" "errors"
"net/http" "net/http"
"strings"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/private" "code.gitea.io/gitea/modules/private"
@ -37,10 +38,10 @@ var CmdRestoreRepository = cli.Command{
Value: "", Value: "",
Usage: "Restore destination repository name", Usage: "Restore destination repository name",
}, },
cli.StringSliceFlag{ cli.StringFlag{
Name: "units", Name: "units",
Value: nil, Value: "",
Usage: `Which items will be restored, one or more units should be repeated with this flag. Usage: `Which items will be restored, one or more units should be separated as comma.
wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`, wiki, issues, labels, releases, release_assets, milestones, pull_requests, comments are allowed. Empty means all units.`,
}, },
cli.BoolFlag{ cli.BoolFlag{
@ -55,13 +56,16 @@ func runRestoreRepository(c *cli.Context) error {
defer cancel() defer cancel()
setting.LoadFromExisting() setting.LoadFromExisting()
var units []string
if s := c.String("units"); s != "" {
units = strings.Split(s, ",")
}
statusCode, errStr := private.RestoreRepo( statusCode, errStr := private.RestoreRepo(
ctx, ctx,
c.String("repo_dir"), c.String("repo_dir"),
c.String("owner_name"), c.String("owner_name"),
c.String("repo_name"), c.String("repo_name"),
c.StringSlice("units"), units,
c.Bool("validation"), c.Bool("validation"),
) )
if statusCode == http.StatusOK { if statusCode == http.StatusOK {

View file

@ -475,20 +475,6 @@ ENABLE = true
;; Maximum length of oauth2 token/cookie stored on server ;; Maximum length of oauth2 token/cookie stored on server
;MAX_TOKEN_LENGTH = 32767 ;MAX_TOKEN_LENGTH = 32767
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[U2F]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; NOTE: THE DEFAULT VALUES HERE WILL NEED TO BE CHANGED
;; Two Factor authentication with security keys
;; https://developers.yubico.com/U2F/App_ID.html
;;
;; DEPRECATED - this only applies to previously registered security keys using the U2F standard
APP_ID = ; e.g. http://localhost:3000/
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[log] [log]
@ -1245,7 +1231,7 @@ PATH =
;; Define allowed algorithms and their minimum key length (use -1 to disable a type) ;; Define allowed algorithms and their minimum key length (use -1 to disable a type)
;ED25519 = 256 ;ED25519 = 256
;ECDSA = 256 ;ECDSA = 256
;RSA = 2048 ;RSA = 2047 ; we allow 2047 here because an otherwise valid 2048 bit RSA key can be reported as having 2047 bit length
;DSA = -1 ; set to 1024 to switch on ;DSA = -1 ; set to 1024 to switch on
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@ -1690,7 +1676,7 @@ PATH =
;ENABLED = true ;ENABLED = true
;; ;;
;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. ;; Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
;ALLOWED_TYPES = .docx,.gif,.gz,.jpeg,.jpg,.mp4,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip ;ALLOWED_TYPES = .csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip
;; ;;
;; Max size of each file. Defaults to 4MB ;; Max size of each file. Defaults to 4MB
;MAX_SIZE = 4 ;MAX_SIZE = 4
@ -2127,7 +2113,7 @@ PATH =
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; The first locale will be used as the default if user browser's language doesn't match any locale in the list. ;; The first locale will be used as the default if user browser's language doesn't match any locale in the list.
;LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pt-PT,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR,el-GR,fa-IR,hu-HU,id-ID,ml-IN ;LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pt-PT,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sv-SE,ko-KR,el-GR,fa-IR,hu-HU,id-ID,ml-IN
;NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,Français,Nederlands,Latviešu,Русский,Українська,日本語,Español,Português do Brasil,Português de Portugal,Polski,Български,Italiano,Suomi,Türkçe,Čeština,Српски,Svenska,한국어,Ελληνικά,فارسی,Magyar nyelv,Bahasa Indonesia,മലയാളം ;NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,Français,Nederlands,Latviešu,Русский,Українська,日本語,Español,Português do Brasil,Português de Portugal,Polski,Български,Italiano,Suomi,Türkçe,Čeština,Српски,Svenska,한국어,Ελληνικά,فارسی,Magyar nyelv,Bahasa Indonesia,മലയാളം
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -621,7 +621,7 @@ Define allowed algorithms and their minimum key length (use -1 to disable a type
- `ED25519`: **256** - `ED25519`: **256**
- `ECDSA`: **256** - `ECDSA`: **256**
- `RSA`: **2048** - `RSA`: **2047**: We set 2047 here because an otherwise valid 2048 RSA key can be reported as 2047 length.
- `DSA`: **-1**: DSA is now disabled by default. Set to **1024** to re-enable but ensure you may need to reconfigure your SSHD provider - `DSA`: **-1**: DSA is now disabled by default. Set to **1024** to re-enable but ensure you may need to reconfigure your SSHD provider
## Webhook (`webhook`) ## Webhook (`webhook`)
@ -742,7 +742,7 @@ Default templates for project boards:
## Issue and pull request attachments (`attachment`) ## Issue and pull request attachments (`attachment`)
- `ENABLED`: **true**: Whether issue and pull request attachments are enabled. - `ENABLED`: **true**: Whether issue and pull request attachments are enabled.
- `ALLOWED_TYPES`: **.docx,.gif,.gz,.jpeg,.jpg,mp4,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types. - `ALLOWED_TYPES`: **.csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip**: Comma-separated list of allowed file extensions (`.zip`), mime types (`text/plain`) or wildcard type (`image/*`, `audio/*`, `video/*`). Empty value or `*/*` allows all types.
- `MAX_SIZE`: **4**: Maximum size (MB). - `MAX_SIZE`: **4**: Maximum size (MB).
- `MAX_FILES`: **5**: Maximum number of attachments that can be uploaded at once. - `MAX_FILES`: **5**: Maximum number of attachments that can be uploaded at once.
- `STORAGE_TYPE`: **local**: Storage type for attachments, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]` - `STORAGE_TYPE`: **local**: Storage type for attachments, `local` for local disk or `minio` for s3 compatible object storage service, default is `local` or other name defined with `[storage.xxx]`
@ -999,13 +999,10 @@ Default templates for project boards:
## i18n (`i18n`) ## i18n (`i18n`)
- `LANGS`: **en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pt-PT,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR,el-GR,fa-IR,hu-HU,id-ID,ml-IN**: - `LANGS`: **en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pt-PT,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sv-SE,ko-KR,el-GR,fa-IR,hu-HU,id-ID,ml-IN**:
List of locales shown in language selector. The first locale will be used as the default if user browser's language doesn't match any locale in the list. List of locales shown in language selector. The first locale will be used as the default if user browser's language doesn't match any locale in the list.
- `NAMES`: **English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,Français,Nederlands,Latviešu,Русский,Українська,日本語,Español,Português do Brasil,Português de Portugal,Polski,Български,Italiano,Suomi,Türkçe,Čeština,Српски,Svenska,한국어,Ελληνικά,فارسی,Magyar nyelv,Bahasa Indonesia,മലയാളം**: Visible names corresponding to the locales - `NAMES`: **English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,Français,Nederlands,Latviešu,Русский,Українська,日本語,Español,Português do Brasil,Português de Portugal,Polski,Български,Italiano,Suomi,Türkçe,Čeština,Српски,Svenska,한국어,Ελληνικά,فارسی,Magyar nyelv,Bahasa Indonesia,മലയാളം**: Visible names corresponding to the locales
## U2F (`U2F`) **DEPRECATED**
- `APP_ID`: **`ROOT_URL`**: Declares the facet of the application which is used for authentication of previously registered U2F keys. Requires HTTPS.
## Markup (`markup`) ## Markup (`markup`)
- `MERMAID_MAX_SOURCE_CHARACTERS`: **5000**: Set the maximum size of a Mermaid source. (Set to -1 to disable) - `MERMAID_MAX_SOURCE_CHARACTERS`: **5000**: Set the maximum size of a Mermaid source. (Set to -1 to disable)

View file

@ -335,8 +335,8 @@ The list of themes a user can choose from can be configured with the `THEMES` va
To make a custom theme available to all users: To make a custom theme available to all users:
1. Add a CSS file to `$GITEA_PUBLIC/public/css/theme-<theme-name>.css`. 1. Add a CSS file to `$GITEA_CUSTOM/public/css/theme-<theme-name>.css`.
The value of `$GITEA_PUBLIC` of your instance can be queried by calling `gitea help` and looking up the value of "CustomPath". The value of `$GITEA_CUSTOM` of your instance can be queried by calling `gitea help` and looking up the value of "CustomPath".
2. Add `<theme-name>` to the comma-separated list of setting `THEMES` in `app.ini` 2. Add `<theme-name>` to the comma-separated list of setting `THEMES` in `app.ini`
Community themes are listed in [gitea/awesome-gitea#themes](https://gitea.com/gitea/awesome-gitea#themes). Community themes are listed in [gitea/awesome-gitea#themes](https://gitea.com/gitea/awesome-gitea#themes).

View file

@ -50,7 +50,7 @@ menu:
| 有寫入權限的儲存庫 Token | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ | | 有寫入權限的儲存庫 Token | ✓ | ✘ | ✓ | ✓ | ✓ | ✘ | ✓ |
| 內建 Container Registry | [](https://github.com/go-gitea/gitea/issues/2316) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ | | 內建 Container Registry | [](https://github.com/go-gitea/gitea/issues/2316) | ✘ | ✘ | ✓ | ✓ | ✘ | ✘ |
| 對外部 Git 鏡像 | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ | | 對外部 Git 鏡像 | ✓ | ✓ | ✘ | ✘ | ✓ | ✓ | ✓ |
| FIDO U2F (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ | | FIDO (2FA) | ✓ | ✘ | ✓ | ✓ | ✓ | ✓ | ✘ |
| 內建 CI/CD | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ | | 內建 CI/CD | ✘ | ✘ | ✓ | ✓ | ✓ | ✘ | ✘ |
| 子群組: 群組中的群組 | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ | | 子群組: 群組中的群組 | ✘ | ✘ | ✘ | ✓ | ✓ | ✘ | ✓ |

6
go.mod
View file

@ -66,7 +66,7 @@ require (
github.com/mattn/go-isatty v0.0.14 github.com/mattn/go-isatty v0.0.14
github.com/mattn/go-sqlite3 v1.14.12 github.com/mattn/go-sqlite3 v1.14.12
github.com/mholt/archiver/v3 v3.5.1 github.com/mholt/archiver/v3 v3.5.1
github.com/microcosm-cc/bluemonday v1.0.18 github.com/microcosm-cc/bluemonday v1.0.19
github.com/minio/minio-go/v7 v7.0.26 github.com/minio/minio-go/v7 v7.0.26
github.com/msteinert/pam v1.0.0 github.com/msteinert/pam v1.0.0
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
@ -94,9 +94,9 @@ require (
go.jolheiser.com/hcaptcha v0.0.4 go.jolheiser.com/hcaptcha v0.0.4
go.jolheiser.com/pwn v0.0.3 go.jolheiser.com/pwn v0.0.3
golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122 golang.org/x/crypto v0.0.0-20220507011949-2cf3adece122
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 golang.org/x/net v0.0.0-20220630215102-69896b714898
golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
golang.org/x/text v0.3.7 golang.org/x/text v0.3.7
golang.org/x/tools v0.1.10 golang.org/x/tools v0.1.10
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df

13
go.sum
View file

@ -1154,8 +1154,8 @@ github.com/mholt/acmez v1.0.2 h1:C8wsEBIUVi6e0DYoxqCcFuXtwc4AWXL/jgcDjF7mjVo=
github.com/mholt/acmez v1.0.2/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM= github.com/mholt/acmez v1.0.2/go.mod h1:8qnn8QA/Ewx8E3ZSsmscqsIjhhpxuy9vqdgbX2ceceM=
github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo=
github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4=
github.com/microcosm-cc/bluemonday v1.0.18 h1:6HcxvXDAi3ARt3slx6nTesbvorIc3QeTzBNRvWktHBo= github.com/microcosm-cc/bluemonday v1.0.19 h1:OI7hoF5FY4pFz2VA//RN8TfM0YJ2dJcl4P4APrCWy6c=
github.com/microcosm-cc/bluemonday v1.0.18/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= github.com/microcosm-cc/bluemonday v1.0.19/go.mod h1:QNzV2UbLK2/53oIIwTOyLUSABMkjZ4tqiyC1g/DyqxE=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
@ -1793,14 +1793,13 @@ golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -1947,8 +1946,8 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=

View file

@ -16,7 +16,7 @@ import (
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/services/auth" "code.gitea.io/gitea/services/auth"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -275,8 +275,7 @@ func TestLDAPUserSigninFailed(t *testing.T) {
addAuthSourceLDAP(t, "") addAuthSourceLDAP(t, "")
u := otherLDAPUsers[0] u := otherLDAPUsers[0]
testLoginFailed(t, u.UserName, u.Password, translation.NewLocale("en-US").Tr("form.username_password_incorrect"))
testLoginFailed(t, u.UserName, u.Password, i18n.Tr("en", "form.username_password_incorrect"))
} }
func TestLDAPUserSSHKeySync(t *testing.T) { func TestLDAPUserSSHKeySync(t *testing.T) {

View file

@ -9,7 +9,7 @@ import (
"net/url" "net/url"
"testing" "testing"
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -37,7 +37,7 @@ func TestUndoDeleteBranch(t *testing.T) {
htmlDoc, name := branchAction(t, ".undo-button") htmlDoc, name := branchAction(t, ".undo-button")
assert.Contains(t, assert.Contains(t,
htmlDoc.doc.Find(".ui.positive.message").Text(), htmlDoc.doc.Find(".ui.positive.message").Text(),
i18n.Tr("en", "repo.branch.restore_success", name), translation.NewLocale("en-US").Tr("repo.branch.restore_success", name),
) )
}) })
} }
@ -46,7 +46,7 @@ func deleteBranch(t *testing.T) {
htmlDoc, name := branchAction(t, ".delete-branch-button") htmlDoc, name := branchAction(t, ".delete-branch-button")
assert.Contains(t, assert.Contains(t,
htmlDoc.doc.Find(".ui.positive.message").Text(), htmlDoc.doc.Find(".ui.positive.message").Text(),
i18n.Tr("en", "repo.branch.deletion_success", name), translation.NewLocale("en-US").Tr("repo.branch.deletion_success", name),
) )
} }

View file

@ -5,12 +5,17 @@
package integrations package integrations
import ( import (
"fmt"
"net/http"
"net/url"
"os" "os"
"testing" "testing"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/migrations" "code.gitea.io/gitea/services/migrations"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -40,3 +45,54 @@ func TestMigrateLocalPath(t *testing.T) {
setting.ImportLocalPaths = old setting.ImportLocalPaths = old
} }
func TestMigrateGiteaForm(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
AllowLocalNetworks := setting.Migrations.AllowLocalNetworks
setting.Migrations.AllowLocalNetworks = true
AppVer := setting.AppVer
// Gitea SDK (go-sdk) need to parse the AppVer from server response, so we must set it to a valid version string.
setting.AppVer = "1.16.0"
defer func() {
setting.Migrations.AllowLocalNetworks = AllowLocalNetworks
setting.AppVer = AppVer
migrations.Init()
}()
assert.NoError(t, migrations.Init())
ownerName := "user2"
repoName := "repo1"
repoOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: ownerName}).(*user_model.User)
session := loginUser(t, ownerName)
token := getTokenForLoggedInUser(t, session)
// Step 0: verify the repo is available
req := NewRequestf(t, "GET", fmt.Sprintf("/%s/%s", ownerName, repoName))
_ = session.MakeRequest(t, req, http.StatusOK)
// Step 1: get the Gitea migration form
req = NewRequestf(t, "GET", "/repo/migrate/?service_type=%d", structs.GiteaService)
resp := session.MakeRequest(t, req, http.StatusOK)
// Step 2: load the form
htmlDoc := NewHTMLParser(t, resp.Body)
link, exists := htmlDoc.doc.Find(`form.ui.form[action^="/repo/migrate"]`).Attr("action")
assert.True(t, exists, "The template has changed")
// Step 4: submit the migration to only migrate issues
migratedRepoName := "otherrepo"
req = NewRequestWithValues(t, "POST", link, map[string]string{
"_csrf": htmlDoc.GetCSRF(),
"service": fmt.Sprintf("%d", structs.GiteaService),
"clone_addr": fmt.Sprintf("%s%s/%s", u, ownerName, repoName),
"auth_token": token,
"issues": "on",
"repo_name": migratedRepoName,
"description": "",
"uid": fmt.Sprintf("%d", repoOwner.ID),
})
resp = session.MakeRequest(t, req, http.StatusSeeOther)
// Step 5: a redirection displays the migrated repository
loc := resp.Header().Get("Location")
assert.EqualValues(t, fmt.Sprintf("/%s/%s", ownerName, migratedRepoName), loc)
// Step 6: check the repo was created
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{Name: migratedRepoName})
})
}

View file

@ -27,7 +27,7 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/services/pull" "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
files_service "code.gitea.io/gitea/services/repository/files" files_service "code.gitea.io/gitea/services/repository/files"
@ -204,7 +204,7 @@ func TestCantMergeWorkInProgress(t *testing.T) {
text := strings.TrimSpace(htmlDoc.doc.Find(".merge-section > .item").Last().Text()) text := strings.TrimSpace(htmlDoc.doc.Find(".merge-section > .item").Last().Text())
assert.NotEmpty(t, text, "Can't find WIP text") assert.NotEmpty(t, text, "Can't find WIP text")
assert.Contains(t, text, i18n.Tr("en", "repo.pulls.cannot_merge_work_in_progress"), "Unable to find WIP text") assert.Contains(t, text, translation.NewLocale("en-US").Tr("repo.pulls.cannot_merge_work_in_progress"), "Unable to find WIP text")
assert.Contains(t, text, "[wip]", "Unable to find WIP text") assert.Contains(t, text, "[wip]", "Unable to find WIP text")
}) })
} }

View file

@ -14,7 +14,7 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -86,7 +86,7 @@ func TestCreateRelease(t *testing.T) {
session := loginUser(t, "user2") session := loginUser(t, "user2")
createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, false) createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, false)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.stable"), 4) checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").Tr("repo.release.stable"), 4)
} }
func TestCreateReleasePreRelease(t *testing.T) { func TestCreateReleasePreRelease(t *testing.T) {
@ -95,7 +95,7 @@ func TestCreateReleasePreRelease(t *testing.T) {
session := loginUser(t, "user2") session := loginUser(t, "user2")
createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", true, false) createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", true, false)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.prerelease"), 4) checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").Tr("repo.release.prerelease"), 4)
} }
func TestCreateReleaseDraft(t *testing.T) { func TestCreateReleaseDraft(t *testing.T) {
@ -104,7 +104,7 @@ func TestCreateReleaseDraft(t *testing.T) {
session := loginUser(t, "user2") session := loginUser(t, "user2")
createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, true) createNewRelease(t, session, "/user2/repo1", "v0.0.1", "v0.0.1", false, true)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", i18n.Tr("en", "repo.release.draft"), 4) checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.1", translation.NewLocale("en-US").Tr("repo.release.draft"), 4)
} }
func TestCreateReleasePaging(t *testing.T) { func TestCreateReleasePaging(t *testing.T) {
@ -124,11 +124,11 @@ func TestCreateReleasePaging(t *testing.T) {
} }
createNewRelease(t, session, "/user2/repo1", "v0.0.12", "v0.0.12", false, true) createNewRelease(t, session, "/user2/repo1", "v0.0.12", "v0.0.12", false, true)
checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.12", i18n.Tr("en", "repo.release.draft"), 10) checkLatestReleaseAndCount(t, session, "/user2/repo1", "v0.0.12", translation.NewLocale("en-US").Tr("repo.release.draft"), 10)
// Check that user4 does not see draft and still see 10 latest releases // Check that user4 does not see draft and still see 10 latest releases
session2 := loginUser(t, "user4") session2 := loginUser(t, "user4")
checkLatestReleaseAndCount(t, session2, "/user2/repo1", "v0.0.11", i18n.Tr("en", "repo.release.stable"), 10) checkLatestReleaseAndCount(t, session2, "/user2/repo1", "v0.0.11", translation.NewLocale("en-US").Tr("repo.release.stable"), 10)
} }
func TestViewReleaseListNoLogin(t *testing.T) { func TestViewReleaseListNoLogin(t *testing.T) {

View file

@ -13,7 +13,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -52,37 +52,37 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
OldRefSubURL: "branch/master", OldRefSubURL: "branch/master",
NewBranch: "feature/test1", NewBranch: "feature/test1",
ExpectedStatus: http.StatusSeeOther, ExpectedStatus: http.StatusSeeOther,
FlashMessage: i18n.Tr("en", "repo.branch.create_success", "feature/test1"), FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test1"),
}, },
{ {
OldRefSubURL: "branch/master", OldRefSubURL: "branch/master",
NewBranch: "", NewBranch: "",
ExpectedStatus: http.StatusSeeOther, ExpectedStatus: http.StatusSeeOther,
FlashMessage: i18n.Tr("en", "form.NewBranchName") + i18n.Tr("en", "form.require_error"), FlashMessage: translation.NewLocale("en-US").Tr("form.NewBranchName") + translation.NewLocale("en-US").Tr("form.require_error"),
}, },
{ {
OldRefSubURL: "branch/master", OldRefSubURL: "branch/master",
NewBranch: "feature=test1", NewBranch: "feature=test1",
ExpectedStatus: http.StatusSeeOther, ExpectedStatus: http.StatusSeeOther,
FlashMessage: i18n.Tr("en", "repo.branch.create_success", "feature=test1"), FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature=test1"),
}, },
{ {
OldRefSubURL: "branch/master", OldRefSubURL: "branch/master",
NewBranch: strings.Repeat("b", 101), NewBranch: strings.Repeat("b", 101),
ExpectedStatus: http.StatusSeeOther, ExpectedStatus: http.StatusSeeOther,
FlashMessage: i18n.Tr("en", "form.NewBranchName") + i18n.Tr("en", "form.max_size_error", "100"), FlashMessage: translation.NewLocale("en-US").Tr("form.NewBranchName") + translation.NewLocale("en-US").Tr("form.max_size_error", "100"),
}, },
{ {
OldRefSubURL: "branch/master", OldRefSubURL: "branch/master",
NewBranch: "master", NewBranch: "master",
ExpectedStatus: http.StatusSeeOther, ExpectedStatus: http.StatusSeeOther,
FlashMessage: i18n.Tr("en", "repo.branch.branch_already_exists", "master"), FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.branch_already_exists", "master"),
}, },
{ {
OldRefSubURL: "branch/master", OldRefSubURL: "branch/master",
NewBranch: "master/test", NewBranch: "master/test",
ExpectedStatus: http.StatusSeeOther, ExpectedStatus: http.StatusSeeOther,
FlashMessage: i18n.Tr("en", "repo.branch.branch_name_conflict", "master/test", "master"), FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.branch_name_conflict", "master/test", "master"),
}, },
{ {
OldRefSubURL: "commit/acd1d892867872cb47f3993468605b8aa59aa2e0", OldRefSubURL: "commit/acd1d892867872cb47f3993468605b8aa59aa2e0",
@ -93,21 +93,21 @@ func testCreateBranches(t *testing.T, giteaURL *url.URL) {
OldRefSubURL: "commit/65f1bf27bc3bf70f64657658635e66094edbcb4d", OldRefSubURL: "commit/65f1bf27bc3bf70f64657658635e66094edbcb4d",
NewBranch: "feature/test3", NewBranch: "feature/test3",
ExpectedStatus: http.StatusSeeOther, ExpectedStatus: http.StatusSeeOther,
FlashMessage: i18n.Tr("en", "repo.branch.create_success", "feature/test3"), FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test3"),
}, },
{ {
OldRefSubURL: "branch/master", OldRefSubURL: "branch/master",
NewBranch: "v1.0.0", NewBranch: "v1.0.0",
CreateRelease: "v1.0.0", CreateRelease: "v1.0.0",
ExpectedStatus: http.StatusSeeOther, ExpectedStatus: http.StatusSeeOther,
FlashMessage: i18n.Tr("en", "repo.branch.tag_collision", "v1.0.0"), FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.tag_collision", "v1.0.0"),
}, },
{ {
OldRefSubURL: "tag/v1.0.0", OldRefSubURL: "tag/v1.0.0",
NewBranch: "feature/test4", NewBranch: "feature/test4",
CreateRelease: "v1.0.1", CreateRelease: "v1.0.1",
ExpectedStatus: http.StatusSeeOther, ExpectedStatus: http.StatusSeeOther,
FlashMessage: i18n.Tr("en", "repo.branch.create_success", "feature/test4"), FlashMessage: translation.NewLocale("en-US").Tr("repo.branch.create_success", "feature/test4"),
}, },
} }
for _, test := range tests { for _, test := range tests {

View file

@ -11,7 +11,7 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -47,10 +47,10 @@ func TestSignin(t *testing.T) {
password string password string
message string message string
}{ }{
{username: "wrongUsername", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")}, {username: "wrongUsername", password: "wrongPassword", message: translation.NewLocale("en-US").Tr("form.username_password_incorrect")},
{username: "wrongUsername", password: "password", message: i18n.Tr("en", "form.username_password_incorrect")}, {username: "wrongUsername", password: "password", message: translation.NewLocale("en-US").Tr("form.username_password_incorrect")},
{username: "user15", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")}, {username: "user15", password: "wrongPassword", message: translation.NewLocale("en-US").Tr("form.username_password_incorrect")},
{username: "user1@example.com", password: "wrongPassword", message: i18n.Tr("en", "form.username_password_incorrect")}, {username: "user1@example.com", password: "wrongPassword", message: translation.NewLocale("en-US").Tr("form.username_password_incorrect")},
} }
for _, s := range samples { for _, s := range samples {

View file

@ -13,7 +13,7 @@ import (
"code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -68,9 +68,9 @@ func TestSignupEmail(t *testing.T) {
wantStatus int wantStatus int
wantMsg string wantMsg string
}{ }{
{"exampleUser@example.com\r\n", http.StatusOK, i18n.Tr("en", "form.email_invalid")}, {"exampleUser@example.com\r\n", http.StatusOK, translation.NewLocale("en-US").Tr("form.email_invalid")},
{"exampleUser@example.com\r", http.StatusOK, i18n.Tr("en", "form.email_invalid")}, {"exampleUser@example.com\r", http.StatusOK, translation.NewLocale("en-US").Tr("form.email_invalid")},
{"exampleUser@example.com\n", http.StatusOK, i18n.Tr("en", "form.email_invalid")}, {"exampleUser@example.com\n", http.StatusOK, translation.NewLocale("en-US").Tr("form.email_invalid")},
{"exampleUser@example.com", http.StatusSeeOther, ""}, {"exampleUser@example.com", http.StatusSeeOther, ""},
} }

View file

@ -14,7 +14,7 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -67,7 +67,7 @@ func TestRenameInvalidUsername(t *testing.T) {
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
assert.Contains(t, assert.Contains(t,
htmlDoc.doc.Find(".ui.negative.message").Text(), htmlDoc.doc.Find(".ui.negative.message").Text(),
i18n.Tr("en", "form.alpha_dash_dot_error"), translation.NewLocale("en-US").Tr("form.alpha_dash_dot_error"),
) )
unittest.AssertNotExistsBean(t, &user_model.User{Name: invalidUsername}) unittest.AssertNotExistsBean(t, &user_model.User{Name: invalidUsername})
@ -131,7 +131,7 @@ func TestRenameReservedUsername(t *testing.T) {
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
assert.Contains(t, assert.Contains(t,
htmlDoc.doc.Find(".ui.negative.message").Text(), htmlDoc.doc.Find(".ui.negative.message").Text(),
i18n.Tr("en", "user.form.name_reserved", reservedUsername), translation.NewLocale("en-US").Tr("user.form.name_reserved", reservedUsername),
) )
unittest.AssertNotExistsBean(t, &user_model.User{Name: reservedUsername}) unittest.AssertNotExistsBean(t, &user_model.User{Name: reservedUsername})

View file

@ -92,12 +92,12 @@ func init() {
// TableIndices implements xorm's TableIndices interface // TableIndices implements xorm's TableIndices interface
func (a *Action) TableIndices() []*schemas.Index { func (a *Action) TableIndices() []*schemas.Index {
repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType) actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted") actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
repoIndex := schemas.NewIndex("r_c_u_d", schemas.IndexType)
repoIndex.AddColumn("repo_id", "created_unix", "user_id", "is_deleted")
return []*schemas.Index{actUserIndex, repoIndex} return []*schemas.Index{actUserIndex, repoIndex}
} }

View file

@ -124,6 +124,17 @@ func ChangeProjectAssign(issue *Issue, doer *user_model.User, newProjectID int64
func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error { func addUpdateIssueProject(ctx context.Context, issue *Issue, doer *user_model.User, newProjectID int64) error {
oldProjectID := issue.projectID(ctx) oldProjectID := issue.projectID(ctx)
// Only check if we add a new project and not remove it.
if newProjectID > 0 {
newProject, err := project_model.GetProjectByID(ctx, newProjectID)
if err != nil {
return err
}
if newProject.RepoID != issue.RepoID {
return fmt.Errorf("issue's repository is not the same as project's repository")
}
}
if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil { if _, err := db.GetEngine(ctx).Where("project_issue.issue_id=?", issue.ID).Delete(&project_model.ProjectIssue{}); err != nil {
return err return err
} }

View file

@ -124,6 +124,11 @@ func NewMilestone(m *Milestone) (err error) {
return committer.Commit() return committer.Commit()
} }
// HasMilestoneByRepoID returns if the milestone exists in the repository.
func HasMilestoneByRepoID(ctx context.Context, repoID, id int64) (bool, error) {
return db.GetEngine(ctx).ID(id).Where("repo_id=?", repoID).Exist(new(Milestone))
}
// GetMilestoneByRepoID returns the milestone in a repository. // GetMilestoneByRepoID returns the milestone in a repository.
func GetMilestoneByRepoID(ctx context.Context, repoID, id int64) (*Milestone, error) { func GetMilestoneByRepoID(ctx context.Context, repoID, id int64) (*Milestone, error) {
m := new(Milestone) m := new(Milestone)
@ -356,7 +361,7 @@ func (opts GetMilestonesOption) toCond() builder.Cond {
} }
if len(opts.Name) != 0 { if len(opts.Name) != 0 {
cond = cond.And(builder.Like{"name", opts.Name}) cond = cond.And(builder.Like{"UPPER(name)", strings.ToUpper(opts.Name)})
} }
return cond return cond

View file

@ -56,6 +56,9 @@ type Version struct {
Version int64 Version int64
} }
// Use noopMigration when there is a migration that has been no-oped
var noopMigration = func(_ *xorm.Engine) error { return nil }
// This is a sequence of migrations. Add new migrations to the bottom of the list. // This is a sequence of migrations. Add new migrations to the bottom of the list.
// If you want to "retire" a migration, remove it from the top of the list and // If you want to "retire" a migration, remove it from the top of the list and
// update minDBVersion accordingly // update minDBVersion accordingly
@ -351,7 +354,7 @@ var migrations = []Migration{
// v198 -> v199 // v198 -> v199
NewMigration("Add issue content history table", addTableIssueContentHistory), NewMigration("Add issue content history table", addTableIssueContentHistory),
// v199 -> v200 // v199 -> v200
NewMigration("No-op (remote version is using AppState now)", addRemoteVersionTableNoop), NewMigration("No-op (remote version is using AppState now)", noopMigration),
// v200 -> v201 // v200 -> v201
NewMigration("Add table app_state", addTableAppState), NewMigration("Add table app_state", addTableAppState),
// v201 -> v202 // v201 -> v202
@ -388,9 +391,11 @@ var migrations = []Migration{
// v215 -> v216 // v215 -> v216
NewMigration("allow to view files in PRs", addReviewViewedFiles), NewMigration("allow to view files in PRs", addReviewViewedFiles),
// v216 -> v217 // v216 -> v217
NewMigration("Improve Action table indices", improveActionTableIndices), NewMigration("No-op (Improve Action table indices v1)", noopMigration),
// v217 -> v218 // v217 -> v218
NewMigration("Alter hook_task table TEXT fields to LONGTEXT", alterHookTaskTextFieldsToLongText), NewMigration("Alter hook_task table TEXT fields to LONGTEXT", alterHookTaskTextFieldsToLongText),
// v218 -> v219
NewMigration("Improve Action table indices v2", improveActionTableIndices),
} }
// GetCurrentDBVersion returns the current db version // GetCurrentDBVersion returns the current db version

View file

@ -4,11 +4,4 @@
package migrations package migrations
import ( // We used to use a table `remote_version` to store information for updater, now we use `AppState`, so this migration task is a no-op now.
"xorm.io/xorm"
)
func addRemoteVersionTableNoop(x *xorm.Engine) error {
// we used to use a table `remote_version` to store information for updater, now we use `AppState`, so this migration task is a no-op now.
return nil
}

View file

@ -4,43 +4,5 @@
package migrations package migrations
import ( // This migration added non-ideal indices to the action table which on larger datasets slowed things down
"code.gitea.io/gitea/modules/timeutil" // it has been superceded by v218.go
"xorm.io/xorm"
"xorm.io/xorm/schemas"
)
type improveActionTableIndicesAction struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 // Receiver user id.
OpType int
ActUserID int64 // Action user id.
RepoID int64
CommentID int64 `xorm:"INDEX"`
IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
RefName string
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
Content string `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
}
// TableName sets the name of this table
func (a *improveActionTableIndicesAction) TableName() string {
return "action"
}
// TableIndices implements xorm's TableIndices interface
func (a *improveActionTableIndicesAction) TableIndices() []*schemas.Index {
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
repoIndex := schemas.NewIndex("r_c_u_d", schemas.IndexType)
repoIndex.AddColumn("repo_id", "created_unix", "user_id", "is_deleted")
return []*schemas.Index{actUserIndex, repoIndex}
}
func improveActionTableIndices(x *xorm.Engine) error {
return x.Sync2(&improveActionTableIndicesAction{})
}

46
models/migrations/v218.go Normal file
View file

@ -0,0 +1,46 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package migrations
import (
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/xorm"
"xorm.io/xorm/schemas"
)
type improveActionTableIndicesAction struct {
ID int64 `xorm:"pk autoincr"`
UserID int64 // Receiver user id.
OpType int
ActUserID int64 // Action user id.
RepoID int64
CommentID int64 `xorm:"INDEX"`
IsDeleted bool `xorm:"NOT NULL DEFAULT false"`
RefName string
IsPrivate bool `xorm:"NOT NULL DEFAULT false"`
Content string `xorm:"TEXT"`
CreatedUnix timeutil.TimeStamp `xorm:"created"`
}
// TableName sets the name of this table
func (*improveActionTableIndicesAction) TableName() string {
return "action"
}
// TableIndices implements xorm's TableIndices interface
func (*improveActionTableIndicesAction) TableIndices() []*schemas.Index {
repoIndex := schemas.NewIndex("r_u_d", schemas.IndexType)
repoIndex.AddColumn("repo_id", "user_id", "is_deleted")
actUserIndex := schemas.NewIndex("au_r_c_u_d", schemas.IndexType)
actUserIndex.AddColumn("act_user_id", "repo_id", "created_unix", "user_id", "is_deleted")
return []*schemas.Index{actUserIndex, repoIndex}
}
func improveActionTableIndices(x *xorm.Engine) error {
return x.Sync2(&improveActionTableIndicesAction{})
}

View file

@ -316,37 +316,45 @@ func (u *User) GenerateEmailActivateCode(email string) string {
} }
// GetUserFollowers returns range of user's followers. // GetUserFollowers returns range of user's followers.
func GetUserFollowers(u *User, listOptions db.ListOptions) ([]*User, error) { func GetUserFollowers(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
sess := db.GetEngine(db.DefaultContext). sess := db.GetEngine(ctx).
Select("`user`.*").
Join("LEFT", "follow", "`user`.id=follow.user_id").
Where("follow.follow_id=?", u.ID). Where("follow.follow_id=?", u.ID).
Join("LEFT", "follow", "`user`.id=follow.user_id") And(isUserVisibleToViewerCond(viewer))
if listOptions.Page != 0 { if listOptions.Page != 0 {
sess = db.SetSessionPagination(sess, &listOptions) sess = db.SetSessionPagination(sess, &listOptions)
users := make([]*User, 0, listOptions.PageSize) users := make([]*User, 0, listOptions.PageSize)
return users, sess.Find(&users) count, err := sess.FindAndCount(&users)
return users, count, err
} }
users := make([]*User, 0, 8) users := make([]*User, 0, 8)
return users, sess.Find(&users) count, err := sess.FindAndCount(&users)
return users, count, err
} }
// GetUserFollowing returns range of user's following. // GetUserFollowing returns range of user's following.
func GetUserFollowing(u *User, listOptions db.ListOptions) ([]*User, error) { func GetUserFollowing(ctx context.Context, u, viewer *User, listOptions db.ListOptions) ([]*User, int64, error) {
sess := db.GetEngine(db.DefaultContext). sess := db.GetEngine(db.DefaultContext).
Select("`user`.*").
Join("LEFT", "follow", "`user`.id=follow.follow_id").
Where("follow.user_id=?", u.ID). Where("follow.user_id=?", u.ID).
Join("LEFT", "follow", "`user`.id=follow.follow_id") And(isUserVisibleToViewerCond(viewer))
if listOptions.Page != 0 { if listOptions.Page != 0 {
sess = db.SetSessionPagination(sess, &listOptions) sess = db.SetSessionPagination(sess, &listOptions)
users := make([]*User, 0, listOptions.PageSize) users := make([]*User, 0, listOptions.PageSize)
return users, sess.Find(&users) count, err := sess.FindAndCount(&users)
return users, count, err
} }
users := make([]*User, 0, 8) users := make([]*User, 0, 8)
return users, sess.Find(&users) count, err := sess.FindAndCount(&users)
return users, count, err
} }
// NewGitSig generates and returns the signature of given user. // NewGitSig generates and returns the signature of given user.
@ -485,6 +493,9 @@ func (u *User) GitName() string {
// ShortName ellipses username to length // ShortName ellipses username to length
func (u *User) ShortName(length int) string { func (u *User) ShortName(length int) string {
if setting.UI.DefaultShowFullName && len(u.FullName) > 0 {
return base.EllipsisString(u.FullName, length)
}
return base.EllipsisString(u.Name, length) return base.EllipsisString(u.Name, length)
} }
@ -1219,6 +1230,39 @@ func GetAdminUser() (*User, error) {
return &admin, nil return &admin, nil
} }
func isUserVisibleToViewerCond(viewer *User) builder.Cond {
if viewer != nil && viewer.IsAdmin {
return builder.NewCond()
}
if viewer == nil || viewer.IsRestricted {
return builder.Eq{
"`user`.visibility": structs.VisibleTypePublic,
}
}
return builder.Neq{
"`user`.visibility": structs.VisibleTypePrivate,
}.Or(
builder.In("`user`.id",
builder.
Select("`follow`.user_id").
From("follow").
Where(builder.Eq{"`follow`.follow_id": viewer.ID})),
builder.In("`user`.id",
builder.
Select("`team_user`.uid").
From("team_user").
Join("INNER", "`team_user` AS t2", "`team_user`.id = `t2`.id").
Where(builder.Eq{"`t2`.uid": viewer.ID})),
builder.In("`user`.id",
builder.
Select("`team_user`.uid").
From("team_user").
Join("INNER", "`team_user` AS t2", "`team_user`.org_id = `t2`.org_id").
Where(builder.Eq{"`t2`.uid": viewer.ID})))
}
// IsUserVisibleToViewer check if viewer is able to see user profile // IsUserVisibleToViewer check if viewer is able to see user profile
func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool { func IsUserVisibleToViewer(ctx context.Context, u, viewer *User) bool {
if viewer != nil && viewer.IsAdmin { if viewer != nil && viewer.IsAdmin {

View file

@ -794,7 +794,7 @@ func Contexter() func(next http.Handler) http.Handler {
ctx.Data["UnitPullsGlobalDisabled"] = unit.TypePullRequests.UnitGlobalDisabled() ctx.Data["UnitPullsGlobalDisabled"] = unit.TypePullRequests.UnitGlobalDisabled()
ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled() ctx.Data["UnitProjectsGlobalDisabled"] = unit.TypeProjects.UnitGlobalDisabled()
ctx.Data["i18n"] = locale ctx.Data["locale"] = locale
ctx.Data["AllLangs"] = translation.AllLangs() ctx.Data["AllLangs"] = translation.AllLangs()
next.ServeHTTP(ctx.Resp, ctx.Req) next.ServeHTTP(ctx.Resp, ctx.Req)

View file

@ -58,6 +58,29 @@ func checkUserEmail(ctx context.Context, logger log.Logger, _ bool) error {
return nil return nil
} }
// From time to time Gitea makes changes to the reserved usernames and which symbols
// are allowed for various reasons. This check helps with detecting users that, according
// to our reserved names, don't have a valid username.
func checkUserName(ctx context.Context, logger log.Logger, _ bool) error {
var invalidUserCount int64
if err := iterateUserAccounts(ctx, func(u *user.User) error {
if err := user.IsUsableUsername(u.Name); err != nil {
invalidUserCount++
logger.Warn("User[id=%d] does not have a valid username: %v", u.ID, err)
}
return nil
}); err != nil {
return fmt.Errorf("iterateUserAccounts: %v", err)
}
if invalidUserCount == 0 {
logger.Info("All users have a valid username.")
} else {
logger.Warn("%d user(s) have a non-valid username.", invalidUserCount)
}
return nil
}
func init() { func init() {
Register(&Check{ Register(&Check{
Title: "Check if users has an valid email address", Title: "Check if users has an valid email address",
@ -66,4 +89,11 @@ func init() {
Run: checkUserEmail, Run: checkUserEmail,
Priority: 9, Priority: 9,
}) })
Register(&Check{
Title: "Check if users have a valid username",
Name: "check-user-names",
IsDefault: false,
Run: checkUserName,
Priority: 9,
})
} }

View file

@ -284,7 +284,7 @@ func (b *ElasticSearchIndexer) Index(ctx context.Context, repo *repo_model.Repos
reqs := make([]elastic.BulkableRequest, 0) reqs := make([]elastic.BulkableRequest, 0)
if len(changes.Updates) > 0 { if len(changes.Updates) > 0 {
// Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first! // Now because of some insanity with git cat-file not immediately failing if not run in a valid git directory we need to run git rev-parse first!
if err := git.EnsureValidGitRepository(git.DefaultContext, repo.RepoPath()); err != nil { if err := git.EnsureValidGitRepository(ctx, repo.RepoPath()); err != nil {
log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err) log.Error("Unable to open git repo: %s for %-v: %v", repo.RepoPath(), repo, err)
return err return err
} }

View file

@ -841,9 +841,10 @@ func issueIndexPatternProcessor(ctx *RenderContext, node *html.Node) {
// Repos with external issue trackers might still need to reference local PRs // Repos with external issue trackers might still need to reference local PRs
// We need to concern with the first one that shows up in the text, whichever it is // We need to concern with the first one that shows up in the text, whichever it is
if hasExtTrackFormat && !isNumericStyle { if hasExtTrackFormat && !isNumericStyle && refNumeric != nil {
// If numeric (PR) was found, and it was BEFORE the non-numeric pattern, use that // If numeric (PR) was found, and it was BEFORE the non-numeric pattern, use that
if foundNumeric && refNumeric.RefLocation.Start < ref.RefLocation.Start { // Allow a free-pass when non-numeric pattern wasn't found.
if found && (ref == nil || refNumeric.RefLocation.Start < ref.RefLocation.Start) {
found = foundNumeric found = foundNumeric
ref = refNumeric ref = refNumeric
} }

View file

@ -9,7 +9,7 @@ import (
"net/url" "net/url"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation"
"github.com/yuin/goldmark/ast" "github.com/yuin/goldmark/ast"
) )
@ -18,7 +18,7 @@ func createTOCNode(toc []markup.Header, lang string) ast.Node {
details := NewDetails() details := NewDetails()
summary := NewSummary() summary := NewSummary()
summary.AppendChild(summary, ast.NewString([]byte(i18n.Tr(lang, "toc")))) summary.AppendChild(summary, ast.NewString([]byte(translation.NewLocale(lang).Tr("toc"))))
details.AppendChild(details, summary) details.AppendChild(details, summary)
ul := ast.NewList('-') ul := ast.NewList('-')
details.AppendChild(details, ul) details.AppendChild(details, ul)

View file

@ -19,52 +19,52 @@ func (n NullDownloader) SetContext(_ context.Context) {}
// GetRepoInfo returns a repository information // GetRepoInfo returns a repository information
func (n NullDownloader) GetRepoInfo() (*Repository, error) { func (n NullDownloader) GetRepoInfo() (*Repository, error) {
return nil, &ErrNotSupported{Entity: "RepoInfo"} return nil, ErrNotSupported{Entity: "RepoInfo"}
} }
// GetTopics return repository topics // GetTopics return repository topics
func (n NullDownloader) GetTopics() ([]string, error) { func (n NullDownloader) GetTopics() ([]string, error) {
return nil, &ErrNotSupported{Entity: "Topics"} return nil, ErrNotSupported{Entity: "Topics"}
} }
// GetMilestones returns milestones // GetMilestones returns milestones
func (n NullDownloader) GetMilestones() ([]*Milestone, error) { func (n NullDownloader) GetMilestones() ([]*Milestone, error) {
return nil, &ErrNotSupported{Entity: "Milestones"} return nil, ErrNotSupported{Entity: "Milestones"}
} }
// GetReleases returns releases // GetReleases returns releases
func (n NullDownloader) GetReleases() ([]*Release, error) { func (n NullDownloader) GetReleases() ([]*Release, error) {
return nil, &ErrNotSupported{Entity: "Releases"} return nil, ErrNotSupported{Entity: "Releases"}
} }
// GetLabels returns labels // GetLabels returns labels
func (n NullDownloader) GetLabels() ([]*Label, error) { func (n NullDownloader) GetLabels() ([]*Label, error) {
return nil, &ErrNotSupported{Entity: "Labels"} return nil, ErrNotSupported{Entity: "Labels"}
} }
// GetIssues returns issues according start and limit // GetIssues returns issues according start and limit
func (n NullDownloader) GetIssues(page, perPage int) ([]*Issue, bool, error) { func (n NullDownloader) GetIssues(page, perPage int) ([]*Issue, bool, error) {
return nil, false, &ErrNotSupported{Entity: "Issues"} return nil, false, ErrNotSupported{Entity: "Issues"}
} }
// GetComments returns comments of an issue or PR // GetComments returns comments of an issue or PR
func (n NullDownloader) GetComments(commentable Commentable) ([]*Comment, bool, error) { func (n NullDownloader) GetComments(commentable Commentable) ([]*Comment, bool, error) {
return nil, false, &ErrNotSupported{Entity: "Comments"} return nil, false, ErrNotSupported{Entity: "Comments"}
} }
// GetAllComments returns paginated comments // GetAllComments returns paginated comments
func (n NullDownloader) GetAllComments(page, perPage int) ([]*Comment, bool, error) { func (n NullDownloader) GetAllComments(page, perPage int) ([]*Comment, bool, error) {
return nil, false, &ErrNotSupported{Entity: "AllComments"} return nil, false, ErrNotSupported{Entity: "AllComments"}
} }
// GetPullRequests returns pull requests according page and perPage // GetPullRequests returns pull requests according page and perPage
func (n NullDownloader) GetPullRequests(page, perPage int) ([]*PullRequest, bool, error) { func (n NullDownloader) GetPullRequests(page, perPage int) ([]*PullRequest, bool, error) {
return nil, false, &ErrNotSupported{Entity: "PullRequests"} return nil, false, ErrNotSupported{Entity: "PullRequests"}
} }
// GetReviews returns pull requests review // GetReviews returns pull requests review
func (n NullDownloader) GetReviews(reviewable Reviewable) ([]*Review, error) { func (n NullDownloader) GetReviews(reviewable Reviewable) ([]*Review, error) {
return nil, &ErrNotSupported{Entity: "Reviews"} return nil, ErrNotSupported{Entity: "Reviews"}
} }
// FormatCloneURL add authentication into remote URLs // FormatCloneURL add authentication into remote URLs

View file

@ -27,7 +27,7 @@ func newAttachmentService() {
Attachment.Storage = getStorage("attachments", storageType, sec) Attachment.Storage = getStorage("attachments", storageType, sec)
Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".docx,.gif,.gz,.jpeg,.jpg,.mp4,.log,.pdf,.png,.pptx,.txt,.xlsx,.zip") Attachment.AllowedTypes = sec.Key("ALLOWED_TYPES").MustString(".csv,.docx,.fodg,.fodp,.fods,.fodt,.gif,.gz,.jpeg,.jpg,.log,.md,.mov,.mp4,.odf,.odg,.odp,.ods,.odt,.pdf,.png,.pptx,.svg,.tgz,.txt,.webm,.xls,.xlsx,.zip")
Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(4) Attachment.MaxSize = sec.Key("MAX_SIZE").MustInt64(4)
Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5) Attachment.MaxFiles = sec.Key("MAX_FILES").MustInt(5)
Attachment.Enabled = sec.Key("ENABLED").MustBool(true) Attachment.Enabled = sec.Key("ENABLED").MustBool(true)

View file

@ -26,7 +26,6 @@ var defaultI18nLangNames = []string{
"fi-FI", "Suomi", "fi-FI", "Suomi",
"tr-TR", "Türkçe", "tr-TR", "Türkçe",
"cs-CZ", "Čeština", "cs-CZ", "Čeština",
"sr-SP", "Српски",
"sv-SE", "Svenska", "sv-SE", "Svenska",
"ko-KR", "한국어", "ko-KR", "한국어",
"el-GR", "Ελληνικά", "el-GR", "Ελληνικά",

View file

@ -170,7 +170,7 @@ var (
ServerMACs: []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1"}, ServerMACs: []string{"hmac-sha2-256-etm@openssh.com", "hmac-sha2-256", "hmac-sha1"},
KeygenPath: "ssh-keygen", KeygenPath: "ssh-keygen",
MinimumKeySizeCheck: true, MinimumKeySizeCheck: true,
MinimumKeySizes: map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 2048}, MinimumKeySizes: map[string]int{"ed25519": 256, "ed25519-sk": 256, "ecdsa": 256, "ecdsa-sk": 256, "rsa": 2047},
ServerHostKeys: []string{"ssh/gitea.rsa", "ssh/gogs.rsa"}, ServerHostKeys: []string{"ssh/gitea.rsa", "ssh/gogs.rsa"},
AuthorizedKeysCommandTemplate: "{{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}", AuthorizedKeysCommandTemplate: "{{.AppPath}} --config={{.CustomConf}} serv key-{{.Key.ID}}",
PerWriteTimeout: PerWriteTimeout, PerWriteTimeout: PerWriteTimeout,
@ -402,11 +402,6 @@ var (
MaxTokenLength: math.MaxInt16, MaxTokenLength: math.MaxInt16,
} }
// FIXME: DEPRECATED to be removed in v1.18.0
U2F = struct {
AppID string
}{}
// Metrics settings // Metrics settings
Metrics = struct { Metrics = struct {
Enabled bool Enabled bool
@ -1103,16 +1098,6 @@ func loadFromConf(allowEmpty bool, extraConfig string) {
for _, emoji := range UI.CustomEmojis { for _, emoji := range UI.CustomEmojis {
UI.CustomEmojisMap[emoji] = ":" + emoji + ":" UI.CustomEmojisMap[emoji] = ":" + emoji + ":"
} }
// FIXME: DEPRECATED to be removed in v1.18.0
U2F.AppID = strings.TrimSuffix(AppURL, "/")
if Cfg.Section("U2F").HasKey("APP_ID") {
log.Error("Deprecated setting `[U2F]` `APP_ID` present. This fallback will be removed in v1.18.0")
U2F.AppID = Cfg.Section("U2F").Key("APP_ID").MustString(strings.TrimSuffix(AppURL, "/"))
} else if Cfg.Section("u2f").HasKey("APP_ID") {
log.Error("Deprecated setting `[u2]` `APP_ID` present. This fallback will be removed in v1.18.0")
U2F.AppID = Cfg.Section("u2f").Key("APP_ID").MustString(strings.TrimSuffix(AppURL, "/"))
}
} }
func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) { func parseAuthorizedPrincipalsAllow(values []string) ([]string, bool) {

View file

@ -105,7 +105,6 @@ func NewFuncMap() []template.FuncMap {
"Str2html": Str2html, "Str2html": Str2html,
"TimeSince": timeutil.TimeSince, "TimeSince": timeutil.TimeSince,
"TimeSinceUnix": timeutil.TimeSinceUnix, "TimeSinceUnix": timeutil.TimeSinceUnix,
"RawTimeSince": timeutil.RawTimeSince,
"FileSize": base.FileSize, "FileSize": base.FileSize,
"PrettyNumber": base.PrettyNumber, "PrettyNumber": base.PrettyNumber,
"JsPrettyNumber": JsPrettyNumber, "JsPrettyNumber": JsPrettyNumber,
@ -484,7 +483,6 @@ func NewTextFuncMap() []texttmpl.FuncMap {
}, },
"TimeSince": timeutil.TimeSince, "TimeSince": timeutil.TimeSince,
"TimeSinceUnix": timeutil.TimeSinceUnix, "TimeSinceUnix": timeutil.TimeSinceUnix,
"RawTimeSince": timeutil.RawTimeSince,
"DateFmtLong": func(t time.Time) string { "DateFmtLong": func(t time.Time) string {
return t.Format(time.RFC1123Z) return t.Format(time.RFC1123Z)
}, },

View file

@ -12,7 +12,7 @@ import (
"time" "time"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation"
) )
// Seconds-based time units // Seconds-based time units
@ -29,146 +29,146 @@ func round(s float64) int64 {
return int64(math.Round(s)) return int64(math.Round(s))
} }
func computeTimeDiffFloor(diff int64, lang string) (int64, string) { func computeTimeDiffFloor(diff int64, lang translation.Locale) (int64, string) {
var diffStr string diffStr := ""
switch { switch {
case diff <= 0: case diff <= 0:
diff = 0 diff = 0
diffStr = i18n.Tr(lang, "tool.now") diffStr = lang.Tr("tool.now")
case diff < 2: case diff < 2:
diff = 0 diff = 0
diffStr = i18n.Tr(lang, "tool.1s") diffStr = lang.Tr("tool.1s")
case diff < 1*Minute: case diff < 1*Minute:
diffStr = i18n.Tr(lang, "tool.seconds", diff) diffStr = lang.Tr("tool.seconds", diff)
diff = 0 diff = 0
case diff < 2*Minute: case diff < 2*Minute:
diff -= 1 * Minute diff -= 1 * Minute
diffStr = i18n.Tr(lang, "tool.1m") diffStr = lang.Tr("tool.1m")
case diff < 1*Hour: case diff < 1*Hour:
diffStr = i18n.Tr(lang, "tool.minutes", diff/Minute) diffStr = lang.Tr("tool.minutes", diff/Minute)
diff -= diff / Minute * Minute diff -= diff / Minute * Minute
case diff < 2*Hour: case diff < 2*Hour:
diff -= 1 * Hour diff -= 1 * Hour
diffStr = i18n.Tr(lang, "tool.1h") diffStr = lang.Tr("tool.1h")
case diff < 1*Day: case diff < 1*Day:
diffStr = i18n.Tr(lang, "tool.hours", diff/Hour) diffStr = lang.Tr("tool.hours", diff/Hour)
diff -= diff / Hour * Hour diff -= diff / Hour * Hour
case diff < 2*Day: case diff < 2*Day:
diff -= 1 * Day diff -= 1 * Day
diffStr = i18n.Tr(lang, "tool.1d") diffStr = lang.Tr("tool.1d")
case diff < 1*Week: case diff < 1*Week:
diffStr = i18n.Tr(lang, "tool.days", diff/Day) diffStr = lang.Tr("tool.days", diff/Day)
diff -= diff / Day * Day diff -= diff / Day * Day
case diff < 2*Week: case diff < 2*Week:
diff -= 1 * Week diff -= 1 * Week
diffStr = i18n.Tr(lang, "tool.1w") diffStr = lang.Tr("tool.1w")
case diff < 1*Month: case diff < 1*Month:
diffStr = i18n.Tr(lang, "tool.weeks", diff/Week) diffStr = lang.Tr("tool.weeks", diff/Week)
diff -= diff / Week * Week diff -= diff / Week * Week
case diff < 2*Month: case diff < 2*Month:
diff -= 1 * Month diff -= 1 * Month
diffStr = i18n.Tr(lang, "tool.1mon") diffStr = lang.Tr("tool.1mon")
case diff < 1*Year: case diff < 1*Year:
diffStr = i18n.Tr(lang, "tool.months", diff/Month) diffStr = lang.Tr("tool.months", diff/Month)
diff -= diff / Month * Month diff -= diff / Month * Month
case diff < 2*Year: case diff < 2*Year:
diff -= 1 * Year diff -= 1 * Year
diffStr = i18n.Tr(lang, "tool.1y") diffStr = lang.Tr("tool.1y")
default: default:
diffStr = i18n.Tr(lang, "tool.years", diff/Year) diffStr = lang.Tr("tool.years", diff/Year)
diff -= (diff / Year) * Year diff -= (diff / Year) * Year
} }
return diff, diffStr return diff, diffStr
} }
func computeTimeDiff(diff int64, lang string) (int64, string) { func computeTimeDiff(diff int64, lang translation.Locale) (int64, string) {
var diffStr string diffStr := ""
switch { switch {
case diff <= 0: case diff <= 0:
diff = 0 diff = 0
diffStr = i18n.Tr(lang, "tool.now") diffStr = lang.Tr("tool.now")
case diff < 2: case diff < 2:
diff = 0 diff = 0
diffStr = i18n.Tr(lang, "tool.1s") diffStr = lang.Tr("tool.1s")
case diff < 1*Minute: case diff < 1*Minute:
diffStr = i18n.Tr(lang, "tool.seconds", diff) diffStr = lang.Tr("tool.seconds", diff)
diff = 0 diff = 0
case diff < Minute+Minute/2: case diff < Minute+Minute/2:
diff -= 1 * Minute diff -= 1 * Minute
diffStr = i18n.Tr(lang, "tool.1m") diffStr = lang.Tr("tool.1m")
case diff < 1*Hour: case diff < 1*Hour:
minutes := round(float64(diff) / Minute) minutes := round(float64(diff) / Minute)
if minutes > 1 { if minutes > 1 {
diffStr = i18n.Tr(lang, "tool.minutes", minutes) diffStr = lang.Tr("tool.minutes", minutes)
} else { } else {
diffStr = i18n.Tr(lang, "tool.1m") diffStr = lang.Tr("tool.1m")
} }
diff -= diff / Minute * Minute diff -= diff / Minute * Minute
case diff < Hour+Hour/2: case diff < Hour+Hour/2:
diff -= 1 * Hour diff -= 1 * Hour
diffStr = i18n.Tr(lang, "tool.1h") diffStr = lang.Tr("tool.1h")
case diff < 1*Day: case diff < 1*Day:
hours := round(float64(diff) / Hour) hours := round(float64(diff) / Hour)
if hours > 1 { if hours > 1 {
diffStr = i18n.Tr(lang, "tool.hours", hours) diffStr = lang.Tr("tool.hours", hours)
} else { } else {
diffStr = i18n.Tr(lang, "tool.1h") diffStr = lang.Tr("tool.1h")
} }
diff -= diff / Hour * Hour diff -= diff / Hour * Hour
case diff < Day+Day/2: case diff < Day+Day/2:
diff -= 1 * Day diff -= 1 * Day
diffStr = i18n.Tr(lang, "tool.1d") diffStr = lang.Tr("tool.1d")
case diff < 1*Week: case diff < 1*Week:
days := round(float64(diff) / Day) days := round(float64(diff) / Day)
if days > 1 { if days > 1 {
diffStr = i18n.Tr(lang, "tool.days", days) diffStr = lang.Tr("tool.days", days)
} else { } else {
diffStr = i18n.Tr(lang, "tool.1d") diffStr = lang.Tr("tool.1d")
} }
diff -= diff / Day * Day diff -= diff / Day * Day
case diff < Week+Week/2: case diff < Week+Week/2:
diff -= 1 * Week diff -= 1 * Week
diffStr = i18n.Tr(lang, "tool.1w") diffStr = lang.Tr("tool.1w")
case diff < 1*Month: case diff < 1*Month:
weeks := round(float64(diff) / Week) weeks := round(float64(diff) / Week)
if weeks > 1 { if weeks > 1 {
diffStr = i18n.Tr(lang, "tool.weeks", weeks) diffStr = lang.Tr("tool.weeks", weeks)
} else { } else {
diffStr = i18n.Tr(lang, "tool.1w") diffStr = lang.Tr("tool.1w")
} }
diff -= diff / Week * Week diff -= diff / Week * Week
case diff < 1*Month+Month/2: case diff < 1*Month+Month/2:
diff -= 1 * Month diff -= 1 * Month
diffStr = i18n.Tr(lang, "tool.1mon") diffStr = lang.Tr("tool.1mon")
case diff < 1*Year: case diff < 1*Year:
months := round(float64(diff) / Month) months := round(float64(diff) / Month)
if months > 1 { if months > 1 {
diffStr = i18n.Tr(lang, "tool.months", months) diffStr = lang.Tr("tool.months", months)
} else { } else {
diffStr = i18n.Tr(lang, "tool.1mon") diffStr = lang.Tr("tool.1mon")
} }
diff -= diff / Month * Month diff -= diff / Month * Month
case diff < Year+Year/2: case diff < Year+Year/2:
diff -= 1 * Year diff -= 1 * Year
diffStr = i18n.Tr(lang, "tool.1y") diffStr = lang.Tr("tool.1y")
default: default:
years := round(float64(diff) / Year) years := round(float64(diff) / Year)
if years > 1 { if years > 1 {
diffStr = i18n.Tr(lang, "tool.years", years) diffStr = lang.Tr("tool.years", years)
} else { } else {
diffStr = i18n.Tr(lang, "tool.1y") diffStr = lang.Tr("tool.1y")
} }
diff -= (diff / Year) * Year diff -= (diff / Year) * Year
} }
@ -177,24 +177,24 @@ func computeTimeDiff(diff int64, lang string) (int64, string) {
// MinutesToFriendly returns a user friendly string with number of minutes // MinutesToFriendly returns a user friendly string with number of minutes
// converted to hours and minutes. // converted to hours and minutes.
func MinutesToFriendly(minutes int, lang string) string { func MinutesToFriendly(minutes int, lang translation.Locale) string {
duration := time.Duration(minutes) * time.Minute duration := time.Duration(minutes) * time.Minute
return TimeSincePro(time.Now().Add(-duration), lang) return TimeSincePro(time.Now().Add(-duration), lang)
} }
// TimeSincePro calculates the time interval and generate full user-friendly string. // TimeSincePro calculates the time interval and generate full user-friendly string.
func TimeSincePro(then time.Time, lang string) string { func TimeSincePro(then time.Time, lang translation.Locale) string {
return timeSincePro(then, time.Now(), lang) return timeSincePro(then, time.Now(), lang)
} }
func timeSincePro(then, now time.Time, lang string) string { func timeSincePro(then, now time.Time, lang translation.Locale) string {
diff := now.Unix() - then.Unix() diff := now.Unix() - then.Unix()
if then.After(now) { if then.After(now) {
return i18n.Tr(lang, "tool.future") return lang.Tr("tool.future")
} }
if diff == 0 { if diff == 0 {
return i18n.Tr(lang, "tool.now") return lang.Tr("tool.now")
} }
var timeStr, diffStr string var timeStr, diffStr string
@ -209,11 +209,11 @@ func timeSincePro(then, now time.Time, lang string) string {
return strings.TrimPrefix(timeStr, ", ") return strings.TrimPrefix(timeStr, ", ")
} }
func timeSince(then, now time.Time, lang string) string { func timeSince(then, now time.Time, lang translation.Locale) string {
return timeSinceUnix(then.Unix(), now.Unix(), lang) return timeSinceUnix(then.Unix(), now.Unix(), lang)
} }
func timeSinceUnix(then, now int64, lang string) string { func timeSinceUnix(then, now int64, lang translation.Locale) string {
lbl := "tool.ago" lbl := "tool.ago"
diff := now - then diff := now - then
if then > now { if then > now {
@ -221,36 +221,31 @@ func timeSinceUnix(then, now int64, lang string) string {
diff = then - now diff = then - now
} }
if diff <= 0 { if diff <= 0 {
return i18n.Tr(lang, "tool.now") return lang.Tr("tool.now")
} }
_, diffStr := computeTimeDiff(diff, lang) _, diffStr := computeTimeDiff(diff, lang)
return i18n.Tr(lang, lbl, diffStr) return lang.Tr(lbl, diffStr)
}
// RawTimeSince retrieves i18n key of time since t
func RawTimeSince(t time.Time, lang string) string {
return timeSince(t, time.Now(), lang)
} }
// TimeSince calculates the time interval and generate user-friendly string. // TimeSince calculates the time interval and generate user-friendly string.
func TimeSince(then time.Time, lang string) template.HTML { func TimeSince(then time.Time, lang translation.Locale) template.HTML {
return htmlTimeSince(then, time.Now(), lang) return htmlTimeSince(then, time.Now(), lang)
} }
func htmlTimeSince(then, now time.Time, lang string) template.HTML { func htmlTimeSince(then, now time.Time, lang translation.Locale) template.HTML {
return template.HTML(fmt.Sprintf(`<span class="time-since" title="%s">%s</span>`, return template.HTML(fmt.Sprintf(`<span class="time-since" title="%s">%s</span>`,
then.In(setting.DefaultUILocation).Format(GetTimeFormat(lang)), then.In(setting.DefaultUILocation).Format(GetTimeFormat(lang.Language())),
timeSince(then, now, lang))) timeSince(then, now, lang)))
} }
// TimeSinceUnix calculates the time interval and generate user-friendly string. // TimeSinceUnix calculates the time interval and generate user-friendly string.
func TimeSinceUnix(then TimeStamp, lang string) template.HTML { func TimeSinceUnix(then TimeStamp, lang translation.Locale) template.HTML {
return htmlTimeSinceUnix(then, TimeStamp(time.Now().Unix()), lang) return htmlTimeSinceUnix(then, TimeStamp(time.Now().Unix()), lang)
} }
func htmlTimeSinceUnix(then, now TimeStamp, lang string) template.HTML { func htmlTimeSinceUnix(then, now TimeStamp, lang translation.Locale) template.HTML {
return template.HTML(fmt.Sprintf(`<span class="time-since" title="%s">%s</span>`, return template.HTML(fmt.Sprintf(`<span class="time-since" title="%s">%s</span>`,
then.FormatInLocation(GetTimeFormat(lang), setting.DefaultUILocation), then.FormatInLocation(GetTimeFormat(lang.Language()), setting.DefaultUILocation),
timeSinceUnix(int64(then), int64(now), lang))) timeSinceUnix(int64(then), int64(now), lang)))
} }

View file

@ -12,7 +12,6 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/translation/i18n"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -42,16 +41,16 @@ func TestMain(m *testing.M) {
} }
func TestTimeSince(t *testing.T) { func TestTimeSince(t *testing.T) {
assert.Equal(t, "now", timeSince(BaseDate, BaseDate, "en")) assert.Equal(t, "now", timeSince(BaseDate, BaseDate, translation.NewLocale("en-US")))
// test that each diff in `diffs` yields the expected string // test that each diff in `diffs` yields the expected string
test := func(expected string, diffs ...time.Duration) { test := func(expected string, diffs ...time.Duration) {
t.Run(expected, func(t *testing.T) { t.Run(expected, func(t *testing.T) {
for _, diff := range diffs { for _, diff := range diffs {
actual := timeSince(BaseDate, BaseDate.Add(diff), "en") actual := timeSince(BaseDate, BaseDate.Add(diff), translation.NewLocale("en-US"))
assert.Equal(t, i18n.Tr("en", "tool.ago", expected), actual) assert.Equal(t, translation.NewLocale("en-US").Tr("tool.ago", expected), actual)
actual = timeSince(BaseDate.Add(diff), BaseDate, "en") actual = timeSince(BaseDate.Add(diff), BaseDate, translation.NewLocale("en-US"))
assert.Equal(t, i18n.Tr("en", "tool.from_now", expected), actual) assert.Equal(t, translation.NewLocale("en-US").Tr("tool.from_now", expected), actual)
} }
}) })
} }
@ -82,13 +81,13 @@ func TestTimeSince(t *testing.T) {
} }
func TestTimeSincePro(t *testing.T) { func TestTimeSincePro(t *testing.T) {
assert.Equal(t, "now", timeSincePro(BaseDate, BaseDate, "en")) assert.Equal(t, "now", timeSincePro(BaseDate, BaseDate, translation.NewLocale("en-US")))
// test that a difference of `diff` yields the expected string // test that a difference of `diff` yields the expected string
test := func(expected string, diff time.Duration) { test := func(expected string, diff time.Duration) {
actual := timeSincePro(BaseDate, BaseDate.Add(diff), "en") actual := timeSincePro(BaseDate, BaseDate.Add(diff), translation.NewLocale("en-US"))
assert.Equal(t, expected, actual) assert.Equal(t, expected, actual)
assert.Equal(t, "future", timeSincePro(BaseDate.Add(diff), BaseDate, "en")) assert.Equal(t, "future", timeSincePro(BaseDate.Add(diff), BaseDate, translation.NewLocale("en-US")))
} }
test("1 second", time.Second) test("1 second", time.Second)
test("2 seconds", 2*time.Second) test("2 seconds", 2*time.Second)
@ -119,7 +118,7 @@ func TestHtmlTimeSince(t *testing.T) {
setting.DefaultUILocation = time.UTC setting.DefaultUILocation = time.UTC
// test that `diff` yields a result containing `expected` // test that `diff` yields a result containing `expected`
test := func(expected string, diff time.Duration) { test := func(expected string, diff time.Duration) {
actual := htmlTimeSince(BaseDate, BaseDate.Add(diff), "en") actual := htmlTimeSince(BaseDate, BaseDate.Add(diff), translation.NewLocale("en-US"))
assert.Contains(t, actual, `title="Sat Jan 1 00:00:00 UTC 2000"`) assert.Contains(t, actual, `title="Sat Jan 1 00:00:00 UTC 2000"`)
assert.Contains(t, actual, expected) assert.Contains(t, actual, expected)
} }
@ -138,7 +137,7 @@ func TestComputeTimeDiff(t *testing.T) {
test := func(base int64, str string, offsets ...int64) { test := func(base int64, str string, offsets ...int64) {
for _, offset := range offsets { for _, offset := range offsets {
t.Run(fmt.Sprintf("%s:%d", str, offset), func(t *testing.T) { t.Run(fmt.Sprintf("%s:%d", str, offset), func(t *testing.T) {
diff, diffStr := computeTimeDiff(base+offset, "en") diff, diffStr := computeTimeDiff(base+offset, translation.NewLocale("en-US"))
assert.Equal(t, offset, diff) assert.Equal(t, offset, diff)
assert.Equal(t, str, diffStr) assert.Equal(t, str, diffStr)
}) })
@ -171,7 +170,7 @@ func TestComputeTimeDiff(t *testing.T) {
func TestMinutesToFriendly(t *testing.T) { func TestMinutesToFriendly(t *testing.T) {
// test that a number of minutes yields the expected string // test that a number of minutes yields the expected string
test := func(expected string, minutes int) { test := func(expected string, minutes int) {
actual := MinutesToFriendly(minutes, "en") actual := MinutesToFriendly(minutes, translation.NewLocale("en-US"))
assert.Equal(t, expected, actual) assert.Equal(t, expected, actual)
} }
test("1 minute", 1) test("1 minute", 1)

View file

@ -7,10 +7,13 @@ package i18n
import ( import (
"errors" "errors"
"fmt" "fmt"
"os"
"reflect" "reflect"
"strings" "sync"
"time"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
) )
@ -18,123 +21,278 @@ import (
var ( var (
ErrLocaleAlreadyExist = errors.New("lang already exists") ErrLocaleAlreadyExist = errors.New("lang already exists")
DefaultLocales = NewLocaleStore() DefaultLocales = NewLocaleStore(true)
) )
type locale struct { type locale struct {
// This mutex will be set if we have live-reload enabled (e.g. dev mode)
reloadMu *sync.RWMutex
store *LocaleStore store *LocaleStore
langName string langName string
langDesc string
messages *ini.File idxToMsgMap map[int]string // the map idx is generated by store's trKeyToIdxMap
sourceFileName string
sourceFileInfo os.FileInfo
lastReloadCheckTime time.Time
} }
type LocaleStore struct { type LocaleStore struct {
// at the moment, all these fields are readonly after initialization // This mutex will be set if we have live-reload enabled (e.g. dev mode)
langNames []string reloadMu *sync.RWMutex
langDescs []string
localeMap map[string]*locale langNames []string
langDescs []string
localeMap map[string]*locale
// this needs to be locked when live-reloading
trKeyToIdxMap map[string]int
defaultLang string defaultLang string
} }
func NewLocaleStore() *LocaleStore { func NewLocaleStore(isProd bool) *LocaleStore {
return &LocaleStore{localeMap: make(map[string]*locale)} store := &LocaleStore{localeMap: make(map[string]*locale), trKeyToIdxMap: make(map[string]int)}
if !isProd {
store.reloadMu = &sync.RWMutex{}
}
return store
} }
// AddLocaleByIni adds locale by ini into the store // AddLocaleByIni adds locale by ini into the store
func (ls *LocaleStore) AddLocaleByIni(langName, langDesc string, localeFile interface{}, otherLocaleFiles ...interface{}) error { // if source is a string, then the file is loaded. In dev mode, this file will be checked for live-reloading
if _, ok := ls.localeMap[langName]; ok { // if source is a []byte, then the content is used
// Note: this is not concurrent safe
func (store *LocaleStore) AddLocaleByIni(langName, langDesc string, source interface{}) error {
if _, ok := store.localeMap[langName]; ok {
return ErrLocaleAlreadyExist return ErrLocaleAlreadyExist
} }
l := &locale{store: store, langName: langName}
if store.reloadMu != nil {
l.reloadMu = &sync.RWMutex{}
l.reloadMu.Lock() // Arguably this is not necessary as AddLocaleByIni isn't concurrent safe - but for consistency we do this
defer l.reloadMu.Unlock()
}
if fileName, ok := source.(string); ok {
l.sourceFileName = fileName
l.sourceFileInfo, _ = os.Stat(fileName) // live-reload only works for regular files. the error can be ignored
}
var err error
l.idxToMsgMap, err = store.readIniToIdxToMsgMap(source)
if err != nil {
return err
}
store.langNames = append(store.langNames, langName)
store.langDescs = append(store.langDescs, langDesc)
store.localeMap[l.langName] = l
return nil
}
// readIniToIdxToMsgMap will read a provided ini and creates an idxToMsgMap
func (store *LocaleStore) readIniToIdxToMsgMap(source interface{}) (map[int]string, error) {
iniFile, err := ini.LoadSources(ini.LoadOptions{ iniFile, err := ini.LoadSources(ini.LoadOptions{
IgnoreInlineComment: true, IgnoreInlineComment: true,
UnescapeValueCommentSymbols: true, UnescapeValueCommentSymbols: true,
}, localeFile, otherLocaleFiles...) }, source)
if err == nil { if err != nil {
iniFile.BlockMode = false return nil, fmt.Errorf("unable to load ini: %w", err)
lc := &locale{store: ls, langName: langName, langDesc: langDesc, messages: iniFile}
ls.langNames = append(ls.langNames, lc.langName)
ls.langDescs = append(ls.langDescs, lc.langDesc)
ls.localeMap[lc.langName] = lc
} }
return err iniFile.BlockMode = false
idxToMsgMap := make(map[int]string)
if store.reloadMu != nil {
store.reloadMu.Lock()
defer store.reloadMu.Unlock()
}
for _, section := range iniFile.Sections() {
for _, key := range section.Keys() {
var trKey string
if section.Name() == "" || section.Name() == "DEFAULT" {
trKey = key.Name()
} else {
trKey = section.Name() + "." + key.Name()
}
// Instead of storing the key strings in multiple different maps we compute a idx which will act as numeric code for key
// This reduces the size of the locale idxToMsgMaps
idx, ok := store.trKeyToIdxMap[trKey]
if !ok {
idx = len(store.trKeyToIdxMap)
store.trKeyToIdxMap[trKey] = idx
}
idxToMsgMap[idx] = key.Value()
}
}
iniFile = nil
return idxToMsgMap, nil
} }
func (ls *LocaleStore) HasLang(langName string) bool { func (store *LocaleStore) idxForTrKey(trKey string) (int, bool) {
_, ok := ls.localeMap[langName] if store.reloadMu != nil {
store.reloadMu.RLock()
defer store.reloadMu.RUnlock()
}
idx, ok := store.trKeyToIdxMap[trKey]
return idx, ok
}
// HasLang reports if a language is available in the store
func (store *LocaleStore) HasLang(langName string) bool {
_, ok := store.localeMap[langName]
return ok return ok
} }
func (ls *LocaleStore) ListLangNameDesc() (names, desc []string) { // ListLangNameDesc reports if a language available in the store
return ls.langNames, ls.langDescs func (store *LocaleStore) ListLangNameDesc() (names, desc []string) {
return store.langNames, store.langDescs
} }
// SetDefaultLang sets default language as a fallback // SetDefaultLang sets default language as a fallback
func (ls *LocaleStore) SetDefaultLang(lang string) { func (store *LocaleStore) SetDefaultLang(lang string) {
ls.defaultLang = lang store.defaultLang = lang
} }
// Tr translates content to target language. fall back to default language. // Tr translates content to target language. fall back to default language.
func (ls *LocaleStore) Tr(lang, trKey string, trArgs ...interface{}) string { func (store *LocaleStore) Tr(lang, trKey string, trArgs ...interface{}) string {
l, ok := ls.localeMap[lang] l, ok := store.localeMap[lang]
if !ok { if !ok {
l, ok = ls.localeMap[ls.defaultLang] l, ok = store.localeMap[store.defaultLang]
} }
if ok { if ok {
return l.Tr(trKey, trArgs...) return l.Tr(trKey, trArgs...)
} }
return trKey return trKey
} }
// Tr translates content to locale language. fall back to default language. // reloadIfNeeded will check if the locale needs to be reloaded
func (l *locale) Tr(trKey string, trArgs ...interface{}) string { // this function will assume that the l.reloadMu has been RLocked if it already exists
var section string func (l *locale) reloadIfNeeded() {
if l.reloadMu == nil {
idx := strings.IndexByte(trKey, '.') return
if idx > 0 {
section = trKey[:idx]
trKey = trKey[idx+1:]
} }
trMsg := trKey now := time.Now()
if trIni, err := l.messages.Section(section).GetKey(trKey); err == nil { if now.Sub(l.lastReloadCheckTime) < time.Second || l.sourceFileInfo == nil || l.sourceFileName == "" {
trMsg = trIni.Value() return
} else if l.store.defaultLang != "" && l.langName != l.store.defaultLang {
// try to fall back to default
if defaultLocale, ok := l.store.localeMap[l.store.defaultLang]; ok {
if trIni, err = defaultLocale.messages.Section(section).GetKey(trKey); err == nil {
trMsg = trIni.Value()
}
}
} }
if len(trArgs) > 0 { l.reloadMu.RUnlock()
fmtArgs := make([]interface{}, 0, len(trArgs)) l.reloadMu.Lock() // (NOTE: a pre-emption can occur between these two locks so we need to recheck)
for _, arg := range trArgs { defer l.reloadMu.RLock()
val := reflect.ValueOf(arg) defer l.reloadMu.Unlock()
if val.Kind() == reflect.Slice {
// before, it can accept Tr(lang, key, a, [b, c], d, [e, f]) as Sprintf(msg, a, b, c, d, e, f), it's an unstable behavior if now.Sub(l.lastReloadCheckTime) < time.Second || l.sourceFileInfo == nil || l.sourceFileName == "" {
// now, we restrict the strange behavior and only support: return
// 1. Tr(lang, key, [slice-items]) as Sprintf(msg, items...)
// 2. Tr(lang, key, args...) as Sprintf(msg, args...)
if len(trArgs) == 1 {
for i := 0; i < val.Len(); i++ {
fmtArgs = append(fmtArgs, val.Index(i).Interface())
}
} else {
log.Error("the args for i18n shouldn't contain uncertain slices, key=%q, args=%v", trKey, trArgs)
break
}
} else {
fmtArgs = append(fmtArgs, arg)
}
}
return fmt.Sprintf(trMsg, fmtArgs...)
} }
return trMsg
l.lastReloadCheckTime = now
sourceFileInfo, err := os.Stat(l.sourceFileName)
if err != nil || sourceFileInfo.ModTime().Equal(l.sourceFileInfo.ModTime()) {
return
}
idxToMsgMap, err := l.store.readIniToIdxToMsgMap(l.sourceFileName)
if err == nil {
l.idxToMsgMap = idxToMsgMap
} else {
log.Error("Unable to live-reload the locale file %q, err: %v", l.sourceFileName, err)
}
// We will set the sourceFileInfo to this file to prevent repeated attempts to re-load this broken file
l.sourceFileInfo = sourceFileInfo
} }
func ResetDefaultLocales() { // Tr translates content to locale language. fall back to default language.
DefaultLocales = NewLocaleStore() func (l *locale) Tr(trKey string, trArgs ...interface{}) string {
if l.reloadMu != nil {
l.reloadMu.RLock()
defer l.reloadMu.RUnlock()
l.reloadIfNeeded()
}
msg, _ := l.tryTr(trKey, trArgs...)
return msg
}
func (l *locale) tryTr(trKey string, trArgs ...interface{}) (msg string, found bool) {
trMsg := trKey
// convert the provided trKey to a common idx from the store
idx, ok := l.store.idxForTrKey(trKey)
if ok {
if msg, found = l.idxToMsgMap[idx]; found {
trMsg = msg // use the translation that we have found
} else if l.langName != l.store.defaultLang {
// No translation available in our current language... fallback to the default language
// Attempt to get the default language from the locale store
if def, ok := l.store.localeMap[l.store.defaultLang]; ok {
if def.reloadMu != nil {
def.reloadMu.RLock()
def.reloadIfNeeded()
}
if msg, found = def.idxToMsgMap[idx]; found {
trMsg = msg // use the translation that we have found
}
if def.reloadMu != nil {
def.reloadMu.RUnlock()
}
}
}
}
if !found && !setting.IsProd {
log.Error("missing i18n translation key: %q", trKey)
}
if len(trArgs) == 0 {
return trMsg, found
}
fmtArgs := make([]interface{}, 0, len(trArgs))
for _, arg := range trArgs {
val := reflect.ValueOf(arg)
if val.Kind() == reflect.Slice {
// Previously, we would accept Tr(lang, key, a, [b, c], d, [e, f]) as Sprintf(msg, a, b, c, d, e, f)
// but this is an unstable behavior.
//
// So we restrict the accepted arguments to either:
//
// 1. Tr(lang, key, [slice-items]) as Sprintf(msg, items...)
// 2. Tr(lang, key, args...) as Sprintf(msg, args...)
if len(trArgs) == 1 {
for i := 0; i < val.Len(); i++ {
fmtArgs = append(fmtArgs, val.Index(i).Interface())
}
} else {
log.Error("the args for i18n shouldn't contain uncertain slices, key=%q, args=%v", trKey, trArgs)
break
}
} else {
fmtArgs = append(fmtArgs, arg)
}
}
return fmt.Sprintf(trMsg, fmtArgs...), found
}
// ResetDefaultLocales resets the current default locales
// NOTE: this is not synchronized
func ResetDefaultLocales(isProd bool) {
DefaultLocales = NewLocaleStore(isProd)
} }
// Tr use default locales to translate content to target language. // Tr use default locales to translate content to target language.

View file

@ -27,30 +27,36 @@ fmt = %[2]s %[1]s
sub = Changed Sub String sub = Changed Sub String
`) `)
ls := NewLocaleStore() for _, isProd := range []bool{true, false} {
assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1)) ls := NewLocaleStore(isProd)
assert.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", testData2)) assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1))
ls.SetDefaultLang("lang1") assert.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", testData2))
ls.SetDefaultLang("lang1")
result := ls.Tr("lang1", "fmt", "a", "b") result := ls.Tr("lang1", "fmt", "a", "b")
assert.Equal(t, "a b", result) assert.Equal(t, "a b", result)
result = ls.Tr("lang2", "fmt", "a", "b") result = ls.Tr("lang2", "fmt", "a", "b")
assert.Equal(t, "b a", result) assert.Equal(t, "b a", result)
result = ls.Tr("lang1", "section.sub") result = ls.Tr("lang1", "section.sub")
assert.Equal(t, "Sub String", result) assert.Equal(t, "Sub String", result)
result = ls.Tr("lang2", "section.sub") result = ls.Tr("lang2", "section.sub")
assert.Equal(t, "Changed Sub String", result) assert.Equal(t, "Changed Sub String", result)
result = ls.Tr("", ".dot.name") result = ls.Tr("", ".dot.name")
assert.Equal(t, "Dot Name", result) assert.Equal(t, "Dot Name", result)
result = ls.Tr("lang2", "section.mixed") result = ls.Tr("lang2", "section.mixed")
assert.Equal(t, `test value; <span style="color: red; background: none;">more text</span>`, result) assert.Equal(t, `test value; <span style="color: red; background: none;">more text</span>`, result)
langs, descs := ls.ListLangNameDesc() langs, descs := ls.ListLangNameDesc()
assert.Equal(t, []string{"lang1", "lang2"}, langs) assert.Equal(t, []string{"lang1", "lang2"}, langs)
assert.Equal(t, []string{"Lang1", "Lang2"}, descs) assert.Equal(t, []string{"Lang1", "Lang2"}, descs)
result, found := ls.localeMap["lang1"].tryTr("no-such")
assert.Equal(t, "no-such", result)
assert.False(t, found)
}
} }

View file

@ -5,6 +5,7 @@
package translation package translation
import ( import (
"path"
"sort" "sort"
"strings" "strings"
@ -12,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/options" "code.gitea.io/gitea/modules/options"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation/i18n"
"code.gitea.io/gitea/modules/util"
"golang.org/x/text/language" "golang.org/x/text/language"
) )
@ -40,31 +42,35 @@ func AllLangs() []*LangType {
return allLangs return allLangs
} }
// TryTr tries to do the translation, if no translation, it returns (format, false)
func TryTr(lang, format string, args ...interface{}) (string, bool) {
s := i18n.Tr(lang, format, args...)
// now the i18n library is not good enough and we can only use this hacky method to detect whether the transaction exists
idx := strings.IndexByte(format, '.')
defaultText := format
if idx > 0 {
defaultText = format[idx+1:]
}
return s, s != defaultText
}
// InitLocales loads the locales // InitLocales loads the locales
func InitLocales() { func InitLocales() {
i18n.ResetDefaultLocales() i18n.ResetDefaultLocales(setting.IsProd)
localeNames, err := options.Dir("locale") localeNames, err := options.Dir("locale")
if err != nil { if err != nil {
log.Fatal("Failed to list locale files: %v", err) log.Fatal("Failed to list locale files: %v", err)
} }
localFiles := make(map[string][]byte, len(localeNames)) localFiles := make(map[string]interface{}, len(localeNames))
for _, name := range localeNames { for _, name := range localeNames {
localFiles[name], err = options.Locale(name) if options.IsDynamic() {
if err != nil { // Try to check if CustomPath has the file, otherwise fallback to StaticRootPath
log.Fatal("Failed to load %s locale file. %v", name, err) value := path.Join(setting.CustomPath, "options/locale", name)
isFile, err := util.IsFile(value)
if err != nil {
log.Fatal("Failed to load %s locale file. %v", name, err)
}
if isFile {
localFiles[name] = value
} else {
localFiles[name] = path.Join(setting.StaticRootPath, "options/locale", name)
}
} else {
localFiles[name], err = options.Locale(name)
if err != nil {
log.Fatal("Failed to load %s locale file. %v", name, err)
}
} }
} }
@ -76,6 +82,7 @@ func InitLocales() {
matcher = language.NewMatcher(supportedTags) matcher = language.NewMatcher(supportedTags)
for i := range setting.Names { for i := range setting.Names {
key := "locale_" + setting.Langs[i] + ".ini" key := "locale_" + setting.Langs[i] + ".ini"
if err = i18n.DefaultLocales.AddLocaleByIni(setting.Langs[i], setting.Names[i], localFiles[key]); err != nil { if err = i18n.DefaultLocales.AddLocaleByIni(setting.Langs[i], setting.Names[i], localFiles[key]); err != nil {
log.Error("Failed to set messages to %s: %v", setting.Langs[i], err) log.Error("Failed to set messages to %s: %v", setting.Langs[i], err)
} }
@ -132,16 +139,7 @@ func (l *locale) Language() string {
// Tr translates content to target language. // Tr translates content to target language.
func (l *locale) Tr(format string, args ...interface{}) string { func (l *locale) Tr(format string, args ...interface{}) string {
if setting.IsProd { return i18n.Tr(l.Lang, format, args...)
return i18n.Tr(l.Lang, format, args...)
}
// in development, we should show an error if a translation key is missing
s, ok := TryTr(l.Lang, format, args...)
if !ok {
log.Error("missing i18n translation key: %q", format)
}
return s
} }
// Language specific rules for translating plural texts // Language specific rules for translating plural texts

View file

@ -46,7 +46,6 @@ webauthn_error_unable_to_process=Server nemohl zpracovat váš požadavek.
webauthn_error_duplicated=Zabezpečovací klíč není pro tento požadavek povolen. Prosím ujistěte se, zda klíč není již registrován. webauthn_error_duplicated=Zabezpečovací klíč není pro tento požadavek povolen. Prosím ujistěte se, zda klíč není již registrován.
webauthn_error_empty=Musíte nastavit název tohoto klíče. webauthn_error_empty=Musíte nastavit název tohoto klíče.
webauthn_error_timeout=Požadavek vypršel dříve, než se podařilo přečíst váš klíč. Znovu načtěte tuto stránku a akci opakujte. webauthn_error_timeout=Požadavek vypršel dříve, než se podařilo přečíst váš klíč. Znovu načtěte tuto stránku a akci opakujte.
webauthn_u2f_deprecated=Klíč: „%s“ autentifikuje pomocí zastaralého procesu U2F. Měli byste znovu zaregistrovat tento klíč a zrušit starou registraci.
webauthn_reload=Znovu načíst webauthn_reload=Znovu načíst
repository=Repozitář repository=Repozitář

View file

@ -47,7 +47,6 @@ webauthn_error_unable_to_process=Der Server konnte deine Anfrage nicht bearbeite
webauthn_error_duplicated=Für diese Anfrage ist der Sicherheitsschlüssel nicht erlaubt. Bitte stell sicher, dass er nicht bereits registriert ist. webauthn_error_duplicated=Für diese Anfrage ist der Sicherheitsschlüssel nicht erlaubt. Bitte stell sicher, dass er nicht bereits registriert ist.
webauthn_error_empty=Du musst einen Namen für diesen Schlüssel festlegen. webauthn_error_empty=Du musst einen Namen für diesen Schlüssel festlegen.
webauthn_error_timeout=Das Zeitlimit wurde erreicht, bevor dein Schlüssel gelesen werden konnte. Bitte lade die Seite erneut. webauthn_error_timeout=Das Zeitlimit wurde erreicht, bevor dein Schlüssel gelesen werden konnte. Bitte lade die Seite erneut.
webauthn_u2f_deprecated=Der Schlüssel: '%s' authentifiziert sich über den veralteten U2F-Prozess. Bitte registriere den Schlüssel neu und lösche die alte Registrierung.
webauthn_reload=Neu laden webauthn_reload=Neu laden
repository=Repository repository=Repository
@ -1516,7 +1515,7 @@ pulls.tab_conversation=Diskussion
pulls.tab_commits=Commits pulls.tab_commits=Commits
pulls.tab_files=Geänderte Dateien pulls.tab_files=Geänderte Dateien
pulls.reopen_to_merge=Bitte diesen Pull-Request wieder öffnen, um zu mergen. pulls.reopen_to_merge=Bitte diesen Pull-Request wieder öffnen, um zu mergen.
pulls.cant_reopen_deleted_branch=Dieser Pull-Request kann nicht wieder geöffnet werden, da die Branche bereits gelöscht wurde. pulls.cant_reopen_deleted_branch=Dieser Pull-Request kann nicht wieder geöffnet werden, da die Branch bereits gelöscht wurde.
pulls.merged=Zusammengeführt pulls.merged=Zusammengeführt
pulls.merged_as=Der Pull Request wurde als <a rel="nofollow" class="ui sha" href="%[1]s"><code>%[2]s</code></a> gemergt. pulls.merged_as=Der Pull Request wurde als <a rel="nofollow" class="ui sha" href="%[1]s"><code>%[2]s</code></a> gemergt.
pulls.manually_merged=Manuell gemergt pulls.manually_merged=Manuell gemergt

View file

@ -47,7 +47,6 @@ webauthn_error_unable_to_process=Ο διακομιστής δεν μπόρεσε
webauthn_error_duplicated=Το κλειδί ασφαλείας δεν επιτρέπεται για αυτό το αίτημα. Βεβαιωθείτε ότι το κλειδί δεν έχει ήδη καταχωρηθεί. webauthn_error_duplicated=Το κλειδί ασφαλείας δεν επιτρέπεται για αυτό το αίτημα. Βεβαιωθείτε ότι το κλειδί δεν έχει ήδη καταχωρηθεί.
webauthn_error_empty=Πρέπει να ορίσετε ένα όνομα για αυτό το κλειδί. webauthn_error_empty=Πρέπει να ορίσετε ένα όνομα για αυτό το κλειδί.
webauthn_error_timeout=Το χρονικό όριο έφτασε πριν το κλειδί να διαβαστεί. Παρακαλώ ανανεώστε τη σελίδα και προσπαθήστε ξανά. webauthn_error_timeout=Το χρονικό όριο έφτασε πριν το κλειδί να διαβαστεί. Παρακαλώ ανανεώστε τη σελίδα και προσπαθήστε ξανά.
webauthn_u2f_deprecated=Το κλειδί: '%s' πιστοποιεί χρησιμοποιώντας το παρωχημένο πρωτόκολλο U2F. Θα πρέπει να καταχωρήσετε ξανά αυτό το κλειδί και να καταργήσετε την παλιά εγγραφή.
webauthn_reload=Ανανέωση webauthn_reload=Ανανέωση
repository=Αποθετήριο repository=Αποθετήριο
@ -1276,7 +1275,7 @@ issues.filter_sort=Ταξινόμηση
issues.filter_sort.latest=Νεότερα issues.filter_sort.latest=Νεότερα
issues.filter_sort.oldest=Παλαιότερα issues.filter_sort.oldest=Παλαιότερα
issues.filter_sort.recentupdate=Ενημερώθηκαν πρόσφατα issues.filter_sort.recentupdate=Ενημερώθηκαν πρόσφατα
issues.filter_sort.leastupdate=Λιγότερο πρόσφατα ανανεωμένο issues.filter_sort.leastupdate=Ενημερώθηκαν παλαιότερα
issues.filter_sort.mostcomment=Περισσότερο σχολιασμένα issues.filter_sort.mostcomment=Περισσότερο σχολιασμένα
issues.filter_sort.leastcomment=Λιγότερο σχολιασμένα issues.filter_sort.leastcomment=Λιγότερο σχολιασμένα
issues.filter_sort.nearduedate=Πλησιέστερη παράδοση issues.filter_sort.nearduedate=Πλησιέστερη παράδοση

View file

@ -47,7 +47,6 @@ webauthn_error_unable_to_process = The server could not process your request.
webauthn_error_duplicated = The security key is not permitted for this request. Please make sure that the key is not already registered. webauthn_error_duplicated = The security key is not permitted for this request. Please make sure that the key is not already registered.
webauthn_error_empty = You must set a name for this key. webauthn_error_empty = You must set a name for this key.
webauthn_error_timeout = Timeout reached before your key could be read. Please reload this page and retry. webauthn_error_timeout = Timeout reached before your key could be read. Please reload this page and retry.
webauthn_u2f_deprecated = The key: '%s' authenticates using the deprecated U2F process. You should re-register this key and remove the old registration.
webauthn_reload = Reload webauthn_reload = Reload
repository = Repository repository = Repository

View file

@ -47,7 +47,6 @@ webauthn_error_unable_to_process=El servidor no pudo procesar su solicitud.
webauthn_error_duplicated=La clave de seguridad no está permitida para esta solicitud. Por favor, asegúrese de que la clave no está ya registrada. webauthn_error_duplicated=La clave de seguridad no está permitida para esta solicitud. Por favor, asegúrese de que la clave no está ya registrada.
webauthn_error_empty=Debe establecer un nombre para esta clave. webauthn_error_empty=Debe establecer un nombre para esta clave.
webauthn_error_timeout=Tiempo de espera máximo alcanzado antes de que su clave pudiese ser leída. Por favor, cargue la página y vuelva a intentarlo. webauthn_error_timeout=Tiempo de espera máximo alcanzado antes de que su clave pudiese ser leída. Por favor, cargue la página y vuelva a intentarlo.
webauthn_u2f_deprecated=La clave: '%s' se autentifica usando el proceso U2F obsoleto. Debe volver a registrar esta clave y eliminar el registro antiguo.
webauthn_reload=Recargar webauthn_reload=Recargar
repository=Repositorio repository=Repositorio
@ -1610,6 +1609,8 @@ pulls.auto_merge_canceled_schedule=Fusión automaticá estaba cancellada para es
pulls.auto_merge_newly_scheduled_comment=`programó este Pull Request para fusionar automática cuando todas las comprobaciones tengan éxito %[1]s` pulls.auto_merge_newly_scheduled_comment=`programó este Pull Request para fusionar automática cuando todas las comprobaciones tengan éxito %[1]s`
pulls.auto_merge_canceled_schedule_comment=`canceló la fusión automática de este Pull Request %[1]s` pulls.auto_merge_canceled_schedule_comment=`canceló la fusión automática de este Pull Request %[1]s`
pulls.delete.title=¿Borrar este pull request?
pulls.delete.text=¿Realmente quieres eliminar esta pull request? (Esto eliminará permanentemente todo el contenido. Considera cerrarlo si simplemente deseas archivarlo)
milestones.new=Nuevo hito milestones.new=Nuevo hito
milestones.closed=Cerrada %s milestones.closed=Cerrada %s

View file

@ -46,7 +46,6 @@ webauthn_error_unable_to_process=Netþjónninn gat ekki ráðið við beiðni þ
webauthn_error_duplicated=Öryggislykillinn er ekki leyfður fyrir þessa beiðni. Gakktu úr skugga um að lykillinn sé ekki þegar skráður. webauthn_error_duplicated=Öryggislykillinn er ekki leyfður fyrir þessa beiðni. Gakktu úr skugga um að lykillinn sé ekki þegar skráður.
webauthn_error_empty=Þú verður að setja nafn fyrir þennan lykil. webauthn_error_empty=Þú verður að setja nafn fyrir þennan lykil.
webauthn_error_timeout=Tímamörk náð áður en hægt var að lesa lykilinn þinn. Vinsamlegast endurhlaðið þessa síðu og reyndu aftur. webauthn_error_timeout=Tímamörk náð áður en hægt var að lesa lykilinn þinn. Vinsamlegast endurhlaðið þessa síðu og reyndu aftur.
webauthn_u2f_deprecated=Lykillinn: „%s“ auðkennir með því að nota úrelta U2F aðferð. Þú ættir að endurskrá þennan lykil og fjarlægja gömlu skráninguna.
webauthn_reload=Endurhlaða webauthn_reload=Endurhlaða
repository=Hugbúnaðarsafn repository=Hugbúnaðarsafn

View file

@ -47,7 +47,6 @@ webauthn_error_unable_to_process=サーバーがリクエストを処理でき
webauthn_error_duplicated=このリクエストに対しては、許可されていないセキュリティキーです。 キーが未登録であることを確認してください。 webauthn_error_duplicated=このリクエストに対しては、許可されていないセキュリティキーです。 キーが未登録であることを確認してください。
webauthn_error_empty=このキーに名前を設定する必要があります。 webauthn_error_empty=このキーに名前を設定する必要があります。
webauthn_error_timeout=キーを読み取る前にタイムアウトになりました。 このページをリロードしてもう一度やり直してください。 webauthn_error_timeout=キーを読み取る前にタイムアウトになりました。 このページをリロードしてもう一度やり直してください。
webauthn_u2f_deprecated=キー: '%s' は非推奨のU2Fプロセスを使用して認証しています。このキーを再登録して古い登録を削除したほうが良いでしょう。
webauthn_reload=リロード webauthn_reload=リロード
repository=リポジトリ repository=リポジトリ
@ -1610,6 +1609,8 @@ pulls.auto_merge_canceled_schedule=このプルリクエストの自動マージ
pulls.auto_merge_newly_scheduled_comment=`が、すべてのチェックが成功すると自動マージを行うよう、このプルリクエストをスケジュール %[1]s` pulls.auto_merge_newly_scheduled_comment=`が、すべてのチェックが成功すると自動マージを行うよう、このプルリクエストをスケジュール %[1]s`
pulls.auto_merge_canceled_schedule_comment=`が、すべてのチェックが成功したときのプルリクエストの自動マージをキャンセル %[1]s` pulls.auto_merge_canceled_schedule_comment=`が、すべてのチェックが成功したときのプルリクエストの自動マージをキャンセル %[1]s`
pulls.delete.title=このプルリクエストを削除しますか?
pulls.delete.text=本当にこのプルリクエストを削除しますか? (これはすべてのコンテンツを完全に削除します。 保存しておきたい場合は、代わりにクローズすることを検討してください)
milestones.new=新しいマイルストーン milestones.new=新しいマイルストーン
milestones.closed=%s にクローズ milestones.closed=%s にクローズ

View file

@ -47,7 +47,6 @@ webauthn_error_unable_to_process=Serveris nevar apstrādāt Jūsu pieprasījumu.
webauthn_error_duplicated=Drošības atslēga nav atļauta šim pieprasījumam. Pārliecinieties, ka šī atslēga jau nav reģistrēta. webauthn_error_duplicated=Drošības atslēga nav atļauta šim pieprasījumam. Pārliecinieties, ka šī atslēga jau nav reģistrēta.
webauthn_error_empty=Norādiet atslēgas nosaukumu. webauthn_error_empty=Norādiet atslēgas nosaukumu.
webauthn_error_timeout=Iestājusies noildze, mēģinot, nolasīt atslēgu. Pārlādējiet lapu un mēģiniet vēlreiz. webauthn_error_timeout=Iestājusies noildze, mēģinot, nolasīt atslēgu. Pārlādējiet lapu un mēģiniet vēlreiz.
webauthn_u2f_deprecated=Atslēga '%s' izmanto novecojušu U2F procesu. Noņemiet iepriekšējo reģistrāciju un veiciet reģistrācijas procesu no jauna.
webauthn_reload=Pārlādēt webauthn_reload=Pārlādēt
repository=Repozitorijs repository=Repozitorijs

View file

@ -46,7 +46,6 @@ webauthn_error_unable_to_process=Serwer nie mógł obsłużyć Twojego żądania
webauthn_error_duplicated=Klucz bezpieczeństwa nie jest dozwolony dla tego żądania. Upewnij się, że klucz nie jest już zarejestrowany. webauthn_error_duplicated=Klucz bezpieczeństwa nie jest dozwolony dla tego żądania. Upewnij się, że klucz nie jest już zarejestrowany.
webauthn_error_empty=Musisz ustawić nazwę dla tego klucza. webauthn_error_empty=Musisz ustawić nazwę dla tego klucza.
webauthn_error_timeout=Osiągnięto limit czasu zanim Twój klucz może zostać odczytany. Odśwież stronę i spróbuj ponownie. webauthn_error_timeout=Osiągnięto limit czasu zanim Twój klucz może zostać odczytany. Odśwież stronę i spróbuj ponownie.
webauthn_u2f_deprecated=Klucz '%s' uwierzytelnia przy użyciu przestarzałego procesu U2F. Powinieneś ponownie zarejestrować ten klucz i usunąć starą rejestrację.
webauthn_reload=Odśwież webauthn_reload=Odśwież
repository=Repozytorium repository=Repozytorium

View file

@ -2,6 +2,7 @@ home=Inicio
dashboard=Painel dashboard=Painel
explore=Explorar explore=Explorar
help=Ajuda help=Ajuda
logo=Logotipo
sign_in=Acessar sign_in=Acessar
sign_in_with=Acessar com sign_in_with=Acessar com
sign_out=Sair sign_out=Sair
@ -46,7 +47,6 @@ webauthn_error_unable_to_process=O servidor não pôde processar sua solicitaç
webauthn_error_duplicated=A chave de segurança não é permitida para esta solicitação. Por favor, certifique-se que a chave já não está registrada. webauthn_error_duplicated=A chave de segurança não é permitida para esta solicitação. Por favor, certifique-se que a chave já não está registrada.
webauthn_error_empty=Você deve definir um nome para esta chave. webauthn_error_empty=Você deve definir um nome para esta chave.
webauthn_error_timeout=Tempo limite atingido antes de sua chave poder ser lida. Por favor, recarregue esta página e tente novamente. webauthn_error_timeout=Tempo limite atingido antes de sua chave poder ser lida. Por favor, recarregue esta página e tente novamente.
webauthn_u2f_deprecated=A chave: '%s' autentica utilizando o processo U2F descontinuado. Você deve registrar novamente esta chave e remover o registro antigo.
webauthn_reload=Recarregar webauthn_reload=Recarregar
repository=Repositório repository=Repositório
@ -442,6 +442,7 @@ size_error=`deve ser do tamanho %s.`
min_size_error=` deve conter pelo menos %s caracteres.` min_size_error=` deve conter pelo menos %s caracteres.`
max_size_error=` deve conter no máximo %s caracteres.` max_size_error=` deve conter no máximo %s caracteres.`
email_error=` não é um endereço de e-mail válido.` email_error=` não é um endereço de e-mail válido.`
url_error=`'%s' não é uma URL válida.`
include_error=` deve conter '%s'.` include_error=` deve conter '%s'.`
glob_pattern_error=` padrão glob é inválido: %s.` glob_pattern_error=` padrão glob é inválido: %s.`
regex_pattern_error=` o regex é inválido: %s.` regex_pattern_error=` o regex é inválido: %s.`
@ -715,6 +716,9 @@ generate_token_success=Seu novo token foi gerado. Copie-o agora, pois ele não s
generate_token_name_duplicate=<strong>%s</strong> já foi usado como um nome de aplicativo. Por favor, use outro. generate_token_name_duplicate=<strong>%s</strong> já foi usado como um nome de aplicativo. Por favor, use outro.
delete_token=Excluir delete_token=Excluir
access_token_deletion=Excluir token de acesso access_token_deletion=Excluir token de acesso
access_token_deletion_cancel_action=Cancelar
access_token_deletion_confirm_action=Excluir
access_token_deletion_desc=A exclusão de um token revoga o acesso à sua conta para aplicativos que o usam. Continuar?
delete_token_success=O token foi excluído. Os aplicativos que o utilizam já não têm acesso à sua conta. delete_token_success=O token foi excluído. Os aplicativos que o utilizam já não têm acesso à sua conta.
manage_oauth2_applications=Gerenciar aplicativos OAuth2 manage_oauth2_applications=Gerenciar aplicativos OAuth2
@ -1487,6 +1491,7 @@ pulls.new=Novo pull request
pulls.view=Ver Pull Request pulls.view=Ver Pull Request
pulls.compare_changes=Novo pull request pulls.compare_changes=Novo pull request
pulls.allow_edits_from_maintainers=Permitir edições de mantenedores pulls.allow_edits_from_maintainers=Permitir edições de mantenedores
pulls.allow_edits_from_maintainers_err=Falha na atualização
pulls.compare_changes_desc=Selecione o branch de destino (push) e o branch de origem (pull) para o merge. pulls.compare_changes_desc=Selecione o branch de destino (push) e o branch de origem (pull) para o merge.
pulls.has_viewed_file=Visto pulls.has_viewed_file=Visto
pulls.has_changed_since_last_review=Alterado desde a última revisão pulls.has_changed_since_last_review=Alterado desde a última revisão
@ -1590,6 +1595,7 @@ pulls.merge_instruction_hint=`Você também pode ver as <a class="show-instructi
pulls.merge_instruction_step1_desc=No repositório do seu projeto, crie um novo branch e teste as alterações. pulls.merge_instruction_step1_desc=No repositório do seu projeto, crie um novo branch e teste as alterações.
pulls.merge_instruction_step2_desc=Faça merge das alterações e atualize no Gitea. pulls.merge_instruction_step2_desc=Faça merge das alterações e atualize no Gitea.
pulls.auto_merge_button_when_succeed=(Quando a verificação for bem-sucedida)

View file

@ -47,7 +47,6 @@ webauthn_error_unable_to_process=O servidor não conseguiu processar o seu pedid
webauthn_error_duplicated=A chave de segurança não é permitida neste pedido. Certifique-se de que a chave não está já registada. webauthn_error_duplicated=A chave de segurança não é permitida neste pedido. Certifique-se de que a chave não está já registada.
webauthn_error_empty=Você tem que definir um nome para esta chave. webauthn_error_empty=Você tem que definir um nome para esta chave.
webauthn_error_timeout=O tempo limite foi atingido antes que a sua chave pudesse ser lida. Recarregue esta página e tente novamente. webauthn_error_timeout=O tempo limite foi atingido antes que a sua chave pudesse ser lida. Recarregue esta página e tente novamente.
webauthn_u2f_deprecated=A chave: '%s' autentica usando o processo U2F, mas este foi descontinuado. Você deveria registar novamente esta chave e remover o registo antigo.
webauthn_reload=Recarregar webauthn_reload=Recarregar
repository=Repositório repository=Repositório

View file

@ -47,7 +47,6 @@ webauthn_error_unable_to_process=服务器无法处理您的请求。
webauthn_error_duplicated=此安全密钥未被许可用于这个请求。请确保该密钥尚未注册。 webauthn_error_duplicated=此安全密钥未被许可用于这个请求。请确保该密钥尚未注册。
webauthn_error_empty=您必须为此密钥设置一个名称。 webauthn_error_empty=您必须为此密钥设置一个名称。
webauthn_error_timeout=未能在允许的时限内读取密钥。请重新加载此页面并重试。 webauthn_error_timeout=未能在允许的时限内读取密钥。请重新加载此页面并重试。
webauthn_u2f_deprecated=密钥 '%s' 使用的是已经废弃的 U2F 进行身份验证。您应该重新注册此密钥并删除旧的注册。
webauthn_reload=重新加载 webauthn_reload=重新加载
repository=仓库 repository=仓库

View file

@ -47,7 +47,6 @@ webauthn_error_unable_to_process=伺服器無法執行您的請求。
webauthn_error_duplicated=此請求不允許使用這個安全金鑰。請確保該金鑰尚未註冊。 webauthn_error_duplicated=此請求不允許使用這個安全金鑰。請確保該金鑰尚未註冊。
webauthn_error_empty=您必須命名此金鑰。 webauthn_error_empty=您必須命名此金鑰。
webauthn_error_timeout=在成功讀取金鑰之前已逾時,請重新載入此頁面並重試。 webauthn_error_timeout=在成功讀取金鑰之前已逾時,請重新載入此頁面並重試。
webauthn_u2f_deprecated=「%s」金鑰使用已廢棄的 U2F 流程進行驗證。您應該重新註冊此金鑰並將先前註冊的移除。
webauthn_reload=重新載入 webauthn_reload=重新載入
repository=儲存庫 repository=儲存庫

30
package-lock.json generated
View file

@ -22,7 +22,7 @@
"less": "4.1.2", "less": "4.1.2",
"less-loader": "11.0.0", "less-loader": "11.0.0",
"license-checker-webpack-plugin": "0.2.1", "license-checker-webpack-plugin": "0.2.1",
"mermaid": "9.1.1", "mermaid": "9.1.2",
"mini-css-extract-plugin": "2.6.0", "mini-css-extract-plugin": "2.6.0",
"monaco-editor": "0.33.0", "monaco-editor": "0.33.0",
"monaco-editor-webpack-plugin": "7.0.1", "monaco-editor-webpack-plugin": "7.0.1",
@ -3833,9 +3833,9 @@
} }
}, },
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "2.3.6", "version": "2.3.8",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.6.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.8.tgz",
"integrity": "sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==" "integrity": "sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw=="
}, },
"node_modules/domutils": { "node_modules/domutils": {
"version": "2.8.0", "version": "2.8.0",
@ -7741,15 +7741,15 @@
} }
}, },
"node_modules/mermaid": { "node_modules/mermaid": {
"version": "9.1.1", "version": "9.1.2",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.1.1.tgz", "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.1.2.tgz",
"integrity": "sha512-2RVD+WkzZ4VDyO9gQvQAuQ/ux2gLigJtKDTlbwjYqOR/NwsVzTSfGm/kx648/qWJsg6Sv04tE9BWCO8s6a+pFA==", "integrity": "sha512-RVf3hBKqiMfyORHboCaEjOAK1TomLO50hYRPvlTrZCXlCniM5pRpe8UlkHBjjpaLtioZnbdYv/vEVj7iKnwkJQ==",
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^6.0.0", "@braintree/sanitize-url": "^6.0.0",
"d3": "^7.0.0", "d3": "^7.0.0",
"dagre": "^0.8.5", "dagre": "^0.8.5",
"dagre-d3": "^0.6.4", "dagre-d3": "^0.6.4",
"dompurify": "2.3.6", "dompurify": "2.3.8",
"graphlib": "^2.1.8", "graphlib": "^2.1.8",
"khroma": "^2.0.0", "khroma": "^2.0.0",
"moment-mini": "^2.24.0", "moment-mini": "^2.24.0",
@ -13815,9 +13815,9 @@
} }
}, },
"dompurify": { "dompurify": {
"version": "2.3.6", "version": "2.3.8",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.6.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.8.tgz",
"integrity": "sha512-OFP2u/3T1R5CEgWCEONuJ1a5+MFKnOYpkywpUSxv/dj1LeBT1erK+JwM7zK0ROy2BRhqVCf0LRw/kHqKuMkVGg==" "integrity": "sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw=="
}, },
"domutils": { "domutils": {
"version": "2.8.0", "version": "2.8.0",
@ -16860,15 +16860,15 @@
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
}, },
"mermaid": { "mermaid": {
"version": "9.1.1", "version": "9.1.2",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.1.1.tgz", "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-9.1.2.tgz",
"integrity": "sha512-2RVD+WkzZ4VDyO9gQvQAuQ/ux2gLigJtKDTlbwjYqOR/NwsVzTSfGm/kx648/qWJsg6Sv04tE9BWCO8s6a+pFA==", "integrity": "sha512-RVf3hBKqiMfyORHboCaEjOAK1TomLO50hYRPvlTrZCXlCniM5pRpe8UlkHBjjpaLtioZnbdYv/vEVj7iKnwkJQ==",
"requires": { "requires": {
"@braintree/sanitize-url": "^6.0.0", "@braintree/sanitize-url": "^6.0.0",
"d3": "^7.0.0", "d3": "^7.0.0",
"dagre": "^0.8.5", "dagre": "^0.8.5",
"dagre-d3": "^0.6.4", "dagre-d3": "^0.6.4",
"dompurify": "2.3.6", "dompurify": "2.3.8",
"graphlib": "^2.1.8", "graphlib": "^2.1.8",
"khroma": "^2.0.0", "khroma": "^2.0.0",
"moment-mini": "^2.24.0", "moment-mini": "^2.24.0",

View file

@ -22,7 +22,7 @@
"less": "4.1.2", "less": "4.1.2",
"less-loader": "11.0.0", "less-loader": "11.0.0",
"license-checker-webpack-plugin": "0.2.1", "license-checker-webpack-plugin": "0.2.1",
"mermaid": "9.1.1", "mermaid": "9.1.2",
"mini-css-extract-plugin": "2.6.0", "mini-css-extract-plugin": "2.6.0",
"monaco-editor": "0.33.0", "monaco-editor": "0.33.0",
"monaco-editor-webpack-plugin": "7.0.1", "monaco-editor-webpack-plugin": "7.0.1",

View file

@ -886,7 +886,7 @@ func dismissReview(ctx *context.APIContext, msg string, isDismiss bool) {
return return
} }
_, err := pull_service.DismissReview(ctx, review.ID, msg, ctx.Doer, isDismiss) _, err := pull_service.DismissReview(ctx, review.ID, ctx.Repo.Repository.ID, msg, ctx.Doer, isDismiss)
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "pull_service.DismissReview", err) ctx.Error(http.StatusInternalServerError, "pull_service.DismissReview", err)
return return

View file

@ -24,13 +24,13 @@ func responseAPIUsers(ctx *context.APIContext, users []*user_model.User) {
} }
func listUserFollowers(ctx *context.APIContext, u *user_model.User) { func listUserFollowers(ctx *context.APIContext, u *user_model.User) {
users, err := user_model.GetUserFollowers(u, utils.GetListOptions(ctx)) users, count, err := user_model.GetUserFollowers(ctx, u, ctx.Doer, utils.GetListOptions(ctx))
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "GetUserFollowers", err) ctx.Error(http.StatusInternalServerError, "GetUserFollowers", err)
return return
} }
ctx.SetTotalCountHeader(int64(u.NumFollowers)) ctx.SetTotalCountHeader(count)
responseAPIUsers(ctx, users) responseAPIUsers(ctx, users)
} }
@ -86,13 +86,13 @@ func ListFollowers(ctx *context.APIContext) {
} }
func listUserFollowing(ctx *context.APIContext, u *user_model.User) { func listUserFollowing(ctx *context.APIContext, u *user_model.User) {
users, err := user_model.GetUserFollowing(u, utils.GetListOptions(ctx)) users, count, err := user_model.GetUserFollowing(ctx, u, ctx.Doer, utils.GetListOptions(ctx))
if err != nil { if err != nil {
ctx.Error(http.StatusInternalServerError, "GetUserFollowing", err) ctx.Error(http.StatusInternalServerError, "GetUserFollowing", err)
return return
} }
ctx.SetTotalCountHeader(int64(u.NumFollowing)) ctx.SetTotalCountHeader(count)
responseAPIUsers(ctx, users) responseAPIUsers(ctx, users)
} }

View file

@ -69,7 +69,7 @@ func Init(next http.Handler) http.Handler {
Render: rnd, Render: rnd,
Session: session.GetSession(req), Session: session.GetSession(req),
Data: map[string]interface{}{ Data: map[string]interface{}{
"i18n": locale, "locale": locale,
"Title": locale.Tr("install.install"), "Title": locale.Tr("install.install"),
"PageIsInstall": true, "PageIsInstall": true,
"DbTypeNames": dbTypeNames, "DbTypeNames": dbTypeNames,

View file

@ -57,7 +57,7 @@ func installRecovery() func(next http.Handler) http.Handler {
store := dataStore{ store := dataStore{
"Language": lc.Language(), "Language": lc.Language(),
"CurrentURL": setting.AppSubURL + req.URL.RequestURI(), "CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
"i18n": lc, "locale": lc,
"SignedUserID": int64(0), "SignedUserID": int64(0),
"SignedUserName": "", "SignedUserName": "",
} }

View file

@ -477,7 +477,7 @@ func (ctx *preReceiveContext) loadPusherAndPermission() bool {
userPerm, err := access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, user) userPerm, err := access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, user)
if err != nil { if err != nil {
log.Error("Unable to get Repo permission of repo %s/%s of User %s", ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name, user.Name, err) log.Error("Unable to get Repo permission of repo %s/%s of User %s: %v", ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name, user.Name, err)
ctx.JSON(http.StatusInternalServerError, private.Response{ ctx.JSON(http.StatusInternalServerError, private.Response{
Err: fmt.Sprintf("Unable to get Repo permission of repo %s/%s of User %s: %v", ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name, user.Name, err), Err: fmt.Sprintf("Unable to get Repo permission of repo %s/%s of User %s: %v", ctx.Repo.Repository.OwnerName, ctx.Repo.Repository.Name, user.Name, err),
}) })

View file

@ -25,6 +25,7 @@ import (
"code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/queue"
"code.gitea.io/gitea/modules/setting" "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/updatechecker" "code.gitea.io/gitea/modules/updatechecker"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
@ -85,7 +86,7 @@ var sysStatus struct {
} }
func updateSystemStatus() { func updateSystemStatus() {
sysStatus.Uptime = timeutil.TimeSincePro(setting.AppStartTime, "en") sysStatus.Uptime = timeutil.TimeSincePro(setting.AppStartTime, translation.NewLocale("en-US"))
m := new(runtime.MemStats) m := new(runtime.MemStats)
runtime.ReadMemStats(m) runtime.ReadMemStats(m)

View file

@ -266,7 +266,7 @@ func SignInPost(ctx *context.Context) {
} }
if hasTOTPtwofa { if hasTOTPtwofa {
// User will need to use U2F, save data // User will need to use WebAuthn, save data
if err := ctx.Session.Set("totpEnrolled", u.ID); err != nil { if err := ctx.Session.Set("totpEnrolled", u.ID); err != nil {
ctx.ServerError("UserSignIn: Unable to set WebAuthn Enrolled in session", err) ctx.ServerError("UserSignIn: Unable to set WebAuthn Enrolled in session", err)
return return
@ -278,7 +278,7 @@ func SignInPost(ctx *context.Context) {
return return
} }
// If we have U2F redirect there first // If we have WebAuthn redirect there first
if hasWebAuthnTwofa { if hasWebAuthnTwofa {
ctx.Redirect(setting.AppSubURL + "/user/webauthn") ctx.Redirect(setting.AppSubURL + "/user/webauthn")
return return
@ -317,7 +317,6 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember, obeyRe
_ = ctx.Session.Delete("openid_determined_username") _ = ctx.Session.Delete("openid_determined_username")
_ = ctx.Session.Delete("twofaUid") _ = ctx.Session.Delete("twofaUid")
_ = ctx.Session.Delete("twofaRemember") _ = ctx.Session.Delete("twofaRemember")
_ = ctx.Session.Delete("u2fChallenge")
_ = ctx.Session.Delete("linkAccount") _ = ctx.Session.Delete("linkAccount")
if err := ctx.Session.Set("uid", u.ID); err != nil { if err := ctx.Session.Set("uid", u.ID); err != nil {
log.Error("Error setting uid %d in session: %v", u.ID, err) log.Error("Error setting uid %d in session: %v", u.ID, err)
@ -629,7 +628,7 @@ func handleUserCreated(ctx *context.Context, u *user_model.User, gothUser *goth.
ctx.Data["IsSendRegisterMail"] = true ctx.Data["IsSendRegisterMail"] = true
ctx.Data["Email"] = u.Email ctx.Data["Email"] = u.Email
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language()) ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
ctx.HTML(http.StatusOK, TplActivate) ctx.HTML(http.StatusOK, TplActivate)
if setting.CacheService.Enabled { if setting.CacheService.Enabled {
@ -658,7 +657,7 @@ func Activate(ctx *context.Context) {
if setting.CacheService.Enabled && ctx.Cache.IsExist("MailResendLimit_"+ctx.Doer.LowerName) { if setting.CacheService.Enabled && ctx.Cache.IsExist("MailResendLimit_"+ctx.Doer.LowerName) {
ctx.Data["ResendLimited"] = true ctx.Data["ResendLimited"] = true
} else { } else {
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language()) ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
mailer.SendActivateAccountMail(ctx.Locale, ctx.Doer) mailer.SendActivateAccountMail(ctx.Locale, ctx.Doer)
if setting.CacheService.Enabled { if setting.CacheService.Enabled {

View file

@ -63,7 +63,7 @@ func ForgotPasswdPost(ctx *context.Context) {
u, err := user_model.GetUserByEmail(email) u, err := user_model.GetUserByEmail(email)
if err != nil { if err != nil {
if user_model.IsErrUserNotExist(err) { if user_model.IsErrUserNotExist(err) {
ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale.Language()) ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale)
ctx.Data["IsResetSent"] = true ctx.Data["IsResetSent"] = true
ctx.HTML(http.StatusOK, tplForgotPassword) ctx.HTML(http.StatusOK, tplForgotPassword)
return return
@ -93,7 +93,7 @@ func ForgotPasswdPost(ctx *context.Context) {
} }
} }
ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale.Language()) ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale)
ctx.Data["IsResetSent"] = true ctx.Data["IsResetSent"] = true
ctx.HTML(http.StatusOK, tplForgotPassword) ctx.HTML(http.StatusOK, tplForgotPassword)
} }

View file

@ -67,10 +67,7 @@ func WebAuthnLoginAssertion(ctx *context.Context) {
return return
} }
// FIXME: DEPRECATED appid is deprecated and is planned to be removed in v1.18.0 assertion, sessionData, err := wa.WebAuthn.BeginLogin((*wa.User)(user))
assertion, sessionData, err := wa.WebAuthn.BeginLogin((*wa.User)(user), webauthn.WithAssertionExtensions(protocol.AuthenticationExtensions{
"appid": setting.U2F.AppID,
}))
if err != nil { if err != nil {
ctx.ServerError("webauthn.BeginLogin", err) ctx.ServerError("webauthn.BeginLogin", err)
return return
@ -159,12 +156,5 @@ func WebAuthnLoginAssertionPost(ctx *context.Context) {
} }
_ = ctx.Session.Delete("twofaUid") _ = ctx.Session.Delete("twofaUid")
// Finally check if the appid extension was used:
if value, ok := parsedResponse.ClientExtensionResults["appid"]; ok {
if appid, ok := value.(bool); ok && appid {
ctx.Flash.Error(ctx.Tr("webauthn_u2f_deprecated", dbCred.Name))
}
}
ctx.JSON(http.StatusOK, map[string]string{"redirect": redirect}) ctx.JSON(http.StatusOK, map[string]string{"redirect": redirect})
} }

View file

@ -139,7 +139,7 @@ func Recovery() func(next http.Handler) http.Handler {
store := dataStore{ store := dataStore{
"Language": lc.Language(), "Language": lc.Language(),
"CurrentURL": setting.AppSubURL + req.URL.RequestURI(), "CurrentURL": setting.AppSubURL + req.URL.RequestURI(),
"i18n": lc, "locale": lc,
} }
user := context.GetContextUser(req) user := context.GetContextUser(req)

View file

@ -21,8 +21,8 @@ func TemplatePreview(ctx *context.Context) {
ctx.Data["AppVer"] = setting.AppVer ctx.Data["AppVer"] = setting.AppVer
ctx.Data["AppUrl"] = setting.AppURL ctx.Data["AppUrl"] = setting.AppURL
ctx.Data["Code"] = "2014031910370000009fff6782aadb2162b4a997acb69d4400888e0b9274657374" ctx.Data["Code"] = "2014031910370000009fff6782aadb2162b4a997acb69d4400888e0b9274657374"
ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language()) ctx.Data["ActiveCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)
ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale.Language()) ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale)
ctx.Data["CurDbValue"] = "" ctx.Data["CurDbValue"] = ""
ctx.HTML(http.StatusOK, base.TplName(ctx.Params("*"))) ctx.HTML(http.StatusOK, base.TplName(ctx.Params("*")))

View file

@ -255,7 +255,7 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
commitCnt++ commitCnt++
// User avatar image // User avatar image
commitSince := timeutil.TimeSinceUnix(timeutil.TimeStamp(commit.Author.When.Unix()), ctx.Locale.Language()) commitSince := timeutil.TimeSinceUnix(timeutil.TimeStamp(commit.Author.When.Unix()), ctx.Locale)
var avatar string var avatar string
if commit.User != nil { if commit.User != nil {

View file

@ -803,7 +803,8 @@ func NewIssue(ctx *context.Context) {
body := ctx.FormString("body") body := ctx.FormString("body")
ctx.Data["BodyQuery"] = body ctx.Data["BodyQuery"] = body
ctx.Data["IsProjectsEnabled"] = ctx.Repo.CanRead(unit.TypeProjects) isProjectsEnabled := ctx.Repo.CanRead(unit.TypeProjects)
ctx.Data["IsProjectsEnabled"] = isProjectsEnabled
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
upload.AddUploadContext(ctx, "comment") upload.AddUploadContext(ctx, "comment")
@ -819,7 +820,7 @@ func NewIssue(ctx *context.Context) {
} }
projectID := ctx.FormInt64("project") projectID := ctx.FormInt64("project")
if projectID > 0 { if projectID > 0 && isProjectsEnabled {
project, err := project_model.GetProjectByID(ctx, projectID) project, err := project_model.GetProjectByID(ctx, projectID)
if err != nil { if err != nil {
log.Error("GetProjectByID: %d: %v", projectID, err) log.Error("GetProjectByID: %d: %v", projectID, err)
@ -1043,6 +1044,11 @@ func NewIssuePost(ctx *context.Context) {
} }
if projectID > 0 { if projectID > 0 {
if !ctx.Repo.CanRead(unit.TypeProjects) {
// User must also be able to see the project.
ctx.Error(http.StatusBadRequest, "user hasn't permissions to read projects")
return
}
if err := issues_model.ChangeProjectAssign(issue, ctx.Doer, projectID); err != nil { if err := issues_model.ChangeProjectAssign(issue, ctx.Doer, projectID); err != nil {
ctx.ServerError("ChangeProjectAssign", err) ctx.ServerError("ChangeProjectAssign", err)
return return
@ -1783,6 +1789,10 @@ func getActionIssues(ctx *context.Context) []*issues_model.Issue {
issueUnitEnabled := ctx.Repo.CanRead(unit.TypeIssues) issueUnitEnabled := ctx.Repo.CanRead(unit.TypeIssues)
prUnitEnabled := ctx.Repo.CanRead(unit.TypePullRequests) prUnitEnabled := ctx.Repo.CanRead(unit.TypePullRequests)
for _, issue := range issues { for _, issue := range issues {
if issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound("some issue's RepoID is incorrect", errors.New("some issue's RepoID is incorrect"))
return nil
}
if issue.IsPull && !prUnitEnabled || !issue.IsPull && !issueUnitEnabled { if issue.IsPull && !prUnitEnabled || !issue.IsPull && !issueUnitEnabled {
ctx.NotFound("IssueOrPullRequestUnitNotAllowed", nil) ctx.NotFound("IssueOrPullRequestUnitNotAllowed", nil)
return nil return nil

View file

@ -17,7 +17,6 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation/i18n"
"github.com/sergi/go-diff/diffmatchpatch" "github.com/sergi/go-diff/diffmatchpatch"
) )
@ -29,14 +28,13 @@ func GetContentHistoryOverview(ctx *context.Context) {
return return
} }
lang := ctx.Locale.Language()
editedHistoryCountMap, _ := issues_model.QueryIssueContentHistoryEditedCountMap(ctx, issue.ID) editedHistoryCountMap, _ := issues_model.QueryIssueContentHistoryEditedCountMap(ctx, issue.ID)
ctx.JSON(http.StatusOK, map[string]interface{}{ ctx.JSON(http.StatusOK, map[string]interface{}{
"i18n": map[string]interface{}{ "i18n": map[string]interface{}{
"textEdited": i18n.Tr(lang, "repo.issues.content_history.edited"), "textEdited": ctx.Tr("repo.issues.content_history.edited"),
"textDeleteFromHistory": i18n.Tr(lang, "repo.issues.content_history.delete_from_history"), "textDeleteFromHistory": ctx.Tr("repo.issues.content_history.delete_from_history"),
"textDeleteFromHistoryConfirm": i18n.Tr(lang, "repo.issues.content_history.delete_from_history_confirm"), "textDeleteFromHistoryConfirm": ctx.Tr("repo.issues.content_history.delete_from_history_confirm"),
"textOptions": i18n.Tr(lang, "repo.issues.content_history.options"), "textOptions": ctx.Tr("repo.issues.content_history.options"),
}, },
"editedHistoryCountMap": editedHistoryCountMap, "editedHistoryCountMap": editedHistoryCountMap,
}) })
@ -55,7 +53,6 @@ func GetContentHistoryList(ctx *context.Context) {
// render history list to HTML for frontend dropdown items: (name, value) // render history list to HTML for frontend dropdown items: (name, value)
// name is HTML of "avatar + userName + userAction + timeSince" // name is HTML of "avatar + userName + userAction + timeSince"
// value is historyId // value is historyId
lang := ctx.Locale.Language()
var results []map[string]interface{} var results []map[string]interface{}
for _, item := range items { for _, item := range items {
var actionText string var actionText string
@ -67,7 +64,7 @@ func GetContentHistoryList(ctx *context.Context) {
} else { } else {
actionText = ctx.Locale.Tr("repo.issues.content_history.edited") actionText = ctx.Locale.Tr("repo.issues.content_history.edited")
} }
timeSinceText := timeutil.TimeSinceUnix(item.EditedUnix, lang) timeSinceText := timeutil.TimeSinceUnix(item.EditedUnix, ctx.Locale)
username := item.UserName username := item.UserName
if setting.UI.DefaultShowFullName && strings.TrimSpace(item.UserFullName) != "" { if setting.UI.DefaultShowFullName && strings.TrimSpace(item.UserFullName) != "" {

View file

@ -5,6 +5,7 @@
package repo package repo
import ( import (
"errors"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
@ -633,10 +634,17 @@ func MoveIssues(ctx *context.Context) {
} }
if len(movedIssues) != len(form.Issues) { if len(movedIssues) != len(form.Issues) {
ctx.ServerError("IssuesNotFound", err) ctx.ServerError("some issues do not exist", errors.New("some issues do not exist"))
return return
} }
for _, issue := range movedIssues {
if issue.RepoID != project.RepoID {
ctx.ServerError("Some issue's repoID is not equal to project's repoID", errors.New("Some issue's repoID is not equal to project's repoID"))
return
}
}
if err = project_model.MoveIssuesOnProjectBoard(board, sortedIssueIDs); err != nil { if err = project_model.MoveIssuesOnProjectBoard(board, sortedIssueIDs); err != nil {
ctx.ServerError("MoveIssuesOnProjectBoard", err) ctx.ServerError("MoveIssuesOnProjectBoard", err)
return return

View file

@ -5,6 +5,7 @@
package repo package repo
import ( import (
"errors"
"fmt" "fmt"
"net/http" "net/http"
@ -118,6 +119,11 @@ func UpdateResolveConversation(ctx *context.Context) {
return return
} }
if comment.Issue.RepoID != ctx.Repo.Repository.ID {
ctx.NotFound("comment's repoID is incorrect", errors.New("comment's repoID is incorrect"))
return
}
var permResult bool var permResult bool
if permResult, err = issues_model.CanMarkConversation(comment.Issue, ctx.Doer); err != nil { if permResult, err = issues_model.CanMarkConversation(comment.Issue, ctx.Doer); err != nil {
ctx.ServerError("CanMarkConversation", err) ctx.ServerError("CanMarkConversation", err)
@ -236,7 +242,7 @@ func SubmitReview(ctx *context.Context) {
// DismissReview dismissing stale review by repo admin // DismissReview dismissing stale review by repo admin
func DismissReview(ctx *context.Context) { func DismissReview(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.DismissReviewForm) form := web.GetForm(ctx).(*forms.DismissReviewForm)
comm, err := pull_service.DismissReview(ctx, form.ReviewID, form.Message, ctx.Doer, true) comm, err := pull_service.DismissReview(ctx, form.ReviewID, ctx.Repo.Repository.ID, form.Message, ctx.Doer, true)
if err != nil { if err != nil {
ctx.ServerError("pull_service.DismissReview", err) ctx.ServerError("pull_service.DismissReview", err)
return return

View file

@ -163,7 +163,7 @@ func Profile(ctx *context.Context) {
switch tab { switch tab {
case "followers": case "followers":
items, err := user_model.GetUserFollowers(ctx.ContextUser, db.ListOptions{ items, count, err := user_model.GetUserFollowers(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{
PageSize: setting.UI.User.RepoPagingNum, PageSize: setting.UI.User.RepoPagingNum,
Page: page, Page: page,
}) })
@ -173,9 +173,9 @@ func Profile(ctx *context.Context) {
} }
ctx.Data["Cards"] = items ctx.Data["Cards"] = items
total = ctx.ContextUser.NumFollowers total = int(count)
case "following": case "following":
items, err := user_model.GetUserFollowing(ctx.ContextUser, db.ListOptions{ items, count, err := user_model.GetUserFollowing(ctx, ctx.ContextUser, ctx.Doer, db.ListOptions{
PageSize: setting.UI.User.RepoPagingNum, PageSize: setting.UI.User.RepoPagingNum,
Page: page, Page: page,
}) })
@ -185,7 +185,7 @@ func Profile(ctx *context.Context) {
} }
ctx.Data["Cards"] = items ctx.Data["Cards"] = items
total = ctx.ContextUser.NumFollowing total = int(count)
case "activity": case "activity":
ctx.Data["Feeds"], err = models.GetFeeds(ctx, models.GetFeedsOptions{ ctx.Data["Feeds"], err = models.GetFeeds(ctx, models.GetFeedsOptions{
RequestedUser: ctx.ContextUser, RequestedUser: ctx.ContextUser,

View file

@ -34,6 +34,7 @@ func Account(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsAccount"] = true ctx.Data["PageIsSettingsAccount"] = true
ctx.Data["Email"] = ctx.Doer.Email ctx.Data["Email"] = ctx.Doer.Email
ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail
loadAccountData(ctx) loadAccountData(ctx)
@ -146,7 +147,7 @@ func EmailPost(ctx *context.Context) {
log.Error("Set cache(MailResendLimit) fail: %v", err) log.Error("Set cache(MailResendLimit) fail: %v", err)
} }
} }
ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", address, timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language()))) ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", address, timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)))
ctx.Redirect(setting.AppSubURL + "/user/settings/account") ctx.Redirect(setting.AppSubURL + "/user/settings/account")
return return
} }
@ -208,7 +209,7 @@ func EmailPost(ctx *context.Context) {
log.Error("Set cache(MailResendLimit) fail: %v", err) log.Error("Set cache(MailResendLimit) fail: %v", err)
} }
} }
ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", email.Email, timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale.Language()))) ctx.Flash.Info(ctx.Tr("settings.add_email_confirmation_sent", email.Email, timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, ctx.Locale)))
} else { } else {
ctx.Flash.Success(ctx.Tr("settings.add_email_success")) ctx.Flash.Success(ctx.Tr("settings.add_email_success"))
} }

View file

@ -23,7 +23,7 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/typesniffer" "code.gitea.io/gitea/modules/typesniffer"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
@ -136,7 +136,7 @@ func ProfilePost(ctx *context.Context) {
middleware.SetLocaleCookie(ctx.Resp, ctx.Doer.Language, 0) middleware.SetLocaleCookie(ctx.Resp, ctx.Doer.Language, 0)
log.Trace("User settings updated: %s", ctx.Doer.Name) log.Trace("User settings updated: %s", ctx.Doer.Name)
ctx.Flash.Success(i18n.Tr(ctx.Doer.Language, "settings.update_profile_success")) ctx.Flash.Success(translation.NewLocale(ctx.Doer.Language).Tr("settings.update_profile_success"))
ctx.Redirect(setting.AppSubURL + "/user/settings") ctx.Redirect(setting.AppSubURL + "/user/settings")
} }
@ -425,7 +425,7 @@ func UpdateUserLang(ctx *context.Context) {
middleware.SetLocaleCookie(ctx.Resp, ctx.Doer.Language, 0) middleware.SetLocaleCookie(ctx.Resp, ctx.Doer.Language, 0)
log.Trace("User settings updated: %s", ctx.Doer.Name) log.Trace("User settings updated: %s", ctx.Doer.Name)
ctx.Flash.Success(i18n.Tr(ctx.Doer.Language, "settings.update_language_success")) ctx.Flash.Success(translation.NewLocale(ctx.Doer.Language).Tr("settings.update_language_success"))
ctx.Redirect(setting.AppSubURL + "/user/settings/appearance") ctx.Redirect(setting.AppSubURL + "/user/settings/appearance")
} }

View file

@ -26,7 +26,6 @@ const (
func Security(ctx *context.Context) { func Security(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings") ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["PageIsSettingsSecurity"] = true ctx.Data["PageIsSettingsSecurity"] = true
ctx.Data["RequireU2F"] = true
if ctx.FormString("openid.return_to") != "" { if ctx.FormString("openid.return_to") != "" {
settingsOpenIDVerify(ctx) settingsOpenIDVerify(ctx)

View file

@ -901,7 +901,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel) m.Post("/labels", reqRepoIssuesOrPullsWriter, repo.UpdateIssueLabel)
m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone) m.Post("/milestone", reqRepoIssuesOrPullsWriter, repo.UpdateIssueMilestone)
m.Post("/projects", reqRepoIssuesOrPullsWriter, repo.UpdateIssueProject) m.Post("/projects", reqRepoIssuesOrPullsWriter, reqRepoProjectsReader, repo.UpdateIssueProject)
m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee) m.Post("/assignee", reqRepoIssuesOrPullsWriter, repo.UpdateIssueAssignee)
m.Post("/request_review", reqRepoIssuesOrPullsReader, repo.UpdatePullReviewRequest) m.Post("/request_review", reqRepoIssuesOrPullsReader, repo.UpdatePullReviewRequest)
m.Post("/dismiss_review", reqRepoAdmin, bindIgnErr(forms.DismissReviewForm{}), repo.DismissReview) m.Post("/dismiss_review", reqRepoAdmin, bindIgnErr(forms.DismissReviewForm{}), repo.DismissReview)

View file

@ -199,7 +199,7 @@ func checkRestricted(l *ldap.Conn, ls *Source, userDN string) bool {
// List all group memberships of a user // List all group memberships of a user
func (source *Source) listLdapGroupMemberships(l *ldap.Conn, uid string) []string { func (source *Source) listLdapGroupMemberships(l *ldap.Conn, uid string) []string {
var ldapGroups []string var ldapGroups []string
groupFilter := fmt.Sprintf("(%s=%s)", source.GroupMemberUID, uid) groupFilter := fmt.Sprintf("(%s=%s)", source.GroupMemberUID, ldap.EscapeFilter(uid))
result, err := l.Search(ldap.NewSearchRequest( result, err := l.Search(ldap.NewSearchRequest(
source.GroupDN, source.GroupDN,
ldap.ScopeWholeSubtree, ldap.ScopeWholeSubtree,

View file

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/sync" "code.gitea.io/gitea/modules/sync"
"code.gitea.io/gitea/modules/translation"
"github.com/gogs/cron" "github.com/gogs/cron"
) )
@ -63,7 +64,7 @@ type TaskTableRow struct {
task *Task task *Task
} }
func (t *TaskTableRow) FormatLastMessage(locale string) string { func (t *TaskTableRow) FormatLastMessage(locale translation.Locale) string {
if t.Status == "finished" { if t.Status == "finished" {
return t.task.GetConfig().FormatMessage(locale, t.Name, t.Status, t.LastDoer) return t.task.GetConfig().FormatMessage(locale, t.Name, t.Status, t.LastDoer)
} }

View file

@ -7,7 +7,7 @@ package cron
import ( import (
"time" "time"
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation"
) )
// Config represents a basic configuration interface that cron task // Config represents a basic configuration interface that cron task
@ -15,7 +15,7 @@ type Config interface {
IsEnabled() bool IsEnabled() bool
DoRunAtStart() bool DoRunAtStart() bool
GetSchedule() string GetSchedule() string
FormatMessage(locale, name, status, doer string, args ...interface{}) string FormatMessage(locale translation.Locale, name, status, doer string, args ...interface{}) string
DoNoticeOnSuccess() bool DoNoticeOnSuccess() bool
} }
@ -69,9 +69,9 @@ func (b *BaseConfig) DoNoticeOnSuccess() bool {
// FormatMessage returns a message for the task // FormatMessage returns a message for the task
// Please note the `status` string will be concatenated with `admin.dashboard.cron.` and `admin.dashboard.task.` to provide locale messages. Similarly `name` will be composed with `admin.dashboard.` to provide the locale name for the task. // Please note the `status` string will be concatenated with `admin.dashboard.cron.` and `admin.dashboard.task.` to provide locale messages. Similarly `name` will be composed with `admin.dashboard.` to provide the locale name for the task.
func (b *BaseConfig) FormatMessage(locale, name, status, doer string, args ...interface{}) string { func (b *BaseConfig) FormatMessage(locale translation.Locale, name, status, doer string, args ...interface{}) string {
realArgs := make([]interface{}, 0, len(args)+2) realArgs := make([]interface{}, 0, len(args)+2)
realArgs = append(realArgs, i18n.Tr(locale, "admin.dashboard."+name)) realArgs = append(realArgs, locale.Tr("admin.dashboard."+name))
if doer == "" { if doer == "" {
realArgs = append(realArgs, "(Cron)") realArgs = append(realArgs, "(Cron)")
} else { } else {
@ -81,7 +81,7 @@ func (b *BaseConfig) FormatMessage(locale, name, status, doer string, args ...in
realArgs = append(realArgs, args...) realArgs = append(realArgs, args...)
} }
if doer == "" { if doer == "" {
return i18n.Tr(locale, "admin.dashboard.cron."+status, realArgs...) return locale.Tr("admin.dashboard.cron."+status, realArgs...)
} }
return i18n.Tr(locale, "admin.dashboard.task."+status, realArgs...) return locale.Tr("admin.dashboard.task."+status, realArgs...)
} }

View file

@ -94,7 +94,7 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) {
doerName = doer.Name doerName = doer.Name
} }
ctx, _, finished := pm.AddContext(baseCtx, config.FormatMessage("en-US", t.Name, "process", doerName)) ctx, _, finished := pm.AddContext(baseCtx, config.FormatMessage(translation.NewLocale("en-US"), t.Name, "process", doerName))
defer finished() defer finished()
if err := t.fun(ctx, doer, config); err != nil { if err := t.fun(ctx, doer, config); err != nil {
@ -114,7 +114,7 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) {
t.LastDoer = doerName t.LastDoer = doerName
t.lock.Unlock() t.lock.Unlock()
if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage("en-US", t.Name, "cancelled", doerName, message)); err != nil { if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage(translation.NewLocale("en-US"), t.Name, "cancelled", doerName, message)); err != nil {
log.Error("CreateNotice: %v", err) log.Error("CreateNotice: %v", err)
} }
return return
@ -127,7 +127,7 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) {
t.lock.Unlock() t.lock.Unlock()
if config.DoNoticeOnSuccess() { if config.DoNoticeOnSuccess() {
if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage("en-US", t.Name, "finished", doerName)); err != nil { if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage(translation.NewLocale("en-US"), t.Name, "finished", doerName)); err != nil {
log.Error("CreateNotice: %v", err) log.Error("CreateNotice: %v", err)
} }
} }
@ -148,7 +148,7 @@ func RegisterTask(name string, config Config, fun func(context.Context, *user_mo
log.Debug("Registering task: %s", name) log.Debug("Registering task: %s", name)
i18nKey := "admin.dashboard." + name i18nKey := "admin.dashboard." + name
if _, ok := translation.TryTr("en-US", i18nKey); !ok { if value := translation.NewLocale("en-US").Tr(i18nKey); value == i18nKey {
return fmt.Errorf("translation is missing for task %q, please add translation for %q", name, i18nKey) return fmt.Errorf("translation is missing for task %q, please add translation for %q", name, i18nKey)
} }

View file

@ -15,6 +15,17 @@ import (
) )
func changeMilestoneAssign(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldMilestoneID int64) error { func changeMilestoneAssign(ctx context.Context, doer *user_model.User, issue *issues_model.Issue, oldMilestoneID int64) error {
// Only check if milestone exists if we don't remove it.
if issue.MilestoneID > 0 {
has, err := issues_model.HasMilestoneByRepoID(ctx, issue.RepoID, issue.MilestoneID)
if err != nil {
return fmt.Errorf("HasMilestoneByRepoID: %v", err)
}
if !has {
return fmt.Errorf("HasMilestoneByRepoID: issue doesn't exist")
}
}
if err := issues_model.UpdateIssueCols(ctx, issue, "milestone_id"); err != nil { if err := issues_model.UpdateIssueCols(ctx, issue, "milestone_id"); err != nil {
return err return err
} }

View file

@ -75,12 +75,12 @@ func sendUserMail(language string, u *user_model.User, tpl base.TplName, code, s
locale := translation.NewLocale(language) locale := translation.NewLocale(language)
data := map[string]interface{}{ data := map[string]interface{}{
"DisplayName": u.DisplayName(), "DisplayName": u.DisplayName(),
"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, language), "ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale),
"ResetPwdCodeLives": timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, language), "ResetPwdCodeLives": timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, locale),
"Code": code, "Code": code,
"Language": locale.Language(), "Language": locale.Language(),
// helper // helper
"i18n": locale, "locale": locale,
"Str2html": templates.Str2html, "Str2html": templates.Str2html,
"DotEscape": templates.DotEscape, "DotEscape": templates.DotEscape,
} }
@ -126,12 +126,12 @@ func SendActivateEmailMail(u *user_model.User, email *user_model.EmailAddress) {
locale := translation.NewLocale(u.Language) locale := translation.NewLocale(u.Language)
data := map[string]interface{}{ data := map[string]interface{}{
"DisplayName": u.DisplayName(), "DisplayName": u.DisplayName(),
"ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale.Language()), "ActiveCodeLives": timeutil.MinutesToFriendly(setting.Service.ActiveCodeLives, locale),
"Code": u.GenerateEmailActivateCode(email.Email), "Code": u.GenerateEmailActivateCode(email.Email),
"Email": email.Email, "Email": email.Email,
"Language": locale.Language(), "Language": locale.Language(),
// helper // helper
"i18n": locale, "locale": locale,
"Str2html": templates.Str2html, "Str2html": templates.Str2html,
"DotEscape": templates.DotEscape, "DotEscape": templates.DotEscape,
} }
@ -162,7 +162,7 @@ func SendRegisterNotifyMail(u *user_model.User) {
"Username": u.Name, "Username": u.Name,
"Language": locale.Language(), "Language": locale.Language(),
// helper // helper
"i18n": locale, "locale": locale,
"Str2html": templates.Str2html, "Str2html": templates.Str2html,
"DotEscape": templates.DotEscape, "DotEscape": templates.DotEscape,
} }
@ -196,7 +196,7 @@ func SendCollaboratorMail(u, doer *user_model.User, repo *repo_model.Repository)
"Link": repo.HTMLURL(), "Link": repo.HTMLURL(),
"Language": locale.Language(), "Language": locale.Language(),
// helper // helper
"i18n": locale, "locale": locale,
"Str2html": templates.Str2html, "Str2html": templates.Str2html,
"DotEscape": templates.DotEscape, "DotEscape": templates.DotEscape,
} }
@ -281,7 +281,7 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
"ReviewComments": reviewComments, "ReviewComments": reviewComments,
"Language": locale.Language(), "Language": locale.Language(),
// helper // helper
"i18n": locale, "locale": locale,
"Str2html": templates.Str2html, "Str2html": templates.Str2html,
"DotEscape": templates.DotEscape, "DotEscape": templates.DotEscape,
} }

View file

@ -75,7 +75,7 @@ func mailNewRelease(ctx context.Context, lang string, tos []string, rel *models.
"Subject": subject, "Subject": subject,
"Language": locale.Language(), "Language": locale.Language(),
// helper // helper
"i18n": locale, "locale": locale,
"Str2html": templates.Str2html, "Str2html": templates.Str2html,
"DotEscape": templates.DotEscape, "DotEscape": templates.DotEscape,
} }

View file

@ -74,7 +74,7 @@ func sendRepoTransferNotifyMailPerLang(lang string, newOwner, doer *user_model.U
"Language": locale.Language(), "Language": locale.Language(),
"Destination": destination, "Destination": destination,
// helper // helper
"i18n": locale, "locale": locale,
"Str2html": templates.Str2html, "Str2html": templates.Str2html,
"DotEscape": templates.DotEscape, "DotEscape": templates.DotEscape,
} }

View file

@ -590,7 +590,7 @@ func updateOptionsUnits(opts *base.MigrateOptions, units []string) error {
opts.ReleaseAssets = true opts.ReleaseAssets = true
} else { } else {
for _, unit := range units { for _, unit := range units {
switch strings.ToLower(unit) { switch strings.ToLower(strings.TrimSpace(unit)) {
case "": case "":
continue continue
case "wiki": case "wiki":

View file

@ -271,7 +271,7 @@ func SubmitReview(ctx context.Context, doer *user_model.User, gitRepo *git.Repos
} }
// DismissReview dismissing stale review by repo admin // DismissReview dismissing stale review by repo admin
func DismissReview(ctx context.Context, reviewID int64, message string, doer *user_model.User, isDismiss bool) (comment *issues_model.Comment, err error) { func DismissReview(ctx context.Context, reviewID, repoID int64, message string, doer *user_model.User, isDismiss bool) (comment *issues_model.Comment, err error) {
review, err := issues_model.GetReviewByID(ctx, reviewID) review, err := issues_model.GetReviewByID(ctx, reviewID)
if err != nil { if err != nil {
return return
@ -281,6 +281,16 @@ func DismissReview(ctx context.Context, reviewID int64, message string, doer *us
return nil, fmt.Errorf("not need to dismiss this review because it's type is not Approve or change request") return nil, fmt.Errorf("not need to dismiss this review because it's type is not Approve or change request")
} }
// load data for notify
if err = review.LoadAttributes(ctx); err != nil {
return nil, err
}
// Check if the review's repoID is the one we're currently expecting.
if review.Issue.RepoID != repoID {
return nil, fmt.Errorf("reviews's repository is not the same as the one we expect")
}
if err = issues_model.DismissReview(review, isDismiss); err != nil { if err = issues_model.DismissReview(review, isDismiss); err != nil {
return return
} }
@ -289,10 +299,6 @@ func DismissReview(ctx context.Context, reviewID int64, message string, doer *us
return nil, nil return nil, nil
} }
// load data for notify
if err = review.LoadAttributes(ctx); err != nil {
return
}
if err = review.Issue.LoadPullRequest(); err != nil { if err = review.Issue.LoadPullRequest(); err != nil {
return return
} }

View file

@ -4,7 +4,7 @@
<div class="ui container"> <div class="ui container">
{{template "base/alert" .}} {{template "base/alert" .}}
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.auths.edit"}} {{.locale.Tr "admin.auths.edit"}}
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
@ -12,12 +12,12 @@
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input type="hidden" name="id" value="{{.Source.ID}}"> <input type="hidden" name="id" value="{{.Source.ID}}">
<div class="inline field"> <div class="inline field">
<label>{{$.i18n.Tr "admin.auths.auth_type"}}</label> <label>{{$.locale.Tr "admin.auths.auth_type"}}</label>
<input type="hidden" id="auth_type" name="type" value="{{.Source.Type.Int}}"> <input type="hidden" id="auth_type" name="type" value="{{.Source.Type.Int}}">
<span>{{.Source.TypeName}}</span> <span>{{.Source.TypeName}}</span>
</div> </div>
<div class="required inline field {{if .Err_Name}}error{{end}}"> <div class="required inline field {{if .Err_Name}}error{{end}}">
<label for="name">{{.i18n.Tr "admin.auths.auth_name"}}</label> <label for="name">{{.locale.Tr "admin.auths.auth_name"}}</label>
<input id="name" name="name" value="{{.Source.Name}}" autofocus required> <input id="name" name="name" value="{{.Source.Name}}" autofocus required>
</div> </div>
@ -25,7 +25,7 @@
{{if or .Source.IsLDAP .Source.IsDLDAP}} {{if or .Source.IsLDAP .Source.IsDLDAP}}
{{ $cfg:=.Source.Cfg }} {{ $cfg:=.Source.Cfg }}
<div class="inline required field {{if .Err_SecurityProtocol}}error{{end}}"> <div class="inline required field {{if .Err_SecurityProtocol}}error{{end}}">
<label>{{.i18n.Tr "admin.auths.security_protocol"}}</label> <label>{{.locale.Tr "admin.auths.security_protocol"}}</label>
<div class="ui selection security-protocol dropdown"> <div class="ui selection security-protocol dropdown">
<input type="hidden" id="security_protocol" name="security_protocol" value="{{$cfg.SecurityProtocol.Int}}"> <input type="hidden" id="security_protocol" name="security_protocol" value="{{$cfg.SecurityProtocol.Int}}">
<div class="text">{{$cfg.SecurityProtocolName}}</div> <div class="text">{{$cfg.SecurityProtocolName}}</div>
@ -38,74 +38,74 @@
</div> </div>
</div> </div>
<div class="required field"> <div class="required field">
<label for="host">{{.i18n.Tr "admin.auths.host"}}</label> <label for="host">{{.locale.Tr "admin.auths.host"}}</label>
<input id="host" name="host" value="{{$cfg.Host}}" placeholder="e.g. mydomain.com" required> <input id="host" name="host" value="{{$cfg.Host}}" placeholder="e.g. mydomain.com" required>
</div> </div>
<div class="required field"> <div class="required field">
<label for="port">{{.i18n.Tr "admin.auths.port"}}</label> <label for="port">{{.locale.Tr "admin.auths.port"}}</label>
<input id="port" name="port" value="{{$cfg.Port}}" placeholder="e.g. 636" required> <input id="port" name="port" value="{{$cfg.Port}}" placeholder="e.g. 636" required>
</div> </div>
<div class="has-tls inline field {{if not .HasTLS}}hide{{end}}"> <div class="has-tls inline field {{if not .HasTLS}}hide{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.skip_tls_verify"}}</strong></label> <label><strong>{{.locale.Tr "admin.auths.skip_tls_verify"}}</strong></label>
<input name="skip_verify" type="checkbox" {{if .Source.SkipVerify}}checked{{end}}> <input name="skip_verify" type="checkbox" {{if .Source.SkipVerify}}checked{{end}}>
</div> </div>
</div> </div>
{{if .Source.IsLDAP}} {{if .Source.IsLDAP}}
<div class="field"> <div class="field">
<label for="bind_dn">{{.i18n.Tr "admin.auths.bind_dn"}}</label> <label for="bind_dn">{{.locale.Tr "admin.auths.bind_dn"}}</label>
<input id="bind_dn" name="bind_dn" value="{{$cfg.BindDN}}" placeholder="e.g. cn=Search,dc=mydomain,dc=com"> <input id="bind_dn" name="bind_dn" value="{{$cfg.BindDN}}" placeholder="e.g. cn=Search,dc=mydomain,dc=com">
</div> </div>
<div class="field"> <div class="field">
<label for="bind_password">{{.i18n.Tr "admin.auths.bind_password"}}</label> <label for="bind_password">{{.locale.Tr "admin.auths.bind_password"}}</label>
<input id="bind_password" name="bind_password" type="password" value="{{$cfg.BindPassword}}"> <input id="bind_password" name="bind_password" type="password" value="{{$cfg.BindPassword}}">
</div> </div>
{{end}} {{end}}
<div class="{{if .Source.IsLDAP}}required{{end}} field"> <div class="{{if .Source.IsLDAP}}required{{end}} field">
<label for="user_base">{{.i18n.Tr "admin.auths.user_base"}}</label> <label for="user_base">{{.locale.Tr "admin.auths.user_base"}}</label>
<input id="user_base" name="user_base" value="{{$cfg.UserBase}}" placeholder="e.g. ou=Users,dc=mydomain,dc=com" {{if .Source.IsLDAP}}required{{end}}> <input id="user_base" name="user_base" value="{{$cfg.UserBase}}" placeholder="e.g. ou=Users,dc=mydomain,dc=com" {{if .Source.IsLDAP}}required{{end}}>
</div> </div>
{{if .Source.IsDLDAP}} {{if .Source.IsDLDAP}}
<div class="required field"> <div class="required field">
<label for="user_dn">{{.i18n.Tr "admin.auths.user_dn"}}</label> <label for="user_dn">{{.locale.Tr "admin.auths.user_dn"}}</label>
<input id="user_dn" name="user_dn" value="{{$cfg.UserDN}}" placeholder="e.g. uid=%s,ou=Users,dc=mydomain,dc=com" required> <input id="user_dn" name="user_dn" value="{{$cfg.UserDN}}" placeholder="e.g. uid=%s,ou=Users,dc=mydomain,dc=com" required>
</div> </div>
{{end}} {{end}}
<div class="required field"> <div class="required field">
<label for="filter">{{.i18n.Tr "admin.auths.filter"}}</label> <label for="filter">{{.locale.Tr "admin.auths.filter"}}</label>
<input id="filter" name="filter" value="{{$cfg.Filter}}" placeholder="e.g. (&(objectClass=posixAccount)(uid=%s))" required> <input id="filter" name="filter" value="{{$cfg.Filter}}" placeholder="e.g. (&(objectClass=posixAccount)(uid=%s))" required>
</div> </div>
<div class="field"> <div class="field">
<label for="admin_filter">{{.i18n.Tr "admin.auths.admin_filter"}}</label> <label for="admin_filter">{{.locale.Tr "admin.auths.admin_filter"}}</label>
<input id="admin_filter" name="admin_filter" value="{{$cfg.AdminFilter}}"> <input id="admin_filter" name="admin_filter" value="{{$cfg.AdminFilter}}">
</div> </div>
<div class="field"> <div class="field">
<label for="restricted_filter">{{.i18n.Tr "admin.auths.restricted_filter"}}</label> <label for="restricted_filter">{{.locale.Tr "admin.auths.restricted_filter"}}</label>
<input id="restricted_filter" name="restricted_filter" value="{{$cfg.RestrictedFilter}}"> <input id="restricted_filter" name="restricted_filter" value="{{$cfg.RestrictedFilter}}">
<p class="help">{{.i18n.Tr "admin.auths.restricted_filter_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.restricted_filter_helper"}}</p>
</div> </div>
<div class="field"> <div class="field">
<label for="attribute_username">{{.i18n.Tr "admin.auths.attribute_username"}}</label> <label for="attribute_username">{{.locale.Tr "admin.auths.attribute_username"}}</label>
<input id="attribute_username" name="attribute_username" value="{{$cfg.AttributeUsername}}" placeholder="{{.i18n.Tr "admin.auths.attribute_username_placeholder"}}"> <input id="attribute_username" name="attribute_username" value="{{$cfg.AttributeUsername}}" placeholder="{{.locale.Tr "admin.auths.attribute_username_placeholder"}}">
</div> </div>
<div class="field"> <div class="field">
<label for="attribute_name">{{.i18n.Tr "admin.auths.attribute_name"}}</label> <label for="attribute_name">{{.locale.Tr "admin.auths.attribute_name"}}</label>
<input id="attribute_name" name="attribute_name" value="{{$cfg.AttributeName}}"> <input id="attribute_name" name="attribute_name" value="{{$cfg.AttributeName}}">
</div> </div>
<div class="field"> <div class="field">
<label for="attribute_surname">{{.i18n.Tr "admin.auths.attribute_surname"}}</label> <label for="attribute_surname">{{.locale.Tr "admin.auths.attribute_surname"}}</label>
<input id="attribute_surname" name="attribute_surname" value="{{$cfg.AttributeSurname}}"> <input id="attribute_surname" name="attribute_surname" value="{{$cfg.AttributeSurname}}">
</div> </div>
<div class="required field"> <div class="required field">
<label for="attribute_mail">{{.i18n.Tr "admin.auths.attribute_mail"}}</label> <label for="attribute_mail">{{.locale.Tr "admin.auths.attribute_mail"}}</label>
<input id="attribute_mail" name="attribute_mail" value="{{$cfg.AttributeMail}}" placeholder="e.g. mail" required> <input id="attribute_mail" name="attribute_mail" value="{{$cfg.AttributeMail}}" placeholder="e.g. mail" required>
</div> </div>
<div class="field"> <div class="field">
<label for="attribute_ssh_public_key">{{.i18n.Tr "admin.auths.attribute_ssh_public_key"}}</label> <label for="attribute_ssh_public_key">{{.locale.Tr "admin.auths.attribute_ssh_public_key"}}</label>
<input id="attribute_ssh_public_key" name="attribute_ssh_public_key" value="{{$cfg.AttributeSSHPublicKey}}" placeholder="e.g. SshPublicKey"> <input id="attribute_ssh_public_key" name="attribute_ssh_public_key" value="{{$cfg.AttributeSSHPublicKey}}" placeholder="e.g. SshPublicKey">
</div> </div>
<div class="field"> <div class="field">
<label for="attribute_avatar">{{.i18n.Tr "admin.auths.attribute_avatar"}}</label> <label for="attribute_avatar">{{.locale.Tr "admin.auths.attribute_avatar"}}</label>
<input id="attribute_avatar" name="attribute_avatar" value="{{$cfg.AttributeAvatar}}" placeholder="e.g. jpegPhoto"> <input id="attribute_avatar" name="attribute_avatar" value="{{$cfg.AttributeAvatar}}" placeholder="e.g. jpegPhoto">
</div> </div>
@ -113,33 +113,33 @@
<!-- ldap group begin --> <!-- ldap group begin -->
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.enable_ldap_groups"}}</strong></label> <label><strong>{{.locale.Tr "admin.auths.enable_ldap_groups"}}</strong></label>
<input type="checkbox" name="groups_enabled" class="js-ldap-group-toggle" {{if $cfg.GroupsEnabled}}checked{{end}}> <input type="checkbox" name="groups_enabled" class="js-ldap-group-toggle" {{if $cfg.GroupsEnabled}}checked{{end}}>
</div> </div>
</div> </div>
<div id="ldap-group-options" class="ui segment secondary" {{if not $cfg.GroupsEnabled}}hidden{{end}}> <div id="ldap-group-options" class="ui segment secondary" {{if not $cfg.GroupsEnabled}}hidden{{end}}>
<div class="field"> <div class="field">
<label>{{.i18n.Tr "admin.auths.group_search_base"}}</label> <label>{{.locale.Tr "admin.auths.group_search_base"}}</label>
<input name="group_dn" value="{{$cfg.GroupDN}}" placeholder="e.g. ou=group,dc=mydomain,dc=com"> <input name="group_dn" value="{{$cfg.GroupDN}}" placeholder="e.g. ou=group,dc=mydomain,dc=com">
</div> </div>
<div class="field"> <div class="field">
<label>{{.i18n.Tr "admin.auths.group_attribute_list_users"}}</label> <label>{{.locale.Tr "admin.auths.group_attribute_list_users"}}</label>
<input name="group_member_uid" value="{{$cfg.GroupMemberUID}}" placeholder="e.g. memberUid"> <input name="group_member_uid" value="{{$cfg.GroupMemberUID}}" placeholder="e.g. memberUid">
</div> </div>
<div class="field"> <div class="field">
<label>{{.i18n.Tr "admin.auths.user_attribute_in_group"}}</label> <label>{{.locale.Tr "admin.auths.user_attribute_in_group"}}</label>
<input name="user_uid" value="{{$cfg.UserUID}}" placeholder="e.g. uid"> <input name="user_uid" value="{{$cfg.UserUID}}" placeholder="e.g. uid">
</div> </div>
<div class="field"> <div class="field">
<label>{{.i18n.Tr "admin.auths.verify_group_membership"}}</label> <label>{{.locale.Tr "admin.auths.verify_group_membership"}}</label>
<input name="group_filter" value="{{$cfg.GroupFilter}}" placeholder="e.g. (|(cn=gitea_users)(cn=admins))"> <input name="group_filter" value="{{$cfg.GroupFilter}}" placeholder="e.g. (|(cn=gitea_users)(cn=admins))">
</div> </div>
<div class="field"> <div class="field">
<label>{{.i18n.Tr "admin.auths.map_group_to_team"}}</label> <label>{{.locale.Tr "admin.auths.map_group_to_team"}}</label>
<input name="group_team_map" value="{{$cfg.GroupTeamMap}}" placeholder='e.g. {"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}'> <input name="group_team_map" value="{{$cfg.GroupTeamMap}}" placeholder='e.g. {"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}'>
</div> </div>
<div class="ui checkbox"> <div class="ui checkbox">
<label>{{.i18n.Tr "admin.auths.map_group_to_team_removal"}}</label> <label>{{.locale.Tr "admin.auths.map_group_to_team_removal"}}</label>
<input name="group_team_map_removal" type="checkbox" {{if $cfg.GroupTeamMapRemoval}}checked{{end}}> <input name="group_team_map_removal" type="checkbox" {{if $cfg.GroupTeamMapRemoval}}checked{{end}}>
</div> </div>
</div> </div>
@ -148,31 +148,31 @@
{{if .Source.IsLDAP}} {{if .Source.IsLDAP}}
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="use_paged_search"><strong>{{.i18n.Tr "admin.auths.use_paged_search"}}</strong></label> <label for="use_paged_search"><strong>{{.locale.Tr "admin.auths.use_paged_search"}}</strong></label>
<input id="use_paged_search" name="use_paged_search" type="checkbox" {{if $cfg.UsePagedSearch}}checked{{end}}> <input id="use_paged_search" name="use_paged_search" type="checkbox" {{if $cfg.UsePagedSearch}}checked{{end}}>
</div> </div>
</div> </div>
<div class="field required search-page-size{{if not $cfg.UsePagedSearch}} hide{{end}}"> <div class="field required search-page-size{{if not $cfg.UsePagedSearch}} hide{{end}}">
<label for="search_page_size">{{.i18n.Tr "admin.auths.search_page_size"}}</label> <label for="search_page_size">{{.locale.Tr "admin.auths.search_page_size"}}</label>
<input id="search_page_size" name="search_page_size" value="{{if $cfg.UsePagedSearch}}{{$cfg.SearchPageSize}}{{end}}"> <input id="search_page_size" name="search_page_size" value="{{if $cfg.UsePagedSearch}}{{$cfg.SearchPageSize}}{{end}}">
</div> </div>
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.attributes_in_bind"}}</strong></label> <label><strong>{{.locale.Tr "admin.auths.attributes_in_bind"}}</strong></label>
<input name="attributes_in_bind" type="checkbox" {{if $cfg.AttributesInBind}}checked{{end}}> <input name="attributes_in_bind" type="checkbox" {{if $cfg.AttributesInBind}}checked{{end}}>
</div> </div>
</div> </div>
{{end}} {{end}}
<div class="optional field"> <div class="optional field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="skip_local_two_fa"><strong>{{.i18n.Tr "admin.auths.skip_local_two_fa"}}</strong></label> <label for="skip_local_two_fa"><strong>{{.locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label>
<input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}> <input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
</div> </div>
</div> </div>
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="allow_deactivate_all"><strong>{{.i18n.Tr "admin.auths.allow_deactivate_all"}}</strong></label> <label for="allow_deactivate_all"><strong>{{.locale.Tr "admin.auths.allow_deactivate_all"}}</strong></label>
<input id="allow_deactivate_all" name="allow_deactivate_all" type="checkbox" {{if $cfg.AllowDeactivateAll}}checked{{end}}> <input id="allow_deactivate_all" name="allow_deactivate_all" type="checkbox" {{if $cfg.AllowDeactivateAll}}checked{{end}}>
</div> </div>
</div> </div>
@ -182,7 +182,7 @@
{{if .Source.IsSMTP}} {{if .Source.IsSMTP}}
{{ $cfg:=.Source.Cfg }} {{ $cfg:=.Source.Cfg }}
<div class="inline required field"> <div class="inline required field">
<label>{{.i18n.Tr "admin.auths.smtp_auth"}}</label> <label>{{.locale.Tr "admin.auths.smtp_auth"}}</label>
<div class="ui selection type dropdown"> <div class="ui selection type dropdown">
<input type="hidden" id="smtp_auth" name="smtp_auth" value="{{$cfg.Auth}}" required> <input type="hidden" id="smtp_auth" name="smtp_auth" value="{{$cfg.Auth}}" required>
<div class="text">{{$cfg.Auth}}</div> <div class="text">{{$cfg.Auth}}</div>
@ -195,47 +195,47 @@
</div> </div>
</div> </div>
<div class="required field"> <div class="required field">
<label for="smtp_host">{{.i18n.Tr "admin.auths.smtphost"}}</label> <label for="smtp_host">{{.locale.Tr "admin.auths.smtphost"}}</label>
<input id="smtp_host" name="smtp_host" value="{{$cfg.Host}}" required> <input id="smtp_host" name="smtp_host" value="{{$cfg.Host}}" required>
</div> </div>
<div class="required field"> <div class="required field">
<label for="smtp_port">{{.i18n.Tr "admin.auths.smtpport"}}</label> <label for="smtp_port">{{.locale.Tr "admin.auths.smtpport"}}</label>
<input id="smtp_port" name="smtp_port" value="{{$cfg.Port}}" required> <input id="smtp_port" name="smtp_port" value="{{$cfg.Port}}" required>
</div> </div>
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="force_smtps"><strong>{{.i18n.Tr "admin.auths.force_smtps"}}</strong></label> <label for="force_smtps"><strong>{{.locale.Tr "admin.auths.force_smtps"}}</strong></label>
<input id="force_smtps" name="force_smtps" type="checkbox" {{if $cfg.ForceSMTPS}}checked{{end}}> <input id="force_smtps" name="force_smtps" type="checkbox" {{if $cfg.ForceSMTPS}}checked{{end}}>
</div> </div>
<p class="help">{{.i18n.Tr "admin.auths.force_smtps_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.force_smtps_helper"}}</p>
</div> </div>
<div class="has-tls inline field {{if not .HasTLS}}hide{{end}}"> <div class="has-tls inline field {{if not .HasTLS}}hide{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.skip_tls_verify"}}</strong></label> <label><strong>{{.locale.Tr "admin.auths.skip_tls_verify"}}</strong></label>
<input name="skip_verify" type="checkbox" {{if .Source.SkipVerify}}checked{{end}}> <input name="skip_verify" type="checkbox" {{if .Source.SkipVerify}}checked{{end}}>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<label for="helo_hostname">{{.i18n.Tr "admin.auths.helo_hostname"}}</label> <label for="helo_hostname">{{.locale.Tr "admin.auths.helo_hostname"}}</label>
<input id="helo_hostname" name="helo_hostname" value="{{$cfg.HeloHostname}}"> <input id="helo_hostname" name="helo_hostname" value="{{$cfg.HeloHostname}}">
<p class="help">{{.i18n.Tr "admin.auths.helo_hostname_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.helo_hostname_helper"}}</p>
</div> </div>
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="disable_helo"><strong>{{.i18n.Tr "admin.auths.disable_helo"}}</strong></label> <label for="disable_helo"><strong>{{.locale.Tr "admin.auths.disable_helo"}}</strong></label>
<input id="disable_helo" name="disable_helo" type="checkbox" {{if $cfg.DisableHelo}}checked{{end}}> <input id="disable_helo" name="disable_helo" type="checkbox" {{if $cfg.DisableHelo}}checked{{end}}>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<label for="allowed_domains">{{.i18n.Tr "admin.auths.allowed_domains"}}</label> <label for="allowed_domains">{{.locale.Tr "admin.auths.allowed_domains"}}</label>
<input id="allowed_domains" name="allowed_domains" value="{{$cfg.AllowedDomains}}"> <input id="allowed_domains" name="allowed_domains" value="{{$cfg.AllowedDomains}}">
<p class="help">{{.i18n.Tr "admin.auths.allowed_domains_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.allowed_domains_helper"}}</p>
</div> </div>
<div class="optional field"> <div class="optional field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="skip_local_two_fa"><strong>{{.i18n.Tr "admin.auths.skip_local_two_fa"}}</strong></label> <label for="skip_local_two_fa"><strong>{{.locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label>
<input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}> <input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
</div> </div>
</div> </div>
{{end}} {{end}}
@ -244,18 +244,18 @@
{{if .Source.IsPAM}} {{if .Source.IsPAM}}
{{ $cfg:=.Source.Cfg }} {{ $cfg:=.Source.Cfg }}
<div class="required field"> <div class="required field">
<label for="pam_service_name">{{.i18n.Tr "admin.auths.pam_service_name"}}</label> <label for="pam_service_name">{{.locale.Tr "admin.auths.pam_service_name"}}</label>
<input id="pam_service_name" name="pam_service_name" value="{{$cfg.ServiceName}}" required> <input id="pam_service_name" name="pam_service_name" value="{{$cfg.ServiceName}}" required>
</div> </div>
<div class="field"> <div class="field">
<label for="pam_email_domain">{{.i18n.Tr "admin.auths.pam_email_domain"}}</label> <label for="pam_email_domain">{{.locale.Tr "admin.auths.pam_email_domain"}}</label>
<input id="pam_email_domain" name="pam_email_domain" value="{{$cfg.EmailDomain}}"> <input id="pam_email_domain" name="pam_email_domain" value="{{$cfg.EmailDomain}}">
</div> </div>
<div class="optional field"> <div class="optional field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="skip_local_two_fa"><strong>{{.i18n.Tr "admin.auths.skip_local_two_fa"}}</strong></label> <label for="skip_local_two_fa"><strong>{{.locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label>
<input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}> <input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
</div> </div>
</div> </div>
{{end}} {{end}}
@ -264,7 +264,7 @@
{{if .Source.IsOAuth2}} {{if .Source.IsOAuth2}}
{{ $cfg:=.Source.Cfg }} {{ $cfg:=.Source.Cfg }}
<div class="inline required field"> <div class="inline required field">
<label>{{.i18n.Tr "admin.auths.oauth2_provider"}}</label> <label>{{.locale.Tr "admin.auths.oauth2_provider"}}</label>
<div class="ui selection type dropdown"> <div class="ui selection type dropdown">
<input type="hidden" id="oauth2_provider" name="oauth2_provider" value="{{$cfg.Provider}}" required> <input type="hidden" id="oauth2_provider" name="oauth2_provider" value="{{$cfg.Provider}}" required>
<div class="text">{{.CurrentOAuth2Provider.DisplayName}}</div> <div class="text">{{.CurrentOAuth2Provider.DisplayName}}</div>
@ -277,52 +277,52 @@
</div> </div>
</div> </div>
<div class="required field"> <div class="required field">
<label for="oauth2_key">{{.i18n.Tr "admin.auths.oauth2_clientID"}}</label> <label for="oauth2_key">{{.locale.Tr "admin.auths.oauth2_clientID"}}</label>
<input id="oauth2_key" name="oauth2_key" value="{{$cfg.ClientID}}" required> <input id="oauth2_key" name="oauth2_key" value="{{$cfg.ClientID}}" required>
</div> </div>
<div class="required field"> <div class="required field">
<label for="oauth2_secret">{{.i18n.Tr "admin.auths.oauth2_clientSecret"}}</label> <label for="oauth2_secret">{{.locale.Tr "admin.auths.oauth2_clientSecret"}}</label>
<input id="oauth2_secret" name="oauth2_secret" value="{{$cfg.ClientSecret}}" required> <input id="oauth2_secret" name="oauth2_secret" value="{{$cfg.ClientSecret}}" required>
</div> </div>
<div class="optional field"> <div class="optional field">
<label for="oauth2_icon_url">{{.i18n.Tr "admin.auths.oauth2_icon_url"}}</label> <label for="oauth2_icon_url">{{.locale.Tr "admin.auths.oauth2_icon_url"}}</label>
<input id="oauth2_icon_url" name="oauth2_icon_url" value="{{$cfg.IconURL}}"> <input id="oauth2_icon_url" name="oauth2_icon_url" value="{{$cfg.IconURL}}">
</div> </div>
<div class="open_id_connect_auto_discovery_url required field"> <div class="open_id_connect_auto_discovery_url required field">
<label for="open_id_connect_auto_discovery_url">{{.i18n.Tr "admin.auths.openIdConnectAutoDiscoveryURL"}}</label> <label for="open_id_connect_auto_discovery_url">{{.locale.Tr "admin.auths.openIdConnectAutoDiscoveryURL"}}</label>
<input id="open_id_connect_auto_discovery_url" name="open_id_connect_auto_discovery_url" value="{{$cfg.OpenIDConnectAutoDiscoveryURL}}"> <input id="open_id_connect_auto_discovery_url" name="open_id_connect_auto_discovery_url" value="{{$cfg.OpenIDConnectAutoDiscoveryURL}}">
</div> </div>
<div class="optional field"> <div class="optional field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="skip_local_two_fa"><strong>{{.i18n.Tr "admin.auths.skip_local_two_fa"}}</strong></label> <label for="skip_local_two_fa"><strong>{{.locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label>
<input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}> <input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if $cfg.SkipLocalTwoFA}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
</div> </div>
</div> </div>
<div class="oauth2_use_custom_url inline field"> <div class="oauth2_use_custom_url inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.oauth2_use_custom_url"}}</strong></label> <label><strong>{{.locale.Tr "admin.auths.oauth2_use_custom_url"}}</strong></label>
<input id="oauth2_use_custom_url" name="oauth2_use_custom_url" type="checkbox" {{if $cfg.CustomURLMapping}}checked{{end}}> <input id="oauth2_use_custom_url" name="oauth2_use_custom_url" type="checkbox" {{if $cfg.CustomURLMapping}}checked{{end}}>
</div> </div>
</div> </div>
<div class="oauth2_use_custom_url_field oauth2_auth_url required field"> <div class="oauth2_use_custom_url_field oauth2_auth_url required field">
<label for="oauth2_auth_url">{{.i18n.Tr "admin.auths.oauth2_authURL"}}</label> <label for="oauth2_auth_url">{{.locale.Tr "admin.auths.oauth2_authURL"}}</label>
<input id="oauth2_auth_url" name="oauth2_auth_url" value="{{if $cfg.CustomURLMapping}}{{$cfg.CustomURLMapping.AuthURL}}{{end}}"> <input id="oauth2_auth_url" name="oauth2_auth_url" value="{{if $cfg.CustomURLMapping}}{{$cfg.CustomURLMapping.AuthURL}}{{end}}">
</div> </div>
<div class="oauth2_use_custom_url_field oauth2_token_url required field"> <div class="oauth2_use_custom_url_field oauth2_token_url required field">
<label for="oauth2_token_url">{{.i18n.Tr "admin.auths.oauth2_tokenURL"}}</label> <label for="oauth2_token_url">{{.locale.Tr "admin.auths.oauth2_tokenURL"}}</label>
<input id="oauth2_token_url" name="oauth2_token_url" value="{{if $cfg.CustomURLMapping}}{{$cfg.CustomURLMapping.TokenURL}}{{end}}"> <input id="oauth2_token_url" name="oauth2_token_url" value="{{if $cfg.CustomURLMapping}}{{$cfg.CustomURLMapping.TokenURL}}{{end}}">
</div> </div>
<div class="oauth2_use_custom_url_field oauth2_profile_url required field"> <div class="oauth2_use_custom_url_field oauth2_profile_url required field">
<label for="oauth2_profile_url">{{.i18n.Tr "admin.auths.oauth2_profileURL"}}</label> <label for="oauth2_profile_url">{{.locale.Tr "admin.auths.oauth2_profileURL"}}</label>
<input id="oauth2_profile_url" name="oauth2_profile_url" value="{{if $cfg.CustomURLMapping}}{{$cfg.CustomURLMapping.ProfileURL}}{{end}}"> <input id="oauth2_profile_url" name="oauth2_profile_url" value="{{if $cfg.CustomURLMapping}}{{$cfg.CustomURLMapping.ProfileURL}}{{end}}">
</div> </div>
<div class="oauth2_use_custom_url_field oauth2_email_url required field"> <div class="oauth2_use_custom_url_field oauth2_email_url required field">
<label for="oauth2_email_url">{{.i18n.Tr "admin.auths.oauth2_emailURL"}}</label> <label for="oauth2_email_url">{{.locale.Tr "admin.auths.oauth2_emailURL"}}</label>
<input id="oauth2_email_url" name="oauth2_email_url" value="{{if $cfg.CustomURLMapping}}{{$cfg.CustomURLMapping.EmailURL}}{{end}}"> <input id="oauth2_email_url" name="oauth2_email_url" value="{{if $cfg.CustomURLMapping}}{{$cfg.CustomURLMapping.EmailURL}}{{end}}">
</div> </div>
<div class="oauth2_use_custom_url_field oauth2_tenant required field"> <div class="oauth2_use_custom_url_field oauth2_tenant required field">
<label for="oauth2_tenant">{{.i18n.Tr "admin.auths.oauth2_tenant"}}</label> <label for="oauth2_tenant">{{.locale.Tr "admin.auths.oauth2_tenant"}}</label>
<input id="oauth2_tenant" name="oauth2_tenant" value="{{if $cfg.CustomURLMapping}}{{$cfg.CustomURLMapping.Tenant}}{{end}}"> <input id="oauth2_tenant" name="oauth2_tenant" value="{{if $cfg.CustomURLMapping}}{{$cfg.CustomURLMapping.Tenant}}{{end}}">
</div> </div>
@ -336,29 +336,29 @@
{{end}}{{end}} {{end}}{{end}}
<div class="field"> <div class="field">
<label for="oauth2_scopes">{{.i18n.Tr "admin.auths.oauth2_scopes"}}</label> <label for="oauth2_scopes">{{.locale.Tr "admin.auths.oauth2_scopes"}}</label>
<input id="oauth2_scopes" name="oauth2_scopes" value="{{if $cfg.Scopes}}{{Join $cfg.Scopes "," }}{{end}}"> <input id="oauth2_scopes" name="oauth2_scopes" value="{{if $cfg.Scopes}}{{Join $cfg.Scopes "," }}{{end}}">
</div> </div>
<div class="field"> <div class="field">
<label for="oauth2_required_claim_name">{{.i18n.Tr "admin.auths.oauth2_required_claim_name"}}</label> <label for="oauth2_required_claim_name">{{.locale.Tr "admin.auths.oauth2_required_claim_name"}}</label>
<input id="oauth2_required_claim_name" name="oauth2_required_claim_name" values="{{$cfg.RequiredClaimName}}"> <input id="oauth2_required_claim_name" name="oauth2_required_claim_name" values="{{$cfg.RequiredClaimName}}">
<p class="help">{{.i18n.Tr "admin.auths.oauth2_required_claim_name_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.oauth2_required_claim_name_helper"}}</p>
</div> </div>
<div class="field"> <div class="field">
<label for="oauth2_required_claim_value">{{.i18n.Tr "admin.auths.oauth2_required_claim_value"}}</label> <label for="oauth2_required_claim_value">{{.locale.Tr "admin.auths.oauth2_required_claim_value"}}</label>
<input id="oauth2_required_claim_value" name="oauth2_required_claim_value" values="{{$cfg.RequiredClaimValue}}"> <input id="oauth2_required_claim_value" name="oauth2_required_claim_value" values="{{$cfg.RequiredClaimValue}}">
<p class="help">{{.i18n.Tr "admin.auths.oauth2_required_claim_value_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.oauth2_required_claim_value_helper"}}</p>
</div> </div>
<div class="field"> <div class="field">
<label for="oauth2_group_claim_name">{{.i18n.Tr "admin.auths.oauth2_group_claim_name"}}</label> <label for="oauth2_group_claim_name">{{.locale.Tr "admin.auths.oauth2_group_claim_name"}}</label>
<input id="oauth2_group_claim_name" name="oauth2_group_claim_name" value="{{$cfg.GroupClaimName}}"> <input id="oauth2_group_claim_name" name="oauth2_group_claim_name" value="{{$cfg.GroupClaimName}}">
</div> </div>
<div class="field"> <div class="field">
<label for="oauth2_admin_group">{{.i18n.Tr "admin.auths.oauth2_admin_group"}}</label> <label for="oauth2_admin_group">{{.locale.Tr "admin.auths.oauth2_admin_group"}}</label>
<input id="oauth2_admin_group" name="oauth2_admin_group" value="{{$cfg.AdminGroup}}"> <input id="oauth2_admin_group" name="oauth2_admin_group" value="{{$cfg.AdminGroup}}">
</div> </div>
<div class="field"> <div class="field">
<label for="oauth2_restricted_group">{{.i18n.Tr "admin.auths.oauth2_restricted_group"}}</label> <label for="oauth2_restricted_group">{{.locale.Tr "admin.auths.oauth2_restricted_group"}}</label>
<input id="oauth2_restricted_group" name="oauth2_restricted_group" value="{{$cfg.RestrictedGroup}}"> <input id="oauth2_restricted_group" name="oauth2_restricted_group" value="{{$cfg.RestrictedGroup}}">
</div> </div>
{{end}} {{end}}
@ -368,32 +368,32 @@
{{ $cfg:=.Source.Cfg }} {{ $cfg:=.Source.Cfg }}
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="sspi_auto_create_users"><strong>{{.i18n.Tr "admin.auths.sspi_auto_create_users"}}</strong></label> <label for="sspi_auto_create_users"><strong>{{.locale.Tr "admin.auths.sspi_auto_create_users"}}</strong></label>
<input id="sspi_auto_create_users" name="sspi_auto_create_users" class="sspi-auto-create-users" type="checkbox" {{if $cfg.AutoCreateUsers}}checked{{end}}> <input id="sspi_auto_create_users" name="sspi_auto_create_users" class="sspi-auto-create-users" type="checkbox" {{if $cfg.AutoCreateUsers}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.sspi_auto_create_users_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.sspi_auto_create_users_helper"}}</p>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="sspi_auto_activate_users"><strong>{{.i18n.Tr "admin.auths.sspi_auto_activate_users"}}</strong></label> <label for="sspi_auto_activate_users"><strong>{{.locale.Tr "admin.auths.sspi_auto_activate_users"}}</strong></label>
<input id="sspi_auto_activate_users" name="sspi_auto_activate_users" class="sspi-auto-activate-users" type="checkbox" {{if $cfg.AutoActivateUsers}}checked{{end}}> <input id="sspi_auto_activate_users" name="sspi_auto_activate_users" class="sspi-auto-activate-users" type="checkbox" {{if $cfg.AutoActivateUsers}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.sspi_auto_activate_users_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.sspi_auto_activate_users_helper"}}</p>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="sspi_strip_domain_names"><strong>{{.i18n.Tr "admin.auths.sspi_strip_domain_names"}}</strong></label> <label for="sspi_strip_domain_names"><strong>{{.locale.Tr "admin.auths.sspi_strip_domain_names"}}</strong></label>
<input id="sspi_strip_domain_names" name="sspi_strip_domain_names" class="sspi-strip-domain-names" type="checkbox" {{if $cfg.StripDomainNames}}checked{{end}}> <input id="sspi_strip_domain_names" name="sspi_strip_domain_names" class="sspi-strip-domain-names" type="checkbox" {{if $cfg.StripDomainNames}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.sspi_strip_domain_names_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.sspi_strip_domain_names_helper"}}</p>
</div> </div>
</div> </div>
<div class="required field"> <div class="required field">
<label for="sspi_separator_replacement">{{.i18n.Tr "admin.auths.sspi_separator_replacement"}}</label> <label for="sspi_separator_replacement">{{.locale.Tr "admin.auths.sspi_separator_replacement"}}</label>
<input id="sspi_separator_replacement" name="sspi_separator_replacement" value="{{$cfg.SeparatorReplacement}}" required> <input id="sspi_separator_replacement" name="sspi_separator_replacement" value="{{$cfg.SeparatorReplacement}}" required>
<p class="help">{{.i18n.Tr "admin.auths.sspi_separator_replacement_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.sspi_separator_replacement_helper"}}</p>
</div> </div>
<div class="field"> <div class="field">
<label for="sspi_default_language">{{.i18n.Tr "admin.auths.sspi_default_language"}}</label> <label for="sspi_default_language">{{.locale.Tr "admin.auths.sspi_default_language"}}</label>
<div class="ui language selection dropdown" id="sspi_default_language"> <div class="ui language selection dropdown" id="sspi_default_language">
<input name="sspi_default_language" type="hidden" value="{{$cfg.DefaultLanguage}}"> <input name="sspi_default_language" type="hidden" value="{{$cfg.DefaultLanguage}}">
{{svg "octicon-triangle-down" 14 "dropdown icon"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
@ -405,27 +405,27 @@
{{end}} {{end}}
</div> </div>
</div> </div>
<p class="help">{{.i18n.Tr "admin.auths.sspi_default_language_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.sspi_default_language_helper"}}</p>
</div> </div>
{{end}} {{end}}
{{if .Source.IsLDAP}} {{if .Source.IsLDAP}}
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.syncenabled"}}</strong></label> <label><strong>{{.locale.Tr "admin.auths.syncenabled"}}</strong></label>
<input name="is_sync_enabled" type="checkbox" {{if .Source.IsSyncEnabled}}checked{{end}}> <input name="is_sync_enabled" type="checkbox" {{if .Source.IsSyncEnabled}}checked{{end}}>
</div> </div>
</div> </div>
{{end}} {{end}}
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.activated"}}</strong></label> <label><strong>{{.locale.Tr "admin.auths.activated"}}</strong></label>
<input name="is_active" type="checkbox" {{if .Source.IsActive}}checked{{end}}> <input name="is_active" type="checkbox" {{if .Source.IsActive}}checked{{end}}>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<button class="ui green button">{{.i18n.Tr "admin.auths.update"}}</button> <button class="ui green button">{{.locale.Tr "admin.auths.update"}}</button>
<div class="ui red button delete-button" data-url="{{$.Link}}/delete" data-id="{{.Source.ID}}">{{.i18n.Tr "admin.auths.delete"}}</div> <div class="ui red button delete-button" data-url="{{$.Link}}/delete" data-id="{{.Source.ID}}">{{.locale.Tr "admin.auths.delete"}}</div>
</div> </div>
</form> </form>
</div> </div>
@ -435,10 +435,10 @@
<div class="ui small basic delete modal"> <div class="ui small basic delete modal">
<div class="ui icon header"> <div class="ui icon header">
{{svg "octicon-trash"}} {{svg "octicon-trash"}}
{{.i18n.Tr "admin.auths.delete_auth_title"}} {{.locale.Tr "admin.auths.delete_auth_title"}}
</div> </div>
<div class="content"> <div class="content">
<p>{{.i18n.Tr "admin.auths.delete_auth_desc"}}</p> <p>{{.locale.Tr "admin.auths.delete_auth_desc"}}</p>
</div> </div>
{{template "base/delete_modal_actions" .}} {{template "base/delete_modal_actions" .}}
</div> </div>

View file

@ -4,9 +4,9 @@
<div class="ui container"> <div class="ui container">
{{template "base/alert" .}} {{template "base/alert" .}}
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.auths.auth_manage_panel"}} ({{.i18n.Tr "admin.total" .Total}}) {{.locale.Tr "admin.auths.auth_manage_panel"}} ({{.locale.Tr "admin.total" .Total}})
<div class="ui right"> <div class="ui right">
<a class="ui primary tiny button" href="{{AppSubUrl}}/admin/auths/new">{{.i18n.Tr "admin.auths.new"}}</a> <a class="ui primary tiny button" href="{{AppSubUrl}}/admin/auths/new">{{.locale.Tr "admin.auths.new"}}</a>
</div> </div>
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
@ -14,12 +14,12 @@
<thead> <thead>
<tr> <tr>
<th>ID</th> <th>ID</th>
<th>{{.i18n.Tr "admin.auths.name"}}</th> <th>{{.locale.Tr "admin.auths.name"}}</th>
<th>{{.i18n.Tr "admin.auths.type"}}</th> <th>{{.locale.Tr "admin.auths.type"}}</th>
<th>{{.i18n.Tr "admin.auths.enabled"}}</th> <th>{{.locale.Tr "admin.auths.enabled"}}</th>
<th>{{.i18n.Tr "admin.auths.updated"}}</th> <th>{{.locale.Tr "admin.auths.updated"}}</th>
<th>{{.i18n.Tr "admin.users.created"}}</th> <th>{{.locale.Tr "admin.users.created"}}</th>
<th>{{.i18n.Tr "admin.users.edit"}}</th> <th>{{.locale.Tr "admin.users.edit"}}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>

View file

@ -4,7 +4,7 @@
<div class="ui container"> <div class="ui container">
{{template "base/alert" .}} {{template "base/alert" .}}
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.auths.new"}} {{.locale.Tr "admin.auths.new"}}
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<form class="ui form" action="{{.Link}}" method="post"> <form class="ui form" action="{{.Link}}" method="post">
@ -12,7 +12,7 @@
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<!-- Types and name --> <!-- Types and name -->
<div class="inline required field {{if .Err_Type}}error{{end}}"> <div class="inline required field {{if .Err_Type}}error{{end}}">
<label>{{.i18n.Tr "admin.auths.auth_type"}}</label> <label>{{.locale.Tr "admin.auths.auth_type"}}</label>
<div class="ui selection type dropdown"> <div class="ui selection type dropdown">
<input type="hidden" id="auth_type" name="type" value="{{.type}}"> <input type="hidden" id="auth_type" name="type" value="{{.type}}">
<div class="text">{{.CurrentTypeName}}</div> <div class="text">{{.CurrentTypeName}}</div>
@ -25,7 +25,7 @@
</div> </div>
</div> </div>
<div class="required inline field {{if .Err_Name}}error{{end}}"> <div class="required inline field {{if .Err_Name}}error{{end}}">
<label for="name">{{.i18n.Tr "admin.auths.auth_name"}}</label> <label for="name">{{.locale.Tr "admin.auths.auth_name"}}</label>
<input id="name" name="name" value="{{.name}}" autofocus required> <input id="name" name="name" value="{{.name}}" autofocus required>
</div> </div>
@ -37,16 +37,16 @@
<!-- PAM --> <!-- PAM -->
<div class="pam required field {{if not (eq .type 4)}}hide{{end}}"> <div class="pam required field {{if not (eq .type 4)}}hide{{end}}">
<label for="pam_service_name">{{.i18n.Tr "admin.auths.pam_service_name"}}</label> <label for="pam_service_name">{{.locale.Tr "admin.auths.pam_service_name"}}</label>
<input id="pam_service_name" name="pam_service_name" value="{{.pam_service_name}}" /> <input id="pam_service_name" name="pam_service_name" value="{{.pam_service_name}}" />
<label for="pam_email_domain">{{.i18n.Tr "admin.auths.pam_email_domain"}}</label> <label for="pam_email_domain">{{.locale.Tr "admin.auths.pam_email_domain"}}</label>
<input id="pam_email_domain" name="pam_email_domain" value="{{.pam_email_domain}}"> <input id="pam_email_domain" name="pam_email_domain" value="{{.pam_email_domain}}">
</div> </div>
<div class="pam optional field {{if not (eq .type 4)}}hide{{end}}"> <div class="pam optional field {{if not (eq .type 4)}}hide{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="skip_local_two_fa"><strong>{{.i18n.Tr "admin.auths.skip_local_two_fa"}}</strong></label> <label for="skip_local_two_fa"><strong>{{.locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label>
<input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if .skip_local_two_fa}}checked{{end}}> <input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if .skip_local_two_fa}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
</div> </div>
</div> </div>
@ -58,67 +58,67 @@
<div class="ldap field"> <div class="ldap field">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.attributes_in_bind"}}</strong></label> <label><strong>{{.locale.Tr "admin.auths.attributes_in_bind"}}</strong></label>
<input name="attributes_in_bind" type="checkbox" {{if .attributes_in_bind}}checked{{end}}> <input name="attributes_in_bind" type="checkbox" {{if .attributes_in_bind}}checked{{end}}>
</div> </div>
</div> </div>
<div class="ldap inline field {{if not (eq .type 2)}}hide{{end}}"> <div class="ldap inline field {{if not (eq .type 2)}}hide{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.syncenabled"}}</strong></label> <label><strong>{{.locale.Tr "admin.auths.syncenabled"}}</strong></label>
<input name="is_sync_enabled" type="checkbox" {{if .is_sync_enabled}}checked{{end}}> <input name="is_sync_enabled" type="checkbox" {{if .is_sync_enabled}}checked{{end}}>
</div> </div>
</div> </div>
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.activated"}}</strong></label> <label><strong>{{.locale.Tr "admin.auths.activated"}}</strong></label>
<input name="is_active" type="checkbox" {{if .is_active}}checked{{end}}> <input name="is_active" type="checkbox" {{if .is_active}}checked{{end}}>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<button class="ui green button">{{.i18n.Tr "admin.auths.new"}}</button> <button class="ui green button">{{.locale.Tr "admin.auths.new"}}</button>
</div> </div>
</form> </form>
</div> </div>
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.auths.tips"}} {{.locale.Tr "admin.auths.tips"}}
</h4> </h4>
<div class="ui attached segment"> <div class="ui attached segment">
<h5>GMail Settings:</h5> <h5>GMail Settings:</h5>
<p>Host: smtp.gmail.com, Port: 587, Enable TLS Encryption: true</p> <p>Host: smtp.gmail.com, Port: 587, Enable TLS Encryption: true</p>
<h5>{{.i18n.Tr "admin.auths.tips.oauth2.general"}}:</h5> <h5>{{.locale.Tr "admin.auths.tips.oauth2.general"}}:</h5>
<p>{{.i18n.Tr "admin.auths.tips.oauth2.general.tip"}}</p> <p>{{.locale.Tr "admin.auths.tips.oauth2.general.tip"}}</p>
<h5 class="ui top attached header">{{.i18n.Tr "admin.auths.tip.oauth2_provider"}}</h5> <h5 class="ui top attached header">{{.locale.Tr "admin.auths.tip.oauth2_provider"}}</h5>
<div class="ui attached segment"> <div class="ui attached segment">
<li>Bitbucket</li> <li>Bitbucket</li>
<span>{{.i18n.Tr "admin.auths.tip.bitbucket"}}</span> <span>{{.locale.Tr "admin.auths.tip.bitbucket"}}</span>
<li>Dropbox</li> <li>Dropbox</li>
<span>{{.i18n.Tr "admin.auths.tip.dropbox"}}</span> <span>{{.locale.Tr "admin.auths.tip.dropbox"}}</span>
<li>Facebook</li> <li>Facebook</li>
<span>{{.i18n.Tr "admin.auths.tip.facebook"}}</span> <span>{{.locale.Tr "admin.auths.tip.facebook"}}</span>
<li>GitHub</li> <li>GitHub</li>
<span>{{.i18n.Tr "admin.auths.tip.github"}}</span> <span>{{.locale.Tr "admin.auths.tip.github"}}</span>
<li>GitLab</li> <li>GitLab</li>
<span>{{.i18n.Tr "admin.auths.tip.gitlab"}}</span> <span>{{.locale.Tr "admin.auths.tip.gitlab"}}</span>
<li>Google</li> <li>Google</li>
<span>{{.i18n.Tr "admin.auths.tip.google_plus"}}</span> <span>{{.locale.Tr "admin.auths.tip.google_plus"}}</span>
<li>OpenID Connect</li> <li>OpenID Connect</li>
<span>{{.i18n.Tr "admin.auths.tip.openid_connect"}}</span> <span>{{.locale.Tr "admin.auths.tip.openid_connect"}}</span>
<li>Twitter</li> <li>Twitter</li>
<span>{{.i18n.Tr "admin.auths.tip.twitter"}}</span> <span>{{.locale.Tr "admin.auths.tip.twitter"}}</span>
<li>Discord</li> <li>Discord</li>
<span>{{.i18n.Tr "admin.auths.tip.discord"}}</span> <span>{{.locale.Tr "admin.auths.tip.discord"}}</span>
<li>Gitea</li> <li>Gitea</li>
<span>{{.i18n.Tr "admin.auths.tip.gitea"}}</span> <span>{{.locale.Tr "admin.auths.tip.gitea"}}</span>
<li>Nextcloud</li> <li>Nextcloud</li>
<span>{{.i18n.Tr "admin.auths.tip.nextcloud"}}</span> <span>{{.locale.Tr "admin.auths.tip.nextcloud"}}</span>
<li>Yandex</li> <li>Yandex</li>
<span>{{.i18n.Tr "admin.auths.tip.yandex"}}</span> <span>{{.locale.Tr "admin.auths.tip.yandex"}}</span>
<li>Mastodon</li> <li>Mastodon</li>
<span>{{.i18n.Tr "admin.auths.tip.mastodon"}}</span> <span>{{.locale.Tr "admin.auths.tip.mastodon"}}</span>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,6 +1,6 @@
<div class="ldap dldap field {{if not (or (eq .type 2) (eq .type 5))}}hide{{end}}"> <div class="ldap dldap field {{if not (or (eq .type 2) (eq .type 5))}}hide{{end}}">
<div class="inline required field {{if .Err_SecurityProtocol}}error{{end}}"> <div class="inline required field {{if .Err_SecurityProtocol}}error{{end}}">
<label>{{.i18n.Tr "admin.auths.security_protocol"}}</label> <label>{{.locale.Tr "admin.auths.security_protocol"}}</label>
<div class="ui selection security-protocol dropdown"> <div class="ui selection security-protocol dropdown">
<input type="hidden" id="security_protocol" name="security_protocol" value="{{.security_protocol}}"> <input type="hidden" id="security_protocol" name="security_protocol" value="{{.security_protocol}}">
<div class="text">{{.CurrentSecurityProtocol}}</div> <div class="text">{{.CurrentSecurityProtocol}}</div>
@ -13,103 +13,103 @@
</div> </div>
</div> </div>
<div class="required field"> <div class="required field">
<label for="host">{{.i18n.Tr "admin.auths.host"}}</label> <label for="host">{{.locale.Tr "admin.auths.host"}}</label>
<input id="host" name="host" value="{{.host}}" placeholder="e.g. mydomain.com"> <input id="host" name="host" value="{{.host}}" placeholder="e.g. mydomain.com">
</div> </div>
<div class="required field"> <div class="required field">
<label for="port">{{.i18n.Tr "admin.auths.port"}}</label> <label for="port">{{.locale.Tr "admin.auths.port"}}</label>
<input id="port" name="port" value="{{.port}}" placeholder="e.g. 636"> <input id="port" name="port" value="{{.port}}" placeholder="e.g. 636">
</div> </div>
<div class="has-tls inline field {{if not .HasTLS}}hide{{end}}"> <div class="has-tls inline field {{if not .HasTLS}}hide{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.skip_tls_verify"}}</strong></label> <label><strong>{{.locale.Tr "admin.auths.skip_tls_verify"}}</strong></label>
<input name="skip_verify" type="checkbox" {{if .skip_verify}}checked{{end}}> <input name="skip_verify" type="checkbox" {{if .skip_verify}}checked{{end}}>
</div> </div>
</div> </div>
<div class="ldap field {{if not (eq .type 2)}}hide{{end}}"> <div class="ldap field {{if not (eq .type 2)}}hide{{end}}">
<label for="bind_dn">{{.i18n.Tr "admin.auths.bind_dn"}}</label> <label for="bind_dn">{{.locale.Tr "admin.auths.bind_dn"}}</label>
<input id="bind_dn" name="bind_dn" value="{{.bind_dn}}" placeholder="e.g. cn=Search,dc=mydomain,dc=com"> <input id="bind_dn" name="bind_dn" value="{{.bind_dn}}" placeholder="e.g. cn=Search,dc=mydomain,dc=com">
</div> </div>
<div class="ldap field {{if not (eq .type 2)}}hide{{end}}"> <div class="ldap field {{if not (eq .type 2)}}hide{{end}}">
<label for="bind_password">{{.i18n.Tr "admin.auths.bind_password"}}</label> <label for="bind_password">{{.locale.Tr "admin.auths.bind_password"}}</label>
<input id="bind_password" name="bind_password" type="password" autocomplete="off" value="{{.bind_password}}"> <input id="bind_password" name="bind_password" type="password" autocomplete="off" value="{{.bind_password}}">
</div> </div>
<div class="binddnrequired {{if (eq .type 2)}}required{{end}} field"> <div class="binddnrequired {{if (eq .type 2)}}required{{end}} field">
<label for="user_base">{{.i18n.Tr "admin.auths.user_base"}}</label> <label for="user_base">{{.locale.Tr "admin.auths.user_base"}}</label>
<input id="user_base" name="user_base" value="{{.user_base}}" placeholder="e.g. ou=Users,dc=mydomain,dc=com"> <input id="user_base" name="user_base" value="{{.user_base}}" placeholder="e.g. ou=Users,dc=mydomain,dc=com">
</div> </div>
<div class="dldap required field {{if not (eq .type 5)}}hide{{end}}"> <div class="dldap required field {{if not (eq .type 5)}}hide{{end}}">
<label for="user_dn">{{.i18n.Tr "admin.auths.user_dn"}}</label> <label for="user_dn">{{.locale.Tr "admin.auths.user_dn"}}</label>
<input id="user_dn" name="user_dn" value="{{.user_dn}}" placeholder="e.g. uid=%s,ou=Users,dc=mydomain,dc=com"> <input id="user_dn" name="user_dn" value="{{.user_dn}}" placeholder="e.g. uid=%s,ou=Users,dc=mydomain,dc=com">
</div> </div>
<div class="required field"> <div class="required field">
<label for="filter">{{.i18n.Tr "admin.auths.filter"}}</label> <label for="filter">{{.locale.Tr "admin.auths.filter"}}</label>
<input id="filter" name="filter" value="{{.filter}}" placeholder="e.g. (&(objectClass=posixAccount)(uid=%s))"> <input id="filter" name="filter" value="{{.filter}}" placeholder="e.g. (&(objectClass=posixAccount)(uid=%s))">
</div> </div>
<div class="field"> <div class="field">
<label for="admin_filter">{{.i18n.Tr "admin.auths.admin_filter"}}</label> <label for="admin_filter">{{.locale.Tr "admin.auths.admin_filter"}}</label>
<input id="admin_filter" name="admin_filter" value="{{.admin_filter}}"> <input id="admin_filter" name="admin_filter" value="{{.admin_filter}}">
</div> </div>
<div class="field"> <div class="field">
<label for="restricted_filter">{{.i18n.Tr "admin.auths.restricted_filter"}}</label> <label for="restricted_filter">{{.locale.Tr "admin.auths.restricted_filter"}}</label>
<input id="restricted_filter" name="admin_filter" value="{{.restricted_filter}}"> <input id="restricted_filter" name="admin_filter" value="{{.restricted_filter}}">
<p class="help">{{.i18n.Tr "admin.auths.restricted_filter_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.restricted_filter_helper"}}</p>
</div> </div>
<div class="field"> <div class="field">
<label for="attribute_username">{{.i18n.Tr "admin.auths.attribute_username"}}</label> <label for="attribute_username">{{.locale.Tr "admin.auths.attribute_username"}}</label>
<input id="attribute_username" name="attribute_username" value="{{.attribute_username}}" placeholder="{{.i18n.Tr "admin.auths.attribute_username_placeholder"}}"> <input id="attribute_username" name="attribute_username" value="{{.attribute_username}}" placeholder="{{.locale.Tr "admin.auths.attribute_username_placeholder"}}">
</div> </div>
<div class="field"> <div class="field">
<label for="attribute_name">{{.i18n.Tr "admin.auths.attribute_name"}}</label> <label for="attribute_name">{{.locale.Tr "admin.auths.attribute_name"}}</label>
<input id="attribute_name" name="attribute_name" value="{{.attribute_name}}"> <input id="attribute_name" name="attribute_name" value="{{.attribute_name}}">
</div> </div>
<div class="field"> <div class="field">
<label for="attribute_surname">{{.i18n.Tr "admin.auths.attribute_surname"}}</label> <label for="attribute_surname">{{.locale.Tr "admin.auths.attribute_surname"}}</label>
<input id="attribute_surname" name="attribute_surname" value="{{.attribute_surname}}"> <input id="attribute_surname" name="attribute_surname" value="{{.attribute_surname}}">
</div> </div>
<div class="required field"> <div class="required field">
<label for="attribute_mail">{{.i18n.Tr "admin.auths.attribute_mail"}}</label> <label for="attribute_mail">{{.locale.Tr "admin.auths.attribute_mail"}}</label>
<input id="attribute_mail" name="attribute_mail" value="{{.attribute_mail}}" placeholder="e.g. mail"> <input id="attribute_mail" name="attribute_mail" value="{{.attribute_mail}}" placeholder="e.g. mail">
</div> </div>
<div class="field"> <div class="field">
<label for="attribute_ssh_public_key">{{.i18n.Tr "admin.auths.attribute_ssh_public_key"}}</label> <label for="attribute_ssh_public_key">{{.locale.Tr "admin.auths.attribute_ssh_public_key"}}</label>
<input id="attribute_ssh_public_key" name="attribute_ssh_public_key" value="{{.attribute_ssh_public_key}}" placeholder="e.g. SshPublicKey"> <input id="attribute_ssh_public_key" name="attribute_ssh_public_key" value="{{.attribute_ssh_public_key}}" placeholder="e.g. SshPublicKey">
</div> </div>
<div class="field"> <div class="field">
<label for="attribute_avatar">{{.i18n.Tr "admin.auths.attribute_avatar"}}</label> <label for="attribute_avatar">{{.locale.Tr "admin.auths.attribute_avatar"}}</label>
<input id="attribute_avatar" name="attribute_avatar" value="{{.attribute_avatar}}" placeholder="e.g. jpegPhoto"> <input id="attribute_avatar" name="attribute_avatar" value="{{.attribute_avatar}}" placeholder="e.g. jpegPhoto">
</div> </div>
<!-- ldap group begin --> <!-- ldap group begin -->
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.enable_ldap_groups"}}</strong></label> <label><strong>{{.locale.Tr "admin.auths.enable_ldap_groups"}}</strong></label>
<input type="checkbox" name="groups_enabled" class="js-ldap-group-toggle" {{if .groups_enabled}}checked{{end}}> <input type="checkbox" name="groups_enabled" class="js-ldap-group-toggle" {{if .groups_enabled}}checked{{end}}>
</div> </div>
</div> </div>
<div id="ldap-group-options" class="ui segment secondary"> <div id="ldap-group-options" class="ui segment secondary">
<div class="field"> <div class="field">
<label>{{.i18n.Tr "admin.auths.group_search_base"}}</label> <label>{{.locale.Tr "admin.auths.group_search_base"}}</label>
<input name="group_dn" value="{{.group_dn}}" placeholder="e.g. ou=group,dc=mydomain,dc=com"> <input name="group_dn" value="{{.group_dn}}" placeholder="e.g. ou=group,dc=mydomain,dc=com">
</div> </div>
<div class="field"> <div class="field">
<label>{{.i18n.Tr "admin.auths.group_attribute_list_users"}}</label> <label>{{.locale.Tr "admin.auths.group_attribute_list_users"}}</label>
<input name="group_member_uid" value="{{.group_member_uid}}" placeholder="e.g. memberUid"> <input name="group_member_uid" value="{{.group_member_uid}}" placeholder="e.g. memberUid">
</div> </div>
<div class="field"> <div class="field">
<label>{{.i18n.Tr "admin.auths.user_attribute_in_group"}}</label> <label>{{.locale.Tr "admin.auths.user_attribute_in_group"}}</label>
<input name="user_uid" value="{{.user_uid}}" placeholder="e.g. uid"> <input name="user_uid" value="{{.user_uid}}" placeholder="e.g. uid">
</div> </div>
<div class="field"> <div class="field">
<label>{{.i18n.Tr "admin.auths.verify_group_membership"}}</label> <label>{{.locale.Tr "admin.auths.verify_group_membership"}}</label>
<input name="group_filter" value="{{.group_filter}}" placeholder="e.g. (|(cn=gitea_users)(cn=admins))"> <input name="group_filter" value="{{.group_filter}}" placeholder="e.g. (|(cn=gitea_users)(cn=admins))">
</div> </div>
<div class="field"> <div class="field">
<label>{{.i18n.Tr "admin.auths.map_group_to_team"}}</label> <label>{{.locale.Tr "admin.auths.map_group_to_team"}}</label>
<input name="group_team_map" value="{{.group_team_map}}" placeholder='e.g. {"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}'> <input name="group_team_map" value="{{.group_team_map}}" placeholder='e.g. {"cn=my-group,cn=groups,dc=example,dc=org": {"MyGiteaOrganization": ["MyGiteaTeam1", "MyGiteaTeam2"]}}'>
</div> </div>
<div class="ui checkbox"> <div class="ui checkbox">
<label>{{.i18n.Tr "admin.auths.map_group_to_team_removal"}}</label> <label>{{.locale.Tr "admin.auths.map_group_to_team_removal"}}</label>
<input name="group_team_map_removal" type="checkbox" {{if .group_team_map_removal}}checked{{end}}> <input name="group_team_map_removal" type="checkbox" {{if .group_team_map_removal}}checked{{end}}>
</div> </div>
</div> </div>
@ -117,24 +117,24 @@
<div class="ldap inline field {{if not (eq .type 2)}}hide{{end}}"> <div class="ldap inline field {{if not (eq .type 2)}}hide{{end}}">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="use_paged_search"><strong>{{.i18n.Tr "admin.auths.use_paged_search"}}</strong></label> <label for="use_paged_search"><strong>{{.locale.Tr "admin.auths.use_paged_search"}}</strong></label>
<input id="use_paged_search" name="use_paged_search" class="use-paged-search" type="checkbox" {{if .use_paged_search}}checked{{end}}> <input id="use_paged_search" name="use_paged_search" class="use-paged-search" type="checkbox" {{if .use_paged_search}}checked{{end}}>
</div> </div>
</div> </div>
<div class="ldap field search-page-size required {{if or (not (eq .type 2)) (not .use_paged_search)}}hide{{end}}"> <div class="ldap field search-page-size required {{if or (not (eq .type 2)) (not .use_paged_search)}}hide{{end}}">
<label for="search_page_size">{{.i18n.Tr "admin.auths.search_page_size"}}</label> <label for="search_page_size">{{.locale.Tr "admin.auths.search_page_size"}}</label>
<input id="search_page_size" name="search_page_size" value="{{.search_page_size}}"> <input id="search_page_size" name="search_page_size" value="{{.search_page_size}}">
</div> </div>
<div class="optional field"> <div class="optional field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="skip_local_two_fa"><strong>{{.i18n.Tr "admin.auths.skip_local_two_fa"}}</strong></label> <label for="skip_local_two_fa"><strong>{{.locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label>
<input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if .skip_local_two_fa}}checked{{end}}> <input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if .skip_local_two_fa}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
</div> </div>
</div> </div>
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="allow_deactivate_all"><strong>{{.i18n.Tr "admin.auths.allow_deactivate_all"}}</strong></label> <label for="allow_deactivate_all"><strong>{{.locale.Tr "admin.auths.allow_deactivate_all"}}</strong></label>
<input id="allow_deactivate_all" name="allow_deactivate_all" type="checkbox" {{if .allow_deactivate_all}}checked{{end}}> <input id="allow_deactivate_all" name="allow_deactivate_all" type="checkbox" {{if .allow_deactivate_all}}checked{{end}}>
</div> </div>
</div> </div>

View file

@ -1,6 +1,6 @@
<div class="oauth2 field {{if not (eq .type 6)}}hide{{end}}"> <div class="oauth2 field {{if not (eq .type 6)}}hide{{end}}">
<div class="inline required field"> <div class="inline required field">
<label>{{.i18n.Tr "admin.auths.oauth2_provider"}}</label> <label>{{.locale.Tr "admin.auths.oauth2_provider"}}</label>
<div class="ui selection type dropdown"> <div class="ui selection type dropdown">
<input type="hidden" id="oauth2_provider" name="oauth2_provider" value="{{.oauth2_provider}}"> <input type="hidden" id="oauth2_provider" name="oauth2_provider" value="{{.oauth2_provider}}">
<div class="text">{{.oauth2_provider}}</div> <div class="text">{{.oauth2_provider}}</div>
@ -13,53 +13,53 @@
</div> </div>
</div> </div>
<div class="required field"> <div class="required field">
<label for="oauth2_key">{{.i18n.Tr "admin.auths.oauth2_clientID"}}</label> <label for="oauth2_key">{{.locale.Tr "admin.auths.oauth2_clientID"}}</label>
<input id="oauth2_key" name="oauth2_key" value="{{.oauth2_key}}"> <input id="oauth2_key" name="oauth2_key" value="{{.oauth2_key}}">
</div> </div>
<div class="required field"> <div class="required field">
<label for="oauth2_secret">{{.i18n.Tr "admin.auths.oauth2_clientSecret"}}</label> <label for="oauth2_secret">{{.locale.Tr "admin.auths.oauth2_clientSecret"}}</label>
<input id="oauth2_secret" name="oauth2_secret" value="{{.oauth2_secret}}"> <input id="oauth2_secret" name="oauth2_secret" value="{{.oauth2_secret}}">
</div> </div>
<div class="optional field"> <div class="optional field">
<label for="oauth2_icon_url">{{.i18n.Tr "admin.auths.oauth2_icon_url"}}</label> <label for="oauth2_icon_url">{{.locale.Tr "admin.auths.oauth2_icon_url"}}</label>
<input id="oauth2_icon_url" name="oauth2_icon_url" value="{{.oauth2_icon_url}}"> <input id="oauth2_icon_url" name="oauth2_icon_url" value="{{.oauth2_icon_url}}">
</div> </div>
<div class="open_id_connect_auto_discovery_url required field"> <div class="open_id_connect_auto_discovery_url required field">
<label for="open_id_connect_auto_discovery_url">{{.i18n.Tr "admin.auths.openIdConnectAutoDiscoveryURL"}}</label> <label for="open_id_connect_auto_discovery_url">{{.locale.Tr "admin.auths.openIdConnectAutoDiscoveryURL"}}</label>
<input id="open_id_connect_auto_discovery_url" name="open_id_connect_auto_discovery_url" value="{{.open_id_connect_auto_discovery_url}}"> <input id="open_id_connect_auto_discovery_url" name="open_id_connect_auto_discovery_url" value="{{.open_id_connect_auto_discovery_url}}">
</div> </div>
<div class="optional field"> <div class="optional field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="skip_local_two_fa"><strong>{{.i18n.Tr "admin.auths.skip_local_two_fa"}}</strong></label> <label for="skip_local_two_fa"><strong>{{.locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label>
<input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if .skip_local_two_fa}}checked{{end}}> <input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if .skip_local_two_fa}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
</div> </div>
</div> </div>
<div class="oauth2_use_custom_url inline field"> <div class="oauth2_use_custom_url inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.oauth2_use_custom_url"}}</strong></label> <label><strong>{{.locale.Tr "admin.auths.oauth2_use_custom_url"}}</strong></label>
<input id="oauth2_use_custom_url" name="oauth2_use_custom_url" type="checkbox"> <input id="oauth2_use_custom_url" name="oauth2_use_custom_url" type="checkbox">
</div> </div>
</div> </div>
<div class="oauth2_use_custom_url_field oauth2_auth_url required field"> <div class="oauth2_use_custom_url_field oauth2_auth_url required field">
<label for="oauth2_auth_url">{{.i18n.Tr "admin.auths.oauth2_authURL"}}</label> <label for="oauth2_auth_url">{{.locale.Tr "admin.auths.oauth2_authURL"}}</label>
<input id="oauth2_auth_url" name="oauth2_auth_url" value="{{.oauth2_auth_url}}"> <input id="oauth2_auth_url" name="oauth2_auth_url" value="{{.oauth2_auth_url}}">
</div> </div>
<div class="oauth2_use_custom_url_field oauth2_token_url required field"> <div class="oauth2_use_custom_url_field oauth2_token_url required field">
<label for="oauth2_token_url">{{.i18n.Tr "admin.auths.oauth2_tokenURL"}}</label> <label for="oauth2_token_url">{{.locale.Tr "admin.auths.oauth2_tokenURL"}}</label>
<input id="oauth2_token_url" name="oauth2_token_url" value="{{.oauth2_token_url}}"> <input id="oauth2_token_url" name="oauth2_token_url" value="{{.oauth2_token_url}}">
</div> </div>
<div class="oauth2_use_custom_url_field oauth2_profile_url required field"> <div class="oauth2_use_custom_url_field oauth2_profile_url required field">
<label for="oauth2_profile_url">{{.i18n.Tr "admin.auths.oauth2_profileURL"}}</label> <label for="oauth2_profile_url">{{.locale.Tr "admin.auths.oauth2_profileURL"}}</label>
<input id="oauth2_profile_url" name="oauth2_profile_url" value="{{.oauth2_profile_url}}"> <input id="oauth2_profile_url" name="oauth2_profile_url" value="{{.oauth2_profile_url}}">
</div> </div>
<div class="oauth2_use_custom_url_field oauth2_email_url required field"> <div class="oauth2_use_custom_url_field oauth2_email_url required field">
<label for="oauth2_email_url">{{.i18n.Tr "admin.auths.oauth2_emailURL"}}</label> <label for="oauth2_email_url">{{.locale.Tr "admin.auths.oauth2_emailURL"}}</label>
<input id="oauth2_email_url" name="oauth2_email_url" value="{{.oauth2_email_url}}"> <input id="oauth2_email_url" name="oauth2_email_url" value="{{.oauth2_email_url}}">
</div> </div>
<div class="oauth2_use_custom_url_field oauth2_tenant required field"> <div class="oauth2_use_custom_url_field oauth2_tenant required field">
<label for="oauth2_tenant">{{.i18n.Tr "admin.auths.oauth2_tenant"}}</label> <label for="oauth2_tenant">{{.locale.Tr "admin.auths.oauth2_tenant"}}</label>
<input id="oauth2_tenant" name="oauth2_tenant" value="{{.oauth2_tenant}}"> <input id="oauth2_tenant" name="oauth2_tenant" value="{{.oauth2_tenant}}">
</div> </div>
@ -73,29 +73,29 @@
{{end}}{{end}} {{end}}{{end}}
<div class="field"> <div class="field">
<label for="oauth2_scopes">{{.i18n.Tr "admin.auths.oauth2_scopes"}}</label> <label for="oauth2_scopes">{{.locale.Tr "admin.auths.oauth2_scopes"}}</label>
<input id="oauth2_scopes" name="oauth2_scopes" values="{{.oauth2_scopes}}"> <input id="oauth2_scopes" name="oauth2_scopes" values="{{.oauth2_scopes}}">
</div> </div>
<div class="field"> <div class="field">
<label for="oauth2_required_claim_name">{{.i18n.Tr "admin.auths.oauth2_required_claim_name"}}</label> <label for="oauth2_required_claim_name">{{.locale.Tr "admin.auths.oauth2_required_claim_name"}}</label>
<input id="oauth2_required_claim_name" name="oauth2_required_claim_name" values="{{.oauth2_required_claim_name}}"> <input id="oauth2_required_claim_name" name="oauth2_required_claim_name" values="{{.oauth2_required_claim_name}}">
<p class="help">{{.i18n.Tr "admin.auths.oauth2_required_claim_name_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.oauth2_required_claim_name_helper"}}</p>
</div> </div>
<div class="field"> <div class="field">
<label for="oauth2_required_claim_value">{{.i18n.Tr "admin.auths.oauth2_required_claim_value"}}</label> <label for="oauth2_required_claim_value">{{.locale.Tr "admin.auths.oauth2_required_claim_value"}}</label>
<input id="oauth2_required_claim_value" name="oauth2_required_claim_value" values="{{.oauth2_required_claim_value}}"> <input id="oauth2_required_claim_value" name="oauth2_required_claim_value" values="{{.oauth2_required_claim_value}}">
<p class="help">{{.i18n.Tr "admin.auths.oauth2_required_claim_value_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.oauth2_required_claim_value_helper"}}</p>
</div> </div>
<div class="field"> <div class="field">
<label for="oauth2_group_claim_name">{{.i18n.Tr "admin.auths.oauth2_group_claim_name"}}</label> <label for="oauth2_group_claim_name">{{.locale.Tr "admin.auths.oauth2_group_claim_name"}}</label>
<input id="oauth2_group_claim_name" name="oauth2_group_claim_name" value="{{.oauth2_group_claim_name}}"> <input id="oauth2_group_claim_name" name="oauth2_group_claim_name" value="{{.oauth2_group_claim_name}}">
</div> </div>
<div class="field"> <div class="field">
<label for="oauth2_admin_group">{{.i18n.Tr "admin.auths.oauth2_admin_group"}}</label> <label for="oauth2_admin_group">{{.locale.Tr "admin.auths.oauth2_admin_group"}}</label>
<input id="oauth2_admin_group" name="oauth2_admin_group" value="{{.oauth2_group_claim_name}}"> <input id="oauth2_admin_group" name="oauth2_admin_group" value="{{.oauth2_group_claim_name}}">
</div> </div>
<div class="field"> <div class="field">
<label for="oauth2_restricted_group">{{.i18n.Tr "admin.auths.oauth2_restricted_group"}}</label> <label for="oauth2_restricted_group">{{.locale.Tr "admin.auths.oauth2_restricted_group"}}</label>
<input id="oauth2_restricted_group" name="oauth2_restricted_group" value="{{.oauth2_group_claim_name}}"> <input id="oauth2_restricted_group" name="oauth2_restricted_group" value="{{.oauth2_group_claim_name}}">
</div> </div>
</div> </div>

View file

@ -1,6 +1,6 @@
<div class="smtp field {{if not (eq .type 3)}}hide{{end}}"> <div class="smtp field {{if not (eq .type 3)}}hide{{end}}">
<div class="inline required field"> <div class="inline required field">
<label>{{.i18n.Tr "admin.auths.smtp_auth"}}</label> <label>{{.locale.Tr "admin.auths.smtp_auth"}}</label>
<div class="ui selection type dropdown"> <div class="ui selection type dropdown">
<input type="hidden" id="smtp_auth" name="smtp_auth" value="{{.smtp_auth}}"> <input type="hidden" id="smtp_auth" name="smtp_auth" value="{{.smtp_auth}}">
<div class="text">{{.smtp_auth}}</div> <div class="text">{{.smtp_auth}}</div>
@ -13,47 +13,47 @@
</div> </div>
</div> </div>
<div class="required field"> <div class="required field">
<label for="smtp_host">{{.i18n.Tr "admin.auths.smtphost"}}</label> <label for="smtp_host">{{.locale.Tr "admin.auths.smtphost"}}</label>
<input id="smtp_host" name="smtp_host" value="{{.smtp_host}}"> <input id="smtp_host" name="smtp_host" value="{{.smtp_host}}">
</div> </div>
<div class="required field"> <div class="required field">
<label for="smtp_port">{{.i18n.Tr "admin.auths.smtpport"}}</label> <label for="smtp_port">{{.locale.Tr "admin.auths.smtpport"}}</label>
<input id="smtp_port" name="smtp_port" value="{{.smtp_port}}"> <input id="smtp_port" name="smtp_port" value="{{.smtp_port}}">
</div> </div>
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="force_smtps"><strong>{{.i18n.Tr "admin.auths.force_smtps"}}</strong></label> <label for="force_smtps"><strong>{{.locale.Tr "admin.auths.force_smtps"}}</strong></label>
<input id="force_smtps" name="force_smtps" type="checkbox" {{if .force_smtps}}checked{{end}}> <input id="force_smtps" name="force_smtps" type="checkbox" {{if .force_smtps}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.force_smtps_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.force_smtps_helper"}}</p>
</div> </div>
</div> </div>
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label><strong>{{.i18n.Tr "admin.auths.skip_tls_verify"}}</strong></label> <label><strong>{{.locale.Tr "admin.auths.skip_tls_verify"}}</strong></label>
<input name="skip_verify" type="checkbox" {{if .skip_verify}}checked{{end}}> <input name="skip_verify" type="checkbox" {{if .skip_verify}}checked{{end}}>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<label for="helo_hostname">{{.i18n.Tr "admin.auths.helo_hostname"}}</label> <label for="helo_hostname">{{.locale.Tr "admin.auths.helo_hostname"}}</label>
<input id="helo_hostname" name="helo_hostname" value="{{.helo_hostname}}"> <input id="helo_hostname" name="helo_hostname" value="{{.helo_hostname}}">
<p class="help">{{.i18n.Tr "admin.auths.helo_hostname_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.helo_hostname_helper"}}</p>
</div> </div>
<div class="inline field"> <div class="inline field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="disable_helo"><strong>{{.i18n.Tr "admin.auths.disable_helo"}}</strong></label> <label for="disable_helo"><strong>{{.locale.Tr "admin.auths.disable_helo"}}</strong></label>
<input id="disable_helo" name="disable_helo" type="checkbox" {{if .disable_helo}}checked{{end}}> <input id="disable_helo" name="disable_helo" type="checkbox" {{if .disable_helo}}checked{{end}}>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<label for="allowed_domains">{{.i18n.Tr "admin.auths.allowed_domains"}}</label> <label for="allowed_domains">{{.locale.Tr "admin.auths.allowed_domains"}}</label>
<input id="allowed_domains" name="allowed_domains" value="{{.allowed_domains}}"> <input id="allowed_domains" name="allowed_domains" value="{{.allowed_domains}}">
<p class="help">{{.i18n.Tr "admin.auths.allowed_domains_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.allowed_domains_helper"}}</p>
</div> </div>
<div class="optional field"> <div class="optional field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="skip_local_two_fa"><strong>{{.i18n.Tr "admin.auths.skip_local_two_fa"}}</strong></label> <label for="skip_local_two_fa"><strong>{{.locale.Tr "admin.auths.skip_local_two_fa"}}</strong></label>
<input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if .skip_local_two_fa}}checked{{end}}> <input id="skip_local_two_fa" name="skip_local_two_fa" type="checkbox" {{if .skip_local_two_fa}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.skip_local_two_fa_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.skip_local_two_fa_helper"}}</p>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,32 +1,32 @@
<div class="sspi field {{if not (eq .type 7)}}hide{{end}}"> <div class="sspi field {{if not (eq .type 7)}}hide{{end}}">
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="sspi_auto_create_users"><strong>{{.i18n.Tr "admin.auths.sspi_auto_create_users"}}</strong></label> <label for="sspi_auto_create_users"><strong>{{.locale.Tr "admin.auths.sspi_auto_create_users"}}</strong></label>
<input id="sspi_auto_create_users" name="sspi_auto_create_users" class="sspi-auto-create-users" type="checkbox" {{if .SSPIAutoCreateUsers}}checked{{end}}> <input id="sspi_auto_create_users" name="sspi_auto_create_users" class="sspi-auto-create-users" type="checkbox" {{if .SSPIAutoCreateUsers}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.sspi_auto_create_users_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.sspi_auto_create_users_helper"}}</p>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="sspi_auto_activate_users"><strong>{{.i18n.Tr "admin.auths.sspi_auto_activate_users"}}</strong></label> <label for="sspi_auto_activate_users"><strong>{{.locale.Tr "admin.auths.sspi_auto_activate_users"}}</strong></label>
<input id="sspi_auto_activate_users" name="sspi_auto_activate_users" class="sspi-auto-activate-users" type="checkbox" {{if .SSPIAutoActivateUsers}}checked{{end}}> <input id="sspi_auto_activate_users" name="sspi_auto_activate_users" class="sspi-auto-activate-users" type="checkbox" {{if .SSPIAutoActivateUsers}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.sspi_auto_activate_users_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.sspi_auto_activate_users_helper"}}</p>
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<div class="ui checkbox"> <div class="ui checkbox">
<label for="sspi_strip_domain_names"><strong>{{.i18n.Tr "admin.auths.sspi_strip_domain_names"}}</strong></label> <label for="sspi_strip_domain_names"><strong>{{.locale.Tr "admin.auths.sspi_strip_domain_names"}}</strong></label>
<input id="sspi_strip_domain_names" name="sspi_strip_domain_names" class="sspi-strip-domain-names" type="checkbox" {{if .SSPIStripDomainNames}}checked{{end}}> <input id="sspi_strip_domain_names" name="sspi_strip_domain_names" class="sspi-strip-domain-names" type="checkbox" {{if .SSPIStripDomainNames}}checked{{end}}>
<p class="help">{{.i18n.Tr "admin.auths.sspi_strip_domain_names_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.sspi_strip_domain_names_helper"}}</p>
</div> </div>
</div> </div>
<div class="required field"> <div class="required field">
<label for="sspi_separator_replacement">{{.i18n.Tr "admin.auths.sspi_separator_replacement"}}</label> <label for="sspi_separator_replacement">{{.locale.Tr "admin.auths.sspi_separator_replacement"}}</label>
<input id="sspi_separator_replacement" name="sspi_separator_replacement" value="{{.SSPISeparatorReplacement}}"> <input id="sspi_separator_replacement" name="sspi_separator_replacement" value="{{.SSPISeparatorReplacement}}">
<p class="help">{{.i18n.Tr "admin.auths.sspi_separator_replacement_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.sspi_separator_replacement_helper"}}</p>
</div> </div>
<div class="field"> <div class="field">
<label for="sspi_default_language">{{.i18n.Tr "admin.auths.sspi_default_language"}}</label> <label for="sspi_default_language">{{.locale.Tr "admin.auths.sspi_default_language"}}</label>
<div class="ui language selection dropdown" id="sspi_default_language"> <div class="ui language selection dropdown" id="sspi_default_language">
<input name="sspi_default_language" type="hidden" value="{{.SSPIDefaultLanguage}}"> <input name="sspi_default_language" type="hidden" value="{{.SSPIDefaultLanguage}}">
{{svg "octicon-triangle-down" 14 "dropdown icon"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
@ -38,6 +38,6 @@
{{end}} {{end}}
</div> </div>
</div> </div>
<p class="help">{{.i18n.Tr "admin.auths.sspi_default_language_helper"}}</p> <p class="help">{{.locale.Tr "admin.auths.sspi_default_language_helper"}}</p>
</div> </div>
</div> </div>

View file

@ -2,22 +2,22 @@
<!-- Sort --> <!-- Sort -->
<div class="ui dropdown type jump item"> <div class="ui dropdown type jump item">
<span class="text"> <span class="text">
{{.i18n.Tr "repo.issues.filter_sort"}} {{.locale.Tr "repo.issues.filter_sort"}}
{{svg "octicon-triangle-down" 14 "dropdown icon"}} {{svg "octicon-triangle-down" 14 "dropdown icon"}}
</span> </span>
<div class="menu"> <div class="menu">
<a class="{{if or (eq .SortType "oldest") (not .SortType)}}active{{end}} item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.oldest"}}</a> <a class="{{if or (eq .SortType "oldest") (not .SortType)}}active{{end}} item" href="{{$.Link}}?sort=oldest&q={{$.Keyword}}">{{.locale.Tr "repo.issues.filter_sort.oldest"}}</a>
<a class="{{if eq .SortType "newest"}}active{{end}} item" href="{{$.Link}}?sort=newest&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.latest"}}</a> <a class="{{if eq .SortType "newest"}}active{{end}} item" href="{{$.Link}}?sort=newest&q={{$.Keyword}}">{{.locale.Tr "repo.issues.filter_sort.latest"}}</a>
<a class="{{if eq .SortType "alphabetically"}}active{{end}} item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.label.filter_sort.alphabetically"}}</a> <a class="{{if eq .SortType "alphabetically"}}active{{end}} item" href="{{$.Link}}?sort=alphabetically&q={{$.Keyword}}">{{.locale.Tr "repo.issues.label.filter_sort.alphabetically"}}</a>
<a class="{{if eq .SortType "reversealphabetically"}}active{{end}} item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a> <a class="{{if eq .SortType "reversealphabetically"}}active{{end}} item" href="{{$.Link}}?sort=reversealphabetically&q={{$.Keyword}}">{{.locale.Tr "repo.issues.label.filter_sort.reverse_alphabetically"}}</a>
<a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.recentupdate"}}</a> <a class="{{if eq .SortType "recentupdate"}}active{{end}} item" href="{{$.Link}}?sort=recentupdate&q={{$.Keyword}}">{{.locale.Tr "repo.issues.filter_sort.recentupdate"}}</a>
<a class="{{if eq .SortType "leastupdate"}}active{{end}} item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}">{{.i18n.Tr "repo.issues.filter_sort.leastupdate"}}</a> <a class="{{if eq .SortType "leastupdate"}}active{{end}} item" href="{{$.Link}}?sort=leastupdate&q={{$.Keyword}}">{{.locale.Tr "repo.issues.filter_sort.leastupdate"}}</a>
</div> </div>
</div> </div>
</div> </div>
<form class="ui form ignore-dirty" style="max-width: 90%;"> <form class="ui form ignore-dirty" style="max-width: 90%;">
<div class="ui fluid action input"> <div class="ui fluid action input">
<input name="q" value="{{.Keyword}}" placeholder="{{.i18n.Tr "explore.search"}}..." autofocus> <input name="q" value="{{.Keyword}}" placeholder="{{.locale.Tr "explore.search"}}..." autofocus>
<button class="ui primary button">{{.i18n.Tr "explore.search"}}</button> <button class="ui primary button">{{.locale.Tr "explore.search"}}</button>
</div> </div>
</form> </form>

View file

@ -4,50 +4,50 @@
<div class="ui container"> <div class="ui container">
{{template "base/alert" .}} {{template "base/alert" .}}
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.config.server_config"}} {{.locale.Tr "admin.config.server_config"}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal"> <dl class="dl-horizontal admin-dl-horizontal">
<dt>{{.i18n.Tr "admin.config.app_name"}}</dt> <dt>{{.locale.Tr "admin.config.app_name"}}</dt>
<dd>{{AppName}}</dd> <dd>{{AppName}}</dd>
<dt>{{.i18n.Tr "admin.config.app_ver"}}</dt> <dt>{{.locale.Tr "admin.config.app_ver"}}</dt>
<dd>{{AppVer}}{{AppBuiltWith}}</dd> <dd>{{AppVer}}{{AppBuiltWith}}</dd>
<dt>{{.i18n.Tr "admin.config.custom_conf"}}</dt> <dt>{{.locale.Tr "admin.config.custom_conf"}}</dt>
<dd>{{.CustomConf}}</dd> <dd>{{.CustomConf}}</dd>
<dt>{{.i18n.Tr "admin.config.app_url"}}</dt> <dt>{{.locale.Tr "admin.config.app_url"}}</dt>
<dd>{{.AppUrl}}</dd> <dd>{{.AppUrl}}</dd>
<dt>{{.i18n.Tr "admin.config.domain"}}</dt> <dt>{{.locale.Tr "admin.config.domain"}}</dt>
<dd>{{.Domain}}</dd> <dd>{{.Domain}}</dd>
<dt>{{.i18n.Tr "admin.config.offline_mode"}}</dt> <dt>{{.locale.Tr "admin.config.offline_mode"}}</dt>
<dd>{{if .OfflineMode}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .OfflineMode}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.disable_router_log"}}</dt> <dt>{{.locale.Tr "admin.config.disable_router_log"}}</dt>
<dd>{{if .DisableRouterLog}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .DisableRouterLog}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<div class="ui divider"></div> <div class="ui divider"></div>
<dt>{{.i18n.Tr "admin.config.run_user"}}</dt> <dt>{{.locale.Tr "admin.config.run_user"}}</dt>
<dd>{{.RunUser}}</dd> <dd>{{.RunUser}}</dd>
<dt>{{.i18n.Tr "admin.config.run_mode"}}</dt> <dt>{{.locale.Tr "admin.config.run_mode"}}</dt>
<dd>{{.RunMode}}</dd> <dd>{{.RunMode}}</dd>
<div class="ui divider"></div> <div class="ui divider"></div>
<dt>{{.i18n.Tr "admin.config.git_version"}}</dt> <dt>{{.locale.Tr "admin.config.git_version"}}</dt>
<dd>{{.GitVersion}}</dd> <dd>{{.GitVersion}}</dd>
<div class="ui divider"></div> <div class="ui divider"></div>
<dt>{{.i18n.Tr "admin.config.repo_root_path"}}</dt> <dt>{{.locale.Tr "admin.config.repo_root_path"}}</dt>
<dd>{{.RepoRootPath}}</dd> <dd>{{.RepoRootPath}}</dd>
<dt>{{.i18n.Tr "admin.config.static_file_root_path"}}</dt> <dt>{{.locale.Tr "admin.config.static_file_root_path"}}</dt>
<dd>{{.StaticRootPath}}</dd> <dd>{{.StaticRootPath}}</dd>
<dt>{{.i18n.Tr "admin.config.custom_file_root_path"}}</dt> <dt>{{.locale.Tr "admin.config.custom_file_root_path"}}</dt>
<dd>{{.CustomRootPath}}</dd> <dd>{{.CustomRootPath}}</dd>
<dt>{{.i18n.Tr "admin.config.log_file_root_path"}}</dt> <dt>{{.locale.Tr "admin.config.log_file_root_path"}}</dt>
<dd>{{.LogRootPath}}</dd> <dd>{{.LogRootPath}}</dd>
<dt>{{.i18n.Tr "admin.config.script_type"}}</dt> <dt>{{.locale.Tr "admin.config.script_type"}}</dt>
<dd>{{.ScriptType}}</dd> <dd>{{.ScriptType}}</dd>
<dt>{{.i18n.Tr "admin.config.reverse_auth_user"}}</dt> <dt>{{.locale.Tr "admin.config.reverse_auth_user"}}</dt>
<dd>{{.ReverseProxyAuthUser}}</dd> <dd>{{.ReverseProxyAuthUser}}</dd>
{{if .EnvVars }} {{if .EnvVars }}
@ -62,33 +62,33 @@
</div> </div>
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.config.ssh_config"}} {{.locale.Tr "admin.config.ssh_config"}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal"> <dl class="dl-horizontal admin-dl-horizontal">
<dt>{{.i18n.Tr "admin.config.ssh_enabled"}}</dt> <dt>{{.locale.Tr "admin.config.ssh_enabled"}}</dt>
<dd>{{if not .SSH.Disabled}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if not .SSH.Disabled}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
{{if not .SSH.Disabled}} {{if not .SSH.Disabled}}
<dt>{{.i18n.Tr "admin.config.ssh_start_builtin_server"}}</dt> <dt>{{.locale.Tr "admin.config.ssh_start_builtin_server"}}</dt>
<dd>{{if .SSH.StartBuiltinServer}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .SSH.StartBuiltinServer}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.ssh_domain"}}</dt> <dt>{{.locale.Tr "admin.config.ssh_domain"}}</dt>
<dd>{{.SSH.Domain}}</dd> <dd>{{.SSH.Domain}}</dd>
<dt>{{.i18n.Tr "admin.config.ssh_port"}}</dt> <dt>{{.locale.Tr "admin.config.ssh_port"}}</dt>
<dd>{{.SSH.Port}}</dd> <dd>{{.SSH.Port}}</dd>
<dt>{{.i18n.Tr "admin.config.ssh_listen_port"}}</dt> <dt>{{.locale.Tr "admin.config.ssh_listen_port"}}</dt>
<dd>{{.SSH.ListenPort}}</dd> <dd>{{.SSH.ListenPort}}</dd>
{{if not .SSH.StartBuiltinServer}} {{if not .SSH.StartBuiltinServer}}
<dt>{{.i18n.Tr "admin.config.ssh_root_path"}}</dt> <dt>{{.locale.Tr "admin.config.ssh_root_path"}}</dt>
<dd>{{.SSH.RootPath}}</dd> <dd>{{.SSH.RootPath}}</dd>
<dt>{{.i18n.Tr "admin.config.ssh_key_test_path"}}</dt> <dt>{{.locale.Tr "admin.config.ssh_key_test_path"}}</dt>
<dd>{{.SSH.KeyTestPath}}</dd> <dd>{{.SSH.KeyTestPath}}</dd>
<dt>{{.i18n.Tr "admin.config.ssh_keygen_path"}}</dt> <dt>{{.locale.Tr "admin.config.ssh_keygen_path"}}</dt>
<dd>{{.SSH.KeygenPath}}</dd> <dd>{{.SSH.KeygenPath}}</dd>
<dt>{{.i18n.Tr "admin.config.ssh_minimum_key_size_check"}}</dt> <dt>{{.locale.Tr "admin.config.ssh_minimum_key_size_check"}}</dt>
<dd>{{if .SSH.MinimumKeySizeCheck}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .SSH.MinimumKeySizeCheck}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
{{if .SSH.MinimumKeySizeCheck}} {{if .SSH.MinimumKeySizeCheck}}
<dt>{{.i18n.Tr "admin.config.ssh_minimum_key_sizes"}}</dt> <dt>{{.locale.Tr "admin.config.ssh_minimum_key_sizes"}}</dt>
<dd>{{.SSH.MinimumKeySizes}}</dd> <dd>{{.SSH.MinimumKeySizes}}</dd>
{{end}} {{end}}
{{end}} {{end}}
@ -97,304 +97,304 @@
</div> </div>
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.config.lfs_config"}} {{.locale.Tr "admin.config.lfs_config"}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal"> <dl class="dl-horizontal admin-dl-horizontal">
<dt>{{.i18n.Tr "admin.config.lfs_enabled"}}</dt> <dt>{{.locale.Tr "admin.config.lfs_enabled"}}</dt>
<dd>{{if .LFS.StartServer}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .LFS.StartServer}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
{{if .LFS.StartServer}} {{if .LFS.StartServer}}
<dt>{{.i18n.Tr "admin.config.lfs_content_path"}}</dt> <dt>{{.locale.Tr "admin.config.lfs_content_path"}}</dt>
<dd>{{.LFS.Path}}</dd> <dd>{{.LFS.Path}}</dd>
<dt>{{.i18n.Tr "admin.config.lfs_http_auth_expiry"}}</dt> <dt>{{.locale.Tr "admin.config.lfs_http_auth_expiry"}}</dt>
<dd>{{.LFS.HTTPAuthExpiry}}</dd> <dd>{{.LFS.HTTPAuthExpiry}}</dd>
{{end}} {{end}}
</dl> </dl>
</div> </div>
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.config.db_config"}} {{.locale.Tr "admin.config.db_config"}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal"> <dl class="dl-horizontal admin-dl-horizontal">
<dt>{{.i18n.Tr "admin.config.db_type"}}</dt> <dt>{{.locale.Tr "admin.config.db_type"}}</dt>
<dd>{{.DbCfg.Type}}</dd> <dd>{{.DbCfg.Type}}</dd>
{{if not (eq .DbCfg.Type "sqlite3")}} {{if not (eq .DbCfg.Type "sqlite3")}}
<dt>{{.i18n.Tr "admin.config.db_host"}}</dt> <dt>{{.locale.Tr "admin.config.db_host"}}</dt>
<dd>{{if .DbCfg.Host}}{{.DbCfg.Host}}{{else}}-{{end}}</dd> <dd>{{if .DbCfg.Host}}{{.DbCfg.Host}}{{else}}-{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.db_name"}}</dt> <dt>{{.locale.Tr "admin.config.db_name"}}</dt>
<dd>{{if .DbCfg.Name}}{{.DbCfg.Name}}{{else}}-{{end}}</dd> <dd>{{if .DbCfg.Name}}{{.DbCfg.Name}}{{else}}-{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.db_user"}}</dt> <dt>{{.locale.Tr "admin.config.db_user"}}</dt>
<dd>{{if .DbCfg.User}}{{.DbCfg.User}}{{else}}-{{end}}</dd> <dd>{{if .DbCfg.User}}{{.DbCfg.User}}{{else}}-{{end}}</dd>
{{end}} {{end}}
{{if eq .DbCfg.Type "postgres"}} {{if eq .DbCfg.Type "postgres"}}
<dt>{{.i18n.Tr "admin.config.db_schema"}}</dt> <dt>{{.locale.Tr "admin.config.db_schema"}}</dt>
<dd>{{if .DbCfg.Schema}}{{.DbCfg.Schema}}{{else}}-{{end}}</dd> <dd>{{if .DbCfg.Schema}}{{.DbCfg.Schema}}{{else}}-{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.db_ssl_mode"}}</dt> <dt>{{.locale.Tr "admin.config.db_ssl_mode"}}</dt>
<dd>{{if .DbCfg.SSLMode}}{{.DbCfg.SSLMode}}{{else}}-{{end}}</dd> <dd>{{if .DbCfg.SSLMode}}{{.DbCfg.SSLMode}}{{else}}-{{end}}</dd>
{{end}} {{end}}
{{if eq .DbCfg.Type "sqlite3"}} {{if eq .DbCfg.Type "sqlite3"}}
<dt>{{.i18n.Tr "admin.config.db_path"}}</dt> <dt>{{.locale.Tr "admin.config.db_path"}}</dt>
<dd>{{if .DbCfg.Path}}{{.DbCfg.Path}}{{else}}-{{end}}</dd> <dd>{{if .DbCfg.Path}}{{.DbCfg.Path}}{{else}}-{{end}}</dd>
{{end}} {{end}}
</dl> </dl>
</div> </div>
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.config.service_config"}} {{.locale.Tr "admin.config.service_config"}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal"> <dl class="dl-horizontal admin-dl-horizontal">
<dt>{{.i18n.Tr "admin.config.register_email_confirm"}}</dt> <dt>{{.locale.Tr "admin.config.register_email_confirm"}}</dt>
<dd>{{if .Service.RegisterEmailConfirm}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.RegisterEmailConfirm}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.disable_register"}}</dt> <dt>{{.locale.Tr "admin.config.disable_register"}}</dt>
<dd>{{if .Service.DisableRegistration}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.DisableRegistration}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.allow_only_internal_registration"}}</dt> <dt>{{.locale.Tr "admin.config.allow_only_internal_registration"}}</dt>
<dd>{{if .Service.AllowOnlyInternalRegistration}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.AllowOnlyInternalRegistration}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.allow_only_external_registration"}}</dt> <dt>{{.locale.Tr "admin.config.allow_only_external_registration"}}</dt>
<dd>{{if .Service.AllowOnlyExternalRegistration}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.AllowOnlyExternalRegistration}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.show_registration_button"}}</dt> <dt>{{.locale.Tr "admin.config.show_registration_button"}}</dt>
<dd>{{if .Service.ShowRegistrationButton}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.ShowRegistrationButton}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.enable_openid_signup"}}</dt> <dt>{{.locale.Tr "admin.config.enable_openid_signup"}}</dt>
<dd>{{if .Service.EnableOpenIDSignUp}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.EnableOpenIDSignUp}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.enable_openid_signin"}}</dt> <dt>{{.locale.Tr "admin.config.enable_openid_signin"}}</dt>
<dd>{{if .Service.EnableOpenIDSignIn}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.EnableOpenIDSignIn}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.require_sign_in_view"}}</dt> <dt>{{.locale.Tr "admin.config.require_sign_in_view"}}</dt>
<dd>{{if .Service.RequireSignInView}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.RequireSignInView}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.mail_notify"}}</dt> <dt>{{.locale.Tr "admin.config.mail_notify"}}</dt>
<dd>{{if .Service.EnableNotifyMail}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.EnableNotifyMail}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.disable_key_size_check"}}</dt> <dt>{{.locale.Tr "admin.config.disable_key_size_check"}}</dt>
<dd>{{if .SSH.MinimumKeySizeCheck}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .SSH.MinimumKeySizeCheck}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.enable_captcha"}}</dt> <dt>{{.locale.Tr "admin.config.enable_captcha"}}</dt>
<dd>{{if .Service.EnableCaptcha}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.EnableCaptcha}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.default_keep_email_private"}}</dt> <dt>{{.locale.Tr "admin.config.default_keep_email_private"}}</dt>
<dd>{{if .Service.DefaultKeepEmailPrivate}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.DefaultKeepEmailPrivate}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.default_allow_create_organization"}}</dt> <dt>{{.locale.Tr "admin.config.default_allow_create_organization"}}</dt>
<dd>{{if .Service.DefaultAllowCreateOrganization}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.DefaultAllowCreateOrganization}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.enable_timetracking"}}</dt> <dt>{{.locale.Tr "admin.config.enable_timetracking"}}</dt>
<dd>{{if .Service.EnableTimetracking}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.EnableTimetracking}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
{{if .Service.EnableTimetracking}} {{if .Service.EnableTimetracking}}
<dt>{{.i18n.Tr "admin.config.default_enable_timetracking"}}</dt> <dt>{{.locale.Tr "admin.config.default_enable_timetracking"}}</dt>
<dd>{{if .Service.DefaultEnableTimetracking}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.DefaultEnableTimetracking}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.default_allow_only_contributors_to_track_time"}}</dt> <dt>{{.locale.Tr "admin.config.default_allow_only_contributors_to_track_time"}}</dt>
<dd>{{if .Service.DefaultAllowOnlyContributorsToTrackTime}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.DefaultAllowOnlyContributorsToTrackTime}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
{{end}} {{end}}
<dt>{{.i18n.Tr "admin.config.default_visibility_organization"}}</dt> <dt>{{.locale.Tr "admin.config.default_visibility_organization"}}</dt>
<dd>{{.Service.DefaultOrgVisibility}}</dd> <dd>{{.Service.DefaultOrgVisibility}}</dd>
<dt>{{.i18n.Tr "admin.config.no_reply_address"}}</dt> <dt>{{.locale.Tr "admin.config.no_reply_address"}}</dt>
<dd>{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}</dd> <dd>{{if .Service.NoReplyAddress}}{{.Service.NoReplyAddress}}{{else}}-{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.default_enable_dependencies"}}</dt> <dt>{{.locale.Tr "admin.config.default_enable_dependencies"}}</dt>
<dd>{{if .Service.DefaultEnableDependencies}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Service.DefaultEnableDependencies}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<div class="ui divider"></div> <div class="ui divider"></div>
<dt>{{.i18n.Tr "admin.config.active_code_lives"}}</dt> <dt>{{.locale.Tr "admin.config.active_code_lives"}}</dt>
<dd>{{.Service.ActiveCodeLives}} {{.i18n.Tr "tool.raw_minutes"}}</dd> <dd>{{.Service.ActiveCodeLives}} {{.locale.Tr "tool.raw_minutes"}}</dd>
<dt>{{.i18n.Tr "admin.config.reset_password_code_lives"}}</dt> <dt>{{.locale.Tr "admin.config.reset_password_code_lives"}}</dt>
<dd>{{.Service.ResetPwdCodeLives}} {{.i18n.Tr "tool.raw_minutes"}}</dd> <dd>{{.Service.ResetPwdCodeLives}} {{.locale.Tr "tool.raw_minutes"}}</dd>
</dl> </dl>
</div> </div>
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.config.webhook_config"}} {{.locale.Tr "admin.config.webhook_config"}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal"> <dl class="dl-horizontal admin-dl-horizontal">
<dt>{{.i18n.Tr "admin.config.queue_length"}}</dt> <dt>{{.locale.Tr "admin.config.queue_length"}}</dt>
<dd>{{.Webhook.QueueLength}}</dd> <dd>{{.Webhook.QueueLength}}</dd>
<dt>{{.i18n.Tr "admin.config.deliver_timeout"}}</dt> <dt>{{.locale.Tr "admin.config.deliver_timeout"}}</dt>
<dd>{{.Webhook.DeliverTimeout}} {{.i18n.Tr "tool.raw_seconds"}}</dd> <dd>{{.Webhook.DeliverTimeout}} {{.locale.Tr "tool.raw_seconds"}}</dd>
<dt>{{.i18n.Tr "admin.config.skip_tls_verify"}}</dt> <dt>{{.locale.Tr "admin.config.skip_tls_verify"}}</dt>
<dd>{{if .Webhook.SkipTLSVerify}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Webhook.SkipTLSVerify}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
</dl> </dl>
</div> </div>
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.config.mailer_config"}} {{.locale.Tr "admin.config.mailer_config"}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal"> <dl class="dl-horizontal admin-dl-horizontal">
<dt>{{.i18n.Tr "admin.config.mailer_enabled"}}</dt> <dt>{{.locale.Tr "admin.config.mailer_enabled"}}</dt>
<dd>{{if .MailerEnabled}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .MailerEnabled}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
{{if .MailerEnabled}} {{if .MailerEnabled}}
<dt>{{.i18n.Tr "admin.config.mailer_name"}}</dt> <dt>{{.locale.Tr "admin.config.mailer_name"}}</dt>
<dd>{{.Mailer.Name}}</dd> <dd>{{.Mailer.Name}}</dd>
{{if eq .Mailer.MailerType "smtp"}} {{if eq .Mailer.MailerType "smtp"}}
<dt>{{.i18n.Tr "admin.config.mailer_disable_helo"}}</dt> <dt>{{.locale.Tr "admin.config.mailer_disable_helo"}}</dt>
<dd>{{if .DisableHelo}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .DisableHelo}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.mailer_host"}}</dt> <dt>{{.locale.Tr "admin.config.mailer_host"}}</dt>
<dd>{{.Mailer.Host}}</dd> <dd>{{.Mailer.Host}}</dd>
{{else if eq .Mailer.MailerType "sendmail"}} {{else if eq .Mailer.MailerType "sendmail"}}
<dt>{{.i18n.Tr "admin.config.mailer_use_sendmail"}}</dt> <dt>{{.locale.Tr "admin.config.mailer_use_sendmail"}}</dt>
<dd>{{svg "octicon-check"}}</dd> <dd>{{svg "octicon-check"}}</dd>
<dt>{{.i18n.Tr "admin.config.mailer_sendmail_path"}}</dt> <dt>{{.locale.Tr "admin.config.mailer_sendmail_path"}}</dt>
<dd>{{.Mailer.SendmailPath}}</dd> <dd>{{.Mailer.SendmailPath}}</dd>
<dt>{{.i18n.Tr "admin.config.mailer_sendmail_args"}}</dt> <dt>{{.locale.Tr "admin.config.mailer_sendmail_args"}}</dt>
<dd>{{.Mailer.SendmailArgs}}</dd> <dd>{{.Mailer.SendmailArgs}}</dd>
<dt>{{.i18n.Tr "admin.config.mailer_sendmail_timeout"}}</dt> <dt>{{.locale.Tr "admin.config.mailer_sendmail_timeout"}}</dt>
<dd>{{.Mailer.SendmailTimeout}} {{.i18n.Tr "tool.raw_seconds"}}</dd> <dd>{{.Mailer.SendmailTimeout}} {{.locale.Tr "tool.raw_seconds"}}</dd>
{{end}} {{end}}
<dt>{{.i18n.Tr "admin.config.mailer_user"}}</dt> <dt>{{.locale.Tr "admin.config.mailer_user"}}</dt>
<dd>{{if .Mailer.User}}{{.Mailer.User}}{{else}}(empty){{end}}</dd><br> <dd>{{if .Mailer.User}}{{.Mailer.User}}{{else}}(empty){{end}}</dd><br>
<form class="ui form ignore-dirty" action="{{AppSubUrl}}/admin/config/test_mail" method="post"> <form class="ui form ignore-dirty" action="{{AppSubUrl}}/admin/config/test_mail" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="inline field ui left"> <div class="inline field ui left">
<div class="ui input"> <div class="ui input">
<input type="email" name="email" placeholder="{{.i18n.Tr "admin.config.test_email_placeholder"}}" size="29" required> <input type="email" name="email" placeholder="{{.locale.Tr "admin.config.test_email_placeholder"}}" size="29" required>
</div> </div>
</div> </div>
<button class="ui green button" id="test-mail-btn">{{.i18n.Tr "admin.config.send_test_mail"}}</button> <button class="ui green button" id="test-mail-btn">{{.locale.Tr "admin.config.send_test_mail"}}</button>
</form> </form>
{{end}} {{end}}
</dl> </dl>
</div> </div>
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.config.cache_config"}} {{.locale.Tr "admin.config.cache_config"}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal"> <dl class="dl-horizontal admin-dl-horizontal">
<dt>{{.i18n.Tr "admin.config.cache_adapter"}}</dt> <dt>{{.locale.Tr "admin.config.cache_adapter"}}</dt>
<dd>{{.CacheAdapter}}</dd> <dd>{{.CacheAdapter}}</dd>
{{if eq .CacheAdapter "memory"}} {{if eq .CacheAdapter "memory"}}
<dt>{{.i18n.Tr "admin.config.cache_interval"}}</dt> <dt>{{.locale.Tr "admin.config.cache_interval"}}</dt>
<dd>{{.CacheInterval}} {{.i18n.Tr "tool.raw_seconds"}}</dd> <dd>{{.CacheInterval}} {{.locale.Tr "tool.raw_seconds"}}</dd>
{{end}} {{end}}
{{if .CacheConn}} {{if .CacheConn}}
<dt>{{.i18n.Tr "admin.config.cache_conn"}}</dt> <dt>{{.locale.Tr "admin.config.cache_conn"}}</dt>
<dd><code>{{.CacheConn}}</code></dd> <dd><code>{{.CacheConn}}</code></dd>
<dt>{{.i18n.Tr "admin.config.cache_item_ttl"}}</dt> <dt>{{.locale.Tr "admin.config.cache_item_ttl"}}</dt>
<dd><code>{{.CacheItemTTL}}</code></dd> <dd><code>{{.CacheItemTTL}}</code></dd>
{{end}} {{end}}
</dl> </dl>
</div> </div>
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.config.session_config"}} {{.locale.Tr "admin.config.session_config"}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal"> <dl class="dl-horizontal admin-dl-horizontal">
<dt>{{.i18n.Tr "admin.config.session_provider"}}</dt> <dt>{{.locale.Tr "admin.config.session_provider"}}</dt>
<dd>{{.SessionConfig.Provider}}</dd> <dd>{{.SessionConfig.Provider}}</dd>
<dt>{{.i18n.Tr "admin.config.provider_config"}}</dt> <dt>{{.locale.Tr "admin.config.provider_config"}}</dt>
<dd><code>{{if .SessionConfig.ProviderConfig}}{{.SessionConfig.ProviderConfig}}{{else}}-{{end}}</code></dd> <dd><code>{{if .SessionConfig.ProviderConfig}}{{.SessionConfig.ProviderConfig}}{{else}}-{{end}}</code></dd>
<dt>{{.i18n.Tr "admin.config.cookie_name"}}</dt> <dt>{{.locale.Tr "admin.config.cookie_name"}}</dt>
<dd>{{.SessionConfig.CookieName}}</dd> <dd>{{.SessionConfig.CookieName}}</dd>
<dt>{{.i18n.Tr "admin.config.gc_interval_time"}}</dt> <dt>{{.locale.Tr "admin.config.gc_interval_time"}}</dt>
<dd>{{.SessionConfig.Gclifetime}} {{.i18n.Tr "tool.raw_seconds"}}</dd> <dd>{{.SessionConfig.Gclifetime}} {{.locale.Tr "tool.raw_seconds"}}</dd>
<dt>{{.i18n.Tr "admin.config.session_life_time"}}</dt> <dt>{{.locale.Tr "admin.config.session_life_time"}}</dt>
<dd>{{.SessionConfig.Maxlifetime}} {{.i18n.Tr "tool.raw_seconds"}}</dd> <dd>{{.SessionConfig.Maxlifetime}} {{.locale.Tr "tool.raw_seconds"}}</dd>
<dt>{{.i18n.Tr "admin.config.https_only"}}</dt> <dt>{{.locale.Tr "admin.config.https_only"}}</dt>
<dd>{{if .SessionConfig.Secure}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .SessionConfig.Secure}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
</dl> </dl>
</div> </div>
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.config.picture_config"}} {{.locale.Tr "admin.config.picture_config"}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal"> <dl class="dl-horizontal admin-dl-horizontal">
<dt>{{.i18n.Tr "admin.config.disable_gravatar"}}</dt> <dt>{{.locale.Tr "admin.config.disable_gravatar"}}</dt>
<dd>{{if .DisableGravatar}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .DisableGravatar}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<div class="ui divider"></div> <div class="ui divider"></div>
<dt>{{.i18n.Tr "admin.config.enable_federated_avatar"}}</dt> <dt>{{.locale.Tr "admin.config.enable_federated_avatar"}}</dt>
<dd>{{if .EnableFederatedAvatar}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .EnableFederatedAvatar}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
</dl> </dl>
</div> </div>
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.config.git_config"}} {{.locale.Tr "admin.config.git_config"}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal"> <dl class="dl-horizontal admin-dl-horizontal">
<dt>{{.i18n.Tr "admin.config.git_disable_diff_highlight"}}</dt> <dt>{{.locale.Tr "admin.config.git_disable_diff_highlight"}}</dt>
<dd>{{if .Git.DisableDiffHighlight}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if .Git.DisableDiffHighlight}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
<dt>{{.i18n.Tr "admin.config.git_max_diff_lines"}}</dt> <dt>{{.locale.Tr "admin.config.git_max_diff_lines"}}</dt>
<dd>{{.Git.MaxGitDiffLines}}</dd> <dd>{{.Git.MaxGitDiffLines}}</dd>
<dt>{{.i18n.Tr "admin.config.git_max_diff_line_characters"}}</dt> <dt>{{.locale.Tr "admin.config.git_max_diff_line_characters"}}</dt>
<dd>{{.Git.MaxGitDiffLineCharacters}}</dd> <dd>{{.Git.MaxGitDiffLineCharacters}}</dd>
<dt>{{.i18n.Tr "admin.config.git_max_diff_files"}}</dt> <dt>{{.locale.Tr "admin.config.git_max_diff_files"}}</dt>
<dd>{{.Git.MaxGitDiffFiles}}</dd> <dd>{{.Git.MaxGitDiffFiles}}</dd>
<dt>{{.i18n.Tr "admin.config.git_gc_args"}}</dt> <dt>{{.locale.Tr "admin.config.git_gc_args"}}</dt>
<dd><code>{{.Git.GCArgs}}</code></dd> <dd><code>{{.Git.GCArgs}}</code></dd>
<div class="ui divider"></div> <div class="ui divider"></div>
<dt>{{.i18n.Tr "admin.config.git_migrate_timeout"}}</dt> <dt>{{.locale.Tr "admin.config.git_migrate_timeout"}}</dt>
<dd>{{.Git.Timeout.Migrate}} {{.i18n.Tr "tool.raw_seconds"}}</dd> <dd>{{.Git.Timeout.Migrate}} {{.locale.Tr "tool.raw_seconds"}}</dd>
<dt>{{.i18n.Tr "admin.config.git_mirror_timeout"}}</dt> <dt>{{.locale.Tr "admin.config.git_mirror_timeout"}}</dt>
<dd>{{.Git.Timeout.Mirror}} {{.i18n.Tr "tool.raw_seconds"}}</dd> <dd>{{.Git.Timeout.Mirror}} {{.locale.Tr "tool.raw_seconds"}}</dd>
<dt>{{.i18n.Tr "admin.config.git_clone_timeout"}}</dt> <dt>{{.locale.Tr "admin.config.git_clone_timeout"}}</dt>
<dd>{{.Git.Timeout.Clone}} {{.i18n.Tr "tool.raw_seconds"}}</dd> <dd>{{.Git.Timeout.Clone}} {{.locale.Tr "tool.raw_seconds"}}</dd>
<dt>{{.i18n.Tr "admin.config.git_pull_timeout"}}</dt> <dt>{{.locale.Tr "admin.config.git_pull_timeout"}}</dt>
<dd>{{.Git.Timeout.Pull}} {{.i18n.Tr "tool.raw_seconds"}}</dd> <dd>{{.Git.Timeout.Pull}} {{.locale.Tr "tool.raw_seconds"}}</dd>
<dt>{{.i18n.Tr "admin.config.git_gc_timeout"}}</dt> <dt>{{.locale.Tr "admin.config.git_gc_timeout"}}</dt>
<dd>{{.Git.Timeout.GC}} {{.i18n.Tr "tool.raw_seconds"}}</dd> <dd>{{.Git.Timeout.GC}} {{.locale.Tr "tool.raw_seconds"}}</dd>
</dl> </dl>
</div> </div>
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.config.log_config"}} {{.locale.Tr "admin.config.log_config"}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal"> <dl class="dl-horizontal admin-dl-horizontal">
{{range .Loggers.default.SubLogDescriptions}} {{range .Loggers.default.SubLogDescriptions}}
<dt>{{$.i18n.Tr "admin.config.log_mode"}}</dt> <dt>{{$.locale.Tr "admin.config.log_mode"}}</dt>
<dd>{{.Name}} ({{.Provider}})</dd> <dd>{{.Name}} ({{.Provider}})</dd>
<dt>{{$.i18n.Tr "admin.config.log_config"}}</dt> <dt>{{$.locale.Tr "admin.config.log_config"}}</dt>
<dd><pre>{{.Config | JsonPrettyPrint}}</pre></dd> <dd><pre>{{.Config | JsonPrettyPrint}}</pre></dd>
{{end}} {{end}}
<div class="ui divider"></div> <div class="ui divider"></div>
<dt>{{$.i18n.Tr "admin.config.router_log_mode"}}</dt> <dt>{{$.locale.Tr "admin.config.router_log_mode"}}</dt>
{{if .DisableRouterLog}} {{if .DisableRouterLog}}
<dd>{{$.i18n.Tr "admin.config.disabled_logger"}}</dd> <dd>{{$.locale.Tr "admin.config.disabled_logger"}}</dd>
{{else}} {{else}}
{{if .Loggers.router.SubLogDescriptions}} {{if .Loggers.router.SubLogDescriptions}}
<dd>{{$.i18n.Tr "admin.config.own_named_logger"}}</dd> <dd>{{$.locale.Tr "admin.config.own_named_logger"}}</dd>
{{range .Loggers.router.SubLogDescriptions}} {{range .Loggers.router.SubLogDescriptions}}
<dt>{{$.i18n.Tr "admin.config.log_mode"}}</dt> <dt>{{$.locale.Tr "admin.config.log_mode"}}</dt>
<dd>{{.Name}} ({{.Provider}})</dd> <dd>{{.Name}} ({{.Provider}})</dd>
<dt>{{$.i18n.Tr "admin.config.log_config"}}</dt> <dt>{{$.locale.Tr "admin.config.log_config"}}</dt>
<dd><pre>{{.Config | JsonPrettyPrint}}</pre></dd> <dd><pre>{{.Config | JsonPrettyPrint}}</pre></dd>
{{end}} {{end}}
{{else}} {{else}}
<dd>{{$.i18n.Tr "admin.config.routes_to_default_logger"}}</dd> <dd>{{$.locale.Tr "admin.config.routes_to_default_logger"}}</dd>
{{end}} {{end}}
{{end}} {{end}}
<div class="ui divider"></div> <div class="ui divider"></div>
<dt>{{$.i18n.Tr "admin.config.access_log_mode"}}</dt> <dt>{{$.locale.Tr "admin.config.access_log_mode"}}</dt>
{{if .EnableAccessLog}} {{if .EnableAccessLog}}
{{if .Loggers.access.SubLogDescriptions}} {{if .Loggers.access.SubLogDescriptions}}
<dd>{{$.i18n.Tr "admin.config.own_named_logger"}}</dd> <dd>{{$.locale.Tr "admin.config.own_named_logger"}}</dd>
{{range .Loggers.access.SubLogDescriptions}} {{range .Loggers.access.SubLogDescriptions}}
<dt>{{$.i18n.Tr "admin.config.log_mode"}}</dt> <dt>{{$.locale.Tr "admin.config.log_mode"}}</dt>
<dd>{{.Name}} ({{.Provider}})</dd> <dd>{{.Name}} ({{.Provider}})</dd>
<dt>{{$.i18n.Tr "admin.config.log_config"}}</dt> <dt>{{$.locale.Tr "admin.config.log_config"}}</dt>
<dd><pre>{{.Config | JsonPrettyPrint}}</pre></dd> <dd><pre>{{.Config | JsonPrettyPrint}}</pre></dd>
{{end}} {{end}}
{{else}} {{else}}
<dd>{{$.i18n.Tr "admin.config.routes_to_default_logger"}}</dd> <dd>{{$.locale.Tr "admin.config.routes_to_default_logger"}}</dd>
{{end}} {{end}}
<dt>{{$.i18n.Tr "admin.config.access_log_template"}}</dt> <dt>{{$.locale.Tr "admin.config.access_log_template"}}</dt>
<dd><code>{{$.AccessLogTemplate}}</code></dd> <dd><code>{{$.AccessLogTemplate}}</code></dd>
{{else}} {{else}}
<dd>{{$.i18n.Tr "admin.config.disabled_logger"}}</dd> <dd>{{$.locale.Tr "admin.config.disabled_logger"}}</dd>
{{end}} {{end}}
<div class="ui divider"></div> <div class="ui divider"></div>
<dt>{{$.i18n.Tr "admin.config.xorm_log_mode"}}</dt> <dt>{{$.locale.Tr "admin.config.xorm_log_mode"}}</dt>
{{if .EnableXORMLog}} {{if .EnableXORMLog}}
{{if .Loggers.xorm.SubLogDescriptions}} {{if .Loggers.xorm.SubLogDescriptions}}
<dd>{{$.i18n.Tr "admin.config.own_named_logger"}}</dd> <dd>{{$.locale.Tr "admin.config.own_named_logger"}}</dd>
{{range .Loggers.xorm.SubLogDescriptions}} {{range .Loggers.xorm.SubLogDescriptions}}
<dt>{{$.i18n.Tr "admin.config.log_mode"}}</dt> <dt>{{$.locale.Tr "admin.config.log_mode"}}</dt>
<dd>{{.Name}} ({{.Provider}})</dd> <dd>{{.Name}} ({{.Provider}})</dd>
<dt>{{$.i18n.Tr "admin.config.log_config"}}</dt> <dt>{{$.locale.Tr "admin.config.log_config"}}</dt>
<dd><pre>{{.Config | JsonPrettyPrint}}</pre></dd> <dd><pre>{{.Config | JsonPrettyPrint}}</pre></dd>
{{end}} {{end}}
{{else}} {{else}}
<dd>{{$.i18n.Tr "admin.config.routes_to_default_logger"}}</dd> <dd>{{$.locale.Tr "admin.config.routes_to_default_logger"}}</dd>
{{end}} {{end}}
<dt>{{$.i18n.Tr "admin.config.xorm_log_sql"}}</dt> <dt>{{$.locale.Tr "admin.config.xorm_log_sql"}}</dt>
<dd>{{if $.LogSQL}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>{{if $.LogSQL}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
{{else}} {{else}}
<dd>{{$.i18n.Tr "admin.config.disabled_logger"}}</dd> <dd>{{$.locale.Tr "admin.config.disabled_logger"}}</dd>
{{end}} {{end}}
</dl> </dl>
</div> </div>

View file

@ -1,5 +1,5 @@
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{.i18n.Tr "admin.monitor.cron"}} {{.locale.Tr "admin.monitor.cron"}}
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<form method="post" action="{{AppSubUrl}}/admin"> <form method="post" action="{{AppSubUrl}}/admin">
@ -7,24 +7,24 @@
<thead> <thead>
<tr> <tr>
<th></th> <th></th>
<th>{{.i18n.Tr "admin.monitor.name"}}</th> <th>{{.locale.Tr "admin.monitor.name"}}</th>
<th>{{.i18n.Tr "admin.monitor.schedule"}}</th> <th>{{.locale.Tr "admin.monitor.schedule"}}</th>
<th>{{.i18n.Tr "admin.monitor.next"}}</th> <th>{{.locale.Tr "admin.monitor.next"}}</th>
<th>{{.i18n.Tr "admin.monitor.previous"}}</th> <th>{{.locale.Tr "admin.monitor.previous"}}</th>
<th>{{.i18n.Tr "admin.monitor.execute_times"}}</th> <th>{{.locale.Tr "admin.monitor.execute_times"}}</th>
<th>{{.i18n.Tr "admin.monitor.last_execution_result"}}</th> <th>{{.locale.Tr "admin.monitor.last_execution_result"}}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{range .Entries}} {{range .Entries}}
<tr> <tr>
<td><button type="submit" class="ui green button" name="op" value="{{.Name}}" title="{{$.i18n.Tr "admin.dashboard.operation_run"}}">{{svg "octicon-triangle-right"}}</button></td> <td><button type="submit" class="ui green button" name="op" value="{{.Name}}" title="{{$.locale.Tr "admin.dashboard.operation_run"}}">{{svg "octicon-triangle-right"}}</button></td>
<td>{{$.i18n.Tr (printf "admin.dashboard.%s" .Name)}}</td> <td>{{$.locale.Tr (printf "admin.dashboard.%s" .Name)}}</td>
<td>{{.Spec}}</td> <td>{{.Spec}}</td>
<td>{{DateFmtLong .Next}}</td> <td>{{DateFmtLong .Next}}</td>
<td>{{if gt .Prev.Year 1 }}{{DateFmtLong .Prev}}{{else}}N/A{{end}}</td> <td>{{if gt .Prev.Year 1 }}{{DateFmtLong .Prev}}{{else}}N/A{{end}}</td>
<td>{{.ExecTimes}}</td> <td>{{.ExecTimes}}</td>
<td {{if ne .Status ""}}class="tooltip" data-content="{{.FormatLastMessage $.i18n.Language}}"{{end}} >{{if eq .Status "" }}{{else if eq .Status "finished"}}{{svg "octicon-check" 16}}{{else}}{{svg "octicon-x" 16}}{{end}}</td> <td {{if ne .Status ""}}class="tooltip" data-content="{{.FormatLastMessage $.locale}}"{{end}} >{{if eq .Status "" }}{{else if eq .Status "finished"}}{{svg "octicon-check" 16}}{{else}}{{svg "octicon-x" 16}}{{end}}</td>
</tr> </tr>
{{end}} {{end}}
</tbody> </tbody>

Some files were not shown because too many files have changed in this diff Show more