aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLunny Xiao <xiaolunwen@gmail.com>2022-10-17 07:29:26 +0800
committerGitHub <noreply@github.com>2022-10-17 07:29:26 +0800
commitf860a6d2e4177ed4f4c2a58a07882bd00a1a52ad (patch)
tree93abb2f354576e50c87d70b0b4bb46369fb3a1f1
parent5d3dbffa150d832d2f9aedd9f90ca91178a95f9c (diff)
downloadgitea-f860a6d2e4177ed4f4c2a58a07882bd00a1a52ad.tar.gz
gitea-f860a6d2e4177ed4f4c2a58a07882bd00a1a52ad.zip
Add system setting table with cache and also add cache supports for user setting (#18058)
-rw-r--r--contrib/pr/checkout.go7
-rw-r--r--models/admin/notice_test.go117
-rw-r--r--models/avatars/avatar.go15
-rw-r--r--models/avatars/avatar_test.go32
-rw-r--r--models/avatars/main_test.go (renamed from models/admin/main_test.go)2
-rw-r--r--models/fixtures/system_setting.yml15
-rw-r--r--models/issues/issue.go4
-rw-r--r--models/main_test.go2
-rw-r--r--models/migrations/migrations.go2
-rw-r--r--models/migrations/v227.go64
-rw-r--r--models/repo.go23
-rw-r--r--models/system/appstate.go (renamed from models/appstate/appstate.go)2
-rw-r--r--models/system/main_test.go21
-rw-r--r--models/system/notice.go (renamed from models/admin/notice.go)2
-rw-r--r--models/system/notice_test.go117
-rw-r--r--models/system/setting.go261
-rw-r--r--models/system/setting_key.go11
-rw-r--r--models/system/setting_test.go53
-rw-r--r--models/unittest/testdb.go11
-rw-r--r--models/user/avatar.go9
-rw-r--r--models/user/setting.go28
-rw-r--r--modules/repository/commits_test.go12
-rw-r--r--modules/setting/picture.go60
-rw-r--r--modules/setting/setting.go7
-rw-r--r--modules/system/appstate.go (renamed from modules/appstate/appstate.go)2
-rw-r--r--modules/system/appstate_test.go (renamed from modules/appstate/appstate_test.go)2
-rw-r--r--modules/system/db.go (renamed from modules/appstate/db.go)8
-rw-r--r--modules/system/item_runtime.go (renamed from modules/appstate/item_runtime.go)2
-rw-r--r--modules/system/setting.go46
-rw-r--r--modules/system/user_setting.go34
-rw-r--r--modules/templates/helper.go4
-rw-r--r--modules/updatechecker/update_checker.go6
-rw-r--r--options/locale/locale_en-US.ini3
-rw-r--r--routers/init.go12
-rw-r--r--routers/install/install.go23
-rw-r--r--routers/web/admin/admin.go169
-rw-r--r--routers/web/admin/config.go217
-rw-r--r--routers/web/admin/notice.go10
-rw-r--r--routers/web/repo/middlewares.go4
-rw-r--r--routers/web/web.go9
-rw-r--r--services/cron/tasks.go6
-rw-r--r--services/cron/tasks_extended.go4
-rw-r--r--services/issue/issue.go4
-rw-r--r--services/migrations/common.go4
-rw-r--r--services/migrations/migrate.go4
-rw-r--r--services/mirror/mirror_pull.go12
-rw-r--r--services/repository/check.go14
-rw-r--r--services/repository/repository.go6
-rw-r--r--services/user/user.go6
-rw-r--r--services/wiki/wiki.go4
-rw-r--r--templates/admin/config.tmpl12
-rw-r--r--web_src/js/features/admin/common.js (renamed from web_src/js/features/admin-common.js)0
-rw-r--r--web_src/js/features/admin/config.js37
-rw-r--r--web_src/js/features/admin/emails.js (renamed from web_src/js/features/admin-emails.js)0
-rw-r--r--web_src/js/features/admin/users.js (renamed from web_src/js/features/admin-users.js)0
-rw-r--r--web_src/js/index.js8
-rw-r--r--web_src/less/_base.less1
-rw-r--r--web_src/less/_form.less2
-rw-r--r--web_src/less/themes/theme-arc-green.less1
59 files changed, 1117 insertions, 436 deletions
diff --git a/contrib/pr/checkout.go b/contrib/pr/checkout.go
index 85476afd01..09510ac2c5 100644
--- a/contrib/pr/checkout.go
+++ b/contrib/pr/checkout.go
@@ -14,7 +14,6 @@ import (
"fmt"
"log"
"net/http"
- "net/url"
"os"
"os/exec"
"os/user"
@@ -62,11 +61,7 @@ func runPR() {
}
setting.AppWorkPath = curDir
setting.StaticRootPath = curDir
- setting.GravatarSourceURL, err = url.Parse("https://secure.gravatar.com/avatar/")
- if err != nil {
- log.Fatalf("url.Parse: %v\n", err)
- }
-
+ setting.GravatarSource = "https://secure.gravatar.com/avatar/"
setting.AppURL = "http://localhost:8080/"
setting.HTTPPort = "8080"
setting.SSH.Domain = "localhost"
diff --git a/models/admin/notice_test.go b/models/admin/notice_test.go
deleted file mode 100644
index f3767392d1..0000000000
--- a/models/admin/notice_test.go
+++ /dev/null
@@ -1,117 +0,0 @@
-// Copyright 2017 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 admin_test
-
-import (
- "testing"
-
- "code.gitea.io/gitea/models/admin"
- "code.gitea.io/gitea/models/db"
- "code.gitea.io/gitea/models/unittest"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestNotice_TrStr(t *testing.T) {
- notice := &admin.Notice{
- Type: admin.NoticeRepository,
- Description: "test description",
- }
- assert.Equal(t, "admin.notices.type_1", notice.TrStr())
-}
-
-func TestCreateNotice(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- noticeBean := &admin.Notice{
- Type: admin.NoticeRepository,
- Description: "test description",
- }
- unittest.AssertNotExistsBean(t, noticeBean)
- assert.NoError(t, admin.CreateNotice(db.DefaultContext, noticeBean.Type, noticeBean.Description))
- unittest.AssertExistsAndLoadBean(t, noticeBean)
-}
-
-func TestCreateRepositoryNotice(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- noticeBean := &admin.Notice{
- Type: admin.NoticeRepository,
- Description: "test description",
- }
- unittest.AssertNotExistsBean(t, noticeBean)
- assert.NoError(t, admin.CreateRepositoryNotice(noticeBean.Description))
- unittest.AssertExistsAndLoadBean(t, noticeBean)
-}
-
-// TODO TestRemoveAllWithNotice
-
-func TestCountNotices(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
- assert.Equal(t, int64(3), admin.CountNotices())
-}
-
-func TestNotices(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- notices, err := admin.Notices(1, 2)
- assert.NoError(t, err)
- if assert.Len(t, notices, 2) {
- assert.Equal(t, int64(3), notices[0].ID)
- assert.Equal(t, int64(2), notices[1].ID)
- }
-
- notices, err = admin.Notices(2, 2)
- assert.NoError(t, err)
- if assert.Len(t, notices, 1) {
- assert.Equal(t, int64(1), notices[0].ID)
- }
-}
-
-func TestDeleteNotice(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 3})
- assert.NoError(t, admin.DeleteNotice(3))
- unittest.AssertNotExistsBean(t, &admin.Notice{ID: 3})
-}
-
-func TestDeleteNotices(t *testing.T) {
- // delete a non-empty range
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 1})
- unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 2})
- unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 3})
- assert.NoError(t, admin.DeleteNotices(1, 2))
- unittest.AssertNotExistsBean(t, &admin.Notice{ID: 1})
- unittest.AssertNotExistsBean(t, &admin.Notice{ID: 2})
- unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 3})
-}
-
-func TestDeleteNotices2(t *testing.T) {
- // delete an empty range
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 1})
- unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 2})
- unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 3})
- assert.NoError(t, admin.DeleteNotices(3, 2))
- unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 1})
- unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 2})
- unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 3})
-}
-
-func TestDeleteNoticesByIDs(t *testing.T) {
- assert.NoError(t, unittest.PrepareTestDatabase())
-
- unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 1})
- unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 2})
- unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 3})
- assert.NoError(t, admin.DeleteNoticesByIDs([]int64{1, 3}))
- unittest.AssertNotExistsBean(t, &admin.Notice{ID: 1})
- unittest.AssertExistsAndLoadBean(t, &admin.Notice{ID: 2})
- unittest.AssertNotExistsBean(t, &admin.Notice{ID: 3})
-}
diff --git a/models/avatars/avatar.go b/models/avatars/avatar.go
index 9f7b0c474f..418e9b9ccc 100644
--- a/models/avatars/avatar.go
+++ b/models/avatars/avatar.go
@@ -13,6 +13,7 @@ import (
"sync"
"code.gitea.io/gitea/models/db"
+ system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/log"
@@ -72,7 +73,7 @@ func GetEmailForHash(md5Sum string) (string, error) {
// LibravatarURL returns the URL for the given email. Slow due to the DNS lookup.
// This function should only be called if a federated avatar service is enabled.
func LibravatarURL(email string) (*url.URL, error) {
- urlStr, err := setting.LibravatarService.FromEmail(email)
+ urlStr, err := system_model.LibravatarService.FromEmail(email)
if err != nil {
log.Error("LibravatarService.FromEmail(email=%s): error %v", email, err)
return nil, err
@@ -149,8 +150,10 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
return DefaultAvatarLink()
}
+ enableFederatedAvatar, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
+
var err error
- if setting.EnableFederatedAvatar && setting.LibravatarService != nil {
+ if enableFederatedAvatar != nil && enableFederatedAvatar.GetValueBool() && system_model.LibravatarService != nil {
emailHash := saveEmailHash(email)
if final {
// for final link, we can spend more time on slow external query
@@ -166,12 +169,16 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
urlStr += "?size=" + strconv.Itoa(size)
}
return urlStr
- } else if !setting.DisableGravatar {
+ }
+
+ disableGravatar, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
+ if disableGravatar != nil && !disableGravatar.GetValueBool() {
// copy GravatarSourceURL, because we will modify its Path.
- avatarURLCopy := *setting.GravatarSourceURL
+ avatarURLCopy := *system_model.GravatarSourceURL
avatarURLCopy.Path = path.Join(avatarURLCopy.Path, HashEmail(email))
return generateRecognizedAvatarURL(avatarURLCopy, size)
}
+
return DefaultAvatarLink()
}
diff --git a/models/avatars/avatar_test.go b/models/avatars/avatar_test.go
index 4d6255ca5f..ace5445fc0 100644
--- a/models/avatars/avatar_test.go
+++ b/models/avatars/avatar_test.go
@@ -2,12 +2,13 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
-package avatars
+package avatars_test
import (
- "net/url"
"testing"
+ avatars_model "code.gitea.io/gitea/models/avatars"
+ system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
@@ -15,40 +16,43 @@ import (
const gravatarSource = "https://secure.gravatar.com/avatar/"
-func disableGravatar() {
- setting.EnableFederatedAvatar = false
- setting.LibravatarService = nil
- setting.DisableGravatar = true
+func disableGravatar(t *testing.T) {
+ err := system_model.SetSettingNoVersion(system_model.KeyPictureEnableFederatedAvatar, "false")
+ assert.NoError(t, err)
+ err = system_model.SetSettingNoVersion(system_model.KeyPictureDisableGravatar, "true")
+ assert.NoError(t, err)
+ system_model.LibravatarService = nil
}
func enableGravatar(t *testing.T) {
- setting.DisableGravatar = false
- var err error
- setting.GravatarSourceURL, err = url.Parse(gravatarSource)
+ err := system_model.SetSettingNoVersion(system_model.KeyPictureDisableGravatar, "false")
+ assert.NoError(t, err)
+ setting.GravatarSource = gravatarSource
+ err = system_model.Init()
assert.NoError(t, err)
}
func TestHashEmail(t *testing.T) {
assert.Equal(t,
"d41d8cd98f00b204e9800998ecf8427e",
- HashEmail(""),
+ avatars_model.HashEmail(""),
)
assert.Equal(t,
"353cbad9b58e69c96154ad99f92bedc7",
- HashEmail("gitea@example.com"),
+ avatars_model.HashEmail("gitea@example.com"),
)
}
func TestSizedAvatarLink(t *testing.T) {
setting.AppSubURL = "/testsuburl"
- disableGravatar()
+ disableGravatar(t)
assert.Equal(t, "/testsuburl/assets/img/avatar_default.png",
- GenerateEmailAvatarFastLink("gitea@example.com", 100))
+ avatars_model.GenerateEmailAvatarFastLink("gitea@example.com", 100))
enableGravatar(t)
assert.Equal(t,
"https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon&s=100",
- GenerateEmailAvatarFastLink("gitea@example.com", 100),
+ avatars_model.GenerateEmailAvatarFastLink("gitea@example.com", 100),
)
}
diff --git a/models/admin/main_test.go b/models/avatars/main_test.go
index 23277fe37c..0e98d8f64d 100644
--- a/models/admin/main_test.go
+++ b/models/avatars/main_test.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
-package admin_test
+package avatars_test
import (
"path/filepath"
diff --git a/models/fixtures/system_setting.yml b/models/fixtures/system_setting.yml
new file mode 100644
index 0000000000..6c960168fc
--- /dev/null
+++ b/models/fixtures/system_setting.yml
@@ -0,0 +1,15 @@
+-
+ id: 1
+ setting_key: 'disable_gravatar'
+ setting_value: 'false'
+ version: 1
+ created: 1653533198
+ updated: 1653533198
+
+-
+ id: 2
+ setting_key: 'enable_federated_avatar'
+ setting_value: 'false'
+ version: 1
+ created: 1653533198
+ updated: 1653533198
diff --git a/models/issues/issue.go b/models/issues/issue.go
index 737b625abc..786c969522 100644
--- a/models/issues/issue.go
+++ b/models/issues/issue.go
@@ -13,7 +13,6 @@ import (
"strconv"
"strings"
- admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/foreignreference"
"code.gitea.io/gitea/models/organization"
@@ -21,6 +20,7 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
+ system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
@@ -2470,7 +2470,7 @@ func DeleteOrphanedIssues() error {
// Remove issue attachment files.
for i := range attachmentPaths {
- admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete issue attachment", attachmentPaths[i])
+ system_model.RemoveAllWithNotice(db.DefaultContext, "Delete issue attachment", attachmentPaths[i])
}
return nil
}
diff --git a/models/main_test.go b/models/main_test.go
index 49b6e3e560..3584001569 100644
--- a/models/main_test.go
+++ b/models/main_test.go
@@ -14,6 +14,8 @@ import (
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
+ _ "code.gitea.io/gitea/models/system"
+
"github.com/stretchr/testify/assert"
)
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 2a38772180..afe1445a23 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -415,6 +415,8 @@ var migrations = []Migration{
NewMigration("Alter gpg_key/public_key content TEXT fields to MEDIUMTEXT", alterPublicGPGKeyContentFieldsToMediumText),
// v226 -> v227
NewMigration("Conan and generic packages do not need to be semantically versioned", fixPackageSemverField),
+ // v227 -> v228
+ NewMigration("Create key/value table for system settings", createSystemSettingsTable),
}
// GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v227.go b/models/migrations/v227.go
new file mode 100644
index 0000000000..8a3a9c877e
--- /dev/null
+++ b/models/migrations/v227.go
@@ -0,0 +1,64 @@
+// 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 (
+ "fmt"
+ "strconv"
+
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "xorm.io/xorm"
+)
+
+type SystemSetting struct {
+ ID int64 `xorm:"pk autoincr"`
+ SettingKey string `xorm:"varchar(255) unique"` // ensure key is always lowercase
+ SettingValue string `xorm:"text"`
+ Version int `xorm:"version"` // prevent to override
+ Created timeutil.TimeStamp `xorm:"created"`
+ Updated timeutil.TimeStamp `xorm:"updated"`
+}
+
+func insertSettingsIfNotExist(x *xorm.Engine, sysSettings []*SystemSetting) error {
+ sess := x.NewSession()
+ defer sess.Close()
+ if err := sess.Begin(); err != nil {
+ return err
+ }
+ for _, setting := range sysSettings {
+ exist, err := sess.Table("system_setting").Where("setting_key=?", setting.SettingKey).Exist()
+ if err != nil {
+ return err
+ }
+ if !exist {
+ if _, err := sess.Insert(setting); err != nil {
+ return err
+ }
+ }
+ }
+ return sess.Commit()
+}
+
+func createSystemSettingsTable(x *xorm.Engine) error {
+ if err := x.Sync2(new(SystemSetting)); err != nil {
+ return fmt.Errorf("sync2: %v", err)
+ }
+
+ // migrate xx to database
+ sysSettings := []*SystemSetting{
+ {
+ SettingKey: "picture.disable_gravatar",
+ SettingValue: strconv.FormatBool(setting.DisableGravatar),
+ },
+ {
+ SettingKey: "picture.enable_federated_avatar",
+ SettingValue: strconv.FormatBool(setting.EnableFederatedAvatar),
+ },
+ }
+
+ return insertSettingsIfNotExist(x, sysSettings)
+}
diff --git a/models/repo.go b/models/repo.go
index 94e6249842..65159f14af 100644
--- a/models/repo.go
+++ b/models/repo.go
@@ -22,6 +22,7 @@ import (
access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
+ system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook"
@@ -32,9 +33,13 @@ import (
"xorm.io/builder"
)
-// NewRepoContext creates a new repository context
-func NewRepoContext() {
+// ItemsPerPage maximum items per page in forks, watchers and stars of a repo
+var ItemsPerPage = 40
+
+// Init initialize model
+func Init() error {
unit.LoadUnitConfig()
+ return system_model.Init()
}
// DeleteRepository deletes a repository for a user or organization.
@@ -267,36 +272,36 @@ func DeleteRepository(doer *user_model.User, uid, repoID int64) error {
// Remove repository files.
repoPath := repo.RepoPath()
- admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository files", repoPath)
+ system_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository files", repoPath)
// Remove wiki files
if repo.HasWiki() {
- admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository wiki", repo.WikiPath())
+ system_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository wiki", repo.WikiPath())
}
// Remove archives
for _, archive := range archivePaths {
- admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.RepoArchives, "Delete repo archive file", archive)
+ system_model.RemoveStorageWithNotice(db.DefaultContext, storage.RepoArchives, "Delete repo archive file", archive)
}
// Remove lfs objects
for _, lfsObj := range lfsPaths {
- admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.LFS, "Delete orphaned LFS file", lfsObj)
+ system_model.RemoveStorageWithNotice(db.DefaultContext, storage.LFS, "Delete orphaned LFS file", lfsObj)
}
// Remove issue attachment files.
for _, attachment := range attachmentPaths {
- admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachment)
+ system_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", attachment)
}
// Remove release attachment files.
for _, releaseAttachment := range releaseAttachments {
- admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete release attachment", releaseAttachment)
+ system_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete release attachment", releaseAttachment)
}
// Remove attachment with no issue_id and release_id.
for _, newAttachment := range newAttachmentPaths {
- admin_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", newAttachment)
+ system_model.RemoveStorageWithNotice(db.DefaultContext, storage.Attachments, "Delete issue attachment", newAttachment)
}
if len(repo.Avatar) > 0 {
diff --git a/models/appstate/appstate.go b/models/system/appstate.go
index aa5a59e1a3..c11a2512ab 100644
--- a/models/appstate/appstate.go
+++ b/models/system/appstate.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
-package appstate
+package system
import (
"context"
diff --git a/models/system/main_test.go b/models/system/main_test.go
new file mode 100644
index 0000000000..a56c76aedc
--- /dev/null
+++ b/models/system/main_test.go
@@ -0,0 +1,21 @@
+// Copyright 2020 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 system_test
+
+import (
+ "path/filepath"
+ "testing"
+
+ "code.gitea.io/gitea/models/unittest"
+
+ _ "code.gitea.io/gitea/models" // register models
+ _ "code.gitea.io/gitea/models/system" // register models of system
+)
+
+func TestMain(m *testing.M) {
+ unittest.MainTest(m, &unittest.TestOptions{
+ GiteaRootPath: filepath.Join("..", ".."),
+ })
+}
diff --git a/models/admin/notice.go b/models/system/notice.go
index 4d385cf951..3276fa3ffb 100644
--- a/models/admin/notice.go
+++ b/models/system/notice.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
-package admin
+package system
import (
"context"
diff --git a/models/system/notice_test.go b/models/system/notice_test.go
new file mode 100644
index 0000000000..768bcca66c
--- /dev/null
+++ b/models/system/notice_test.go
@@ -0,0 +1,117 @@
+// Copyright 2017 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 system_test
+
+import (
+ "testing"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/system"
+ "code.gitea.io/gitea/models/unittest"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestNotice_TrStr(t *testing.T) {
+ notice := &system.Notice{
+ Type: system.NoticeRepository,
+ Description: "test description",
+ }
+ assert.Equal(t, "admin.notices.type_1", notice.TrStr())
+}
+
+func TestCreateNotice(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ noticeBean := &system.Notice{
+ Type: system.NoticeRepository,
+ Description: "test description",
+ }
+ unittest.AssertNotExistsBean(t, noticeBean)
+ assert.NoError(t, system.CreateNotice(db.DefaultContext, noticeBean.Type, noticeBean.Description))
+ unittest.AssertExistsAndLoadBean(t, noticeBean)
+}
+
+func TestCreateRepositoryNotice(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ noticeBean := &system.Notice{
+ Type: system.NoticeRepository,
+ Description: "test description",
+ }
+ unittest.AssertNotExistsBean(t, noticeBean)
+ assert.NoError(t, system.CreateRepositoryNotice(noticeBean.Description))
+ unittest.AssertExistsAndLoadBean(t, noticeBean)
+}
+
+// TODO TestRemoveAllWithNotice
+
+func TestCountNotices(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+ assert.Equal(t, int64(3), system.CountNotices())
+}
+
+func TestNotices(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ notices, err := system.Notices(1, 2)
+ assert.NoError(t, err)
+ if assert.Len(t, notices, 2) {
+ assert.Equal(t, int64(3), notices[0].ID)
+ assert.Equal(t, int64(2), notices[1].ID)
+ }
+
+ notices, err = system.Notices(2, 2)
+ assert.NoError(t, err)
+ if assert.Len(t, notices, 1) {
+ assert.Equal(t, int64(1), notices[0].ID)
+ }
+}
+
+func TestDeleteNotice(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3})
+ assert.NoError(t, system.DeleteNotice(3))
+ unittest.AssertNotExistsBean(t, &system.Notice{ID: 3})
+}
+
+func TestDeleteNotices(t *testing.T) {
+ // delete a non-empty range
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1})
+ unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2})
+ unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3})
+ assert.NoError(t, system.DeleteNotices(1, 2))
+ unittest.AssertNotExistsBean(t, &system.Notice{ID: 1})
+ unittest.AssertNotExistsBean(t, &system.Notice{ID: 2})
+ unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3})
+}
+
+func TestDeleteNotices2(t *testing.T) {
+ // delete an empty range
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1})
+ unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2})
+ unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3})
+ assert.NoError(t, system.DeleteNotices(3, 2))
+ unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1})
+ unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2})
+ unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3})
+}
+
+func TestDeleteNoticesByIDs(t *testing.T) {
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 1})
+ unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2})
+ unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 3})
+ assert.NoError(t, system.DeleteNoticesByIDs([]int64{1, 3}))
+ unittest.AssertNotExistsBean(t, &system.Notice{ID: 1})
+ unittest.AssertExistsAndLoadBean(t, &system.Notice{ID: 2})
+ unittest.AssertNotExistsBean(t, &system.Notice{ID: 3})
+}
diff --git a/models/system/setting.go b/models/system/setting.go
new file mode 100644
index 0000000000..ff8b48e618
--- /dev/null
+++ b/models/system/setting.go
@@ -0,0 +1,261 @@
+// Copyright 2021 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 system
+
+import (
+ "context"
+ "fmt"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/timeutil"
+
+ "strk.kbt.io/projects/go/libravatar"
+ "xorm.io/builder"
+)
+
+// Setting is a key value store of user settings
+type Setting struct {
+ ID int64 `xorm:"pk autoincr"`
+ SettingKey string `xorm:"varchar(255) unique"` // ensure key is always lowercase
+ SettingValue string `xorm:"text"`
+ Version int `xorm:"version"` // prevent to override
+ Created timeutil.TimeStamp `xorm:"created"`
+ Updated timeutil.TimeStamp `xorm:"updated"`
+}
+
+// TableName sets the table name for the settings struct
+func (s *Setting) TableName() string {
+ return "system_setting"
+}
+
+func (s *Setting) GetValueBool() bool {
+ b, _ := strconv.ParseBool(s.SettingValue)
+ return b
+}
+
+func init() {
+ db.RegisterModel(new(Setting))
+}
+
+// ErrSettingIsNotExist represents an error that a setting is not exist with special key
+type ErrSettingIsNotExist struct {
+ Key string
+}
+
+// Error implements error
+func (err ErrSettingIsNotExist) Error() string {
+ return fmt.Sprintf("System setting[%s] is not exist", err.Key)
+}
+
+// IsErrSettingIsNotExist return true if err is ErrSettingIsNotExist
+func IsErrSettingIsNotExist(err error) bool {
+ _, ok := err.(ErrSettingIsNotExist)
+ return ok
+}
+
+// ErrDataExpired represents an error that update a record which has been updated by another thread
+type ErrDataExpired struct {
+ Key string
+}
+
+// Error implements error
+func (err ErrDataExpired) Error() string {
+ return fmt.Sprintf("System setting[%s] has been updated by another thread", err.Key)
+}
+
+// IsErrDataExpired return true if err is ErrDataExpired
+func IsErrDataExpired(err error) bool {
+ _, ok := err.(ErrDataExpired)
+ return ok
+}
+
+// GetSetting returns specific setting
+func GetSetting(key string) (*Setting, error) {
+ v, err := GetSettings([]string{key})
+ if err != nil {
+ return nil, err
+ }
+ if len(v) == 0 {
+ return nil, ErrSettingIsNotExist{key}
+ }
+ return v[key], nil
+}
+
+// GetSettings returns specific settings
+func GetSettings(keys []string) (map[string]*Setting, error) {
+ for i := 0; i < len(keys); i++ {
+ keys[i] = strings.ToLower(keys[i])
+ }
+ settings := make([]*Setting, 0, len(keys))
+ if err := db.GetEngine(db.DefaultContext).
+ Where(builder.In("setting_key", keys)).
+ Find(&settings); err != nil {
+ return nil, err
+ }
+ settingsMap := make(map[string]*Setting)
+ for _, s := range settings {
+ settingsMap[s.SettingKey] = s
+ }
+ return settingsMap, nil
+}
+
+type AllSettings map[string]*Setting
+
+func (settings AllSettings) Get(key string) Setting {
+ if v, ok := settings[key]; ok {
+ return *v
+ }
+ return Setting{}
+}
+
+func (settings AllSettings) GetBool(key string) bool {
+ b, _ := strconv.ParseBool(settings.Get(key).SettingValue)
+ return b
+}
+
+func (settings AllSettings) GetVersion(key string) int {
+ return settings.Get(key).Version
+}
+
+// GetAllSettings returns all settings from user
+func GetAllSettings() (AllSettings, error) {
+ settings := make([]*Setting, 0, 5)
+ if err := db.GetEngine(db.DefaultContext).
+ Find(&settings); err != nil {
+ return nil, err
+ }
+ settingsMap := make(map[string]*Setting)
+ for _, s := range settings {
+ settingsMap[s.SettingKey] = s
+ }
+ return settingsMap, nil
+}
+
+// DeleteSetting deletes a specific setting for a user
+func DeleteSetting(setting *Setting) error {
+ _, err := db.GetEngine(db.DefaultContext).Delete(setting)
+ return err
+}
+
+func SetSettingNoVersion(key, value string) error {
+ s, err := GetSetting(key)
+ if IsErrSettingIsNotExist(err) {
+ return SetSetting(&Setting{
+ SettingKey: key,
+ SettingValue: value,
+ })
+ }
+ if err != nil {
+ return err
+ }
+ s.SettingValue = value
+ return SetSetting(s)
+}
+
+// SetSetting updates a users' setting for a specific key
+func SetSetting(setting *Setting) error {
+ if err := upsertSettingValue(strings.ToLower(setting.SettingKey), setting.SettingValue, setting.Version); err != nil {
+ return err
+ }
+ setting.Version++
+ return nil
+}
+
+func upsertSettingValue(key, value string, version int) error {
+ return db.WithTx(func(ctx context.Context) error {
+ e := db.GetEngine(ctx)
+
+ // here we use a general method to do a safe upsert for different databases (and most transaction levels)
+ // 1. try to UPDATE the record and acquire the transaction write lock
+ // if UPDATE returns non-zero rows are changed, OK, the setting is saved correctly
+ // if UPDATE returns "0 rows changed", two possibilities: (a) record doesn't exist (b) value is not changed
+ // 2. do a SELECT to check if the row exists or not (we already have the transaction lock)
+ // 3. if the row doesn't exist, do an INSERT (we are still protected by the transaction lock, so it's safe)
+ //
+ // to optimize the SELECT in step 2, we can use an extra column like `revision=revision+1`
+ // to make sure the UPDATE always returns a non-zero value for existing (unchanged) records.
+
+ res, err := e.Exec("UPDATE system_setting SET setting_value=?, version = version+1 WHERE setting_key=? AND version=?", value, key, version)
+ if err != nil {
+ return err
+ }
+ rows, _ := res.RowsAffected()
+ if rows > 0 {
+ // the existing row is updated, so we can return
+ return nil
+ }
+
+ // in case the value isn't changed, update would return 0 rows changed, so we need this check
+ has, err := e.Exist(&Setting{SettingKey: key})
+ if err != nil {
+ return err
+ }
+ if has {
+ return ErrDataExpired{Key: key}
+ }
+
+ // if no existing row, insert a new row
+ _, err = e.Insert(&Setting{SettingKey: key, SettingValue: value})
+ return err
+ })
+}
+
+var (
+ GravatarSourceURL *url.URL
+ LibravatarService *libravatar.Libravatar
+)
+
+func Init() error {
+ var disableGravatar bool
+ disableGravatarSetting, err := GetSetting(KeyPictureDisableGravatar)
+ if IsErrSettingIsNotExist(err) {
+ disableGravatar = setting.GetDefaultDisableGravatar()
+ disableGravatarSetting = &Setting{SettingValue: strconv.FormatBool(disableGravatar)}
+ } else if err != nil {
+ return err
+ } else {
+ disableGravatar = disableGravatarSetting.GetValueBool()
+ }
+
+ var enableFederatedAvatar bool
+ enableFederatedAvatarSetting, err := GetSetting(KeyPictureEnableFederatedAvatar)
+ if IsErrSettingIsNotExist(err) {
+ enableFederatedAvatar = setting.GetDefaultEnableFederatedAvatar(disableGravatar)
+ enableFederatedAvatarSetting = &Setting{SettingValue: strconv.FormatBool(enableFederatedAvatar)}
+ } else if err != nil {
+ return err
+ } else {
+ enableFederatedAvatar = disableGravatarSetting.GetValueBool()
+ }
+
+ if setting.OfflineMode {
+ disableGravatar = true
+ enableFederatedAvatar = false
+ }
+
+ if disableGravatar || !enableFederatedAvatar {
+ var err error
+ GravatarSourceURL, err = url.Parse(setting.GravatarSource)
+ if err != nil {
+ return fmt.Errorf("Failed to parse Gravatar URL(%s): %v", setting.GravatarSource, err)
+ }
+ }
+
+ if enableFederatedAvatarSetting.GetValueBool() {
+ LibravatarService = libravatar.New()
+ if GravatarSourceURL.Scheme == "https" {
+ LibravatarService.SetUseHTTPS(true)
+ LibravatarService.SetSecureFallbackHost(GravatarSourceURL.Host)
+ } else {
+ LibravatarService.SetUseHTTPS(false)
+ LibravatarService.SetFallbackHost(GravatarSourceURL.Host)
+ }
+ }
+ return nil
+}
diff --git a/models/system/setting_key.go b/models/system/setting_key.go
new file mode 100644
index 0000000000..5a6ea6ed72
--- /dev/null
+++ b/models/system/setting_key.go
@@ -0,0 +1,11 @@
+// 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 system
+
+// enumerate all system setting keys
+const (
+ KeyPictureDisableGravatar = "picture.disable_gravatar"
+ KeyPictureEnableFederatedAvatar = "picture.enable_federated_avatar"
+)
diff --git a/models/system/setting_test.go b/models/system/setting_test.go
new file mode 100644
index 0000000000..d25fc05f31
--- /dev/null
+++ b/models/system/setting_test.go
@@ -0,0 +1,53 @@
+// Copyright 2021 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 system_test
+
+import (
+ "strings"
+ "testing"
+
+ "code.gitea.io/gitea/models/system"
+ "code.gitea.io/gitea/models/unittest"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSettings(t *testing.T) {
+ keyName := "server.LFS_LOCKS_PAGING_NUM"
+ assert.NoError(t, unittest.PrepareTestDatabase())
+
+ newSetting := &system.Setting{SettingKey: keyName, SettingValue: "50"}
+
+ // create setting
+ err := system.SetSetting(newSetting)
+ assert.NoError(t, err)
+ // test about saving unchanged values
+ err = system.SetSetting(newSetting)
+ assert.NoError(t, err)
+
+ // get specific setting
+ settings, err := system.GetSettings([]string{keyName})
+ assert.NoError(t, err)
+ assert.Len(t, settings, 1)
+ assert.EqualValues(t, newSetting.SettingValue, settings[strings.ToLower(keyName)].SettingValue)
+
+ // updated setting
+ updatedSetting := &system.Setting{SettingKey: keyName, SettingValue: "100", Version: newSetting.Version}
+ err = system.SetSetting(updatedSetting)
+ assert.NoError(t, err)
+
+ // get all settings
+ settings, err = system.GetAllSettings()
+ assert.NoError(t, err)
+ assert.Len(t, settings, 3)
+ assert.EqualValues(t, updatedSetting.SettingValue, settings[strings.ToLower(updatedSetting.SettingKey)].SettingValue)
+
+ // delete setting
+ err = system.DeleteSetting(&system.Setting{SettingKey: strings.ToLower(keyName)})
+ assert.NoError(t, err)
+ settings, err = system.GetAllSettings()
+ assert.NoError(t, err)
+ assert.Len(t, settings, 2)
+}
diff --git a/models/unittest/testdb.go b/models/unittest/testdb.go
index 25129137f7..2e6c25ae48 100644
--- a/models/unittest/testdb.go
+++ b/models/unittest/testdb.go
@@ -7,12 +7,12 @@ package unittest
import (
"context"
"fmt"
- "net/url"
"os"
"path/filepath"
"testing"
"code.gitea.io/gitea/models/db"
+ system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
@@ -91,10 +91,8 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
setting.AppDataPath = appDataPath
setting.AppWorkPath = testOpts.GiteaRootPath
setting.StaticRootPath = testOpts.GiteaRootPath
- setting.GravatarSourceURL, err = url.Parse("https://secure.gravatar.com/avatar/")
- if err != nil {
- fatalTestError("url.Parse: %v\n", err)
- }
+ setting.GravatarSource = "https://secure.gravatar.com/avatar/"
+
setting.Attachment.Storage.Path = filepath.Join(setting.AppDataPath, "attachments")
setting.LFS.Storage.Path = filepath.Join(setting.AppDataPath, "lfs")
@@ -112,6 +110,9 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
if err = storage.Init(); err != nil {
fatalTestError("storage.Init: %v\n", err)
}
+ if err = system_model.Init(); err != nil {
+ fatalTestError("models.Init: %v\n", err)
+ }
if err = util.RemoveAll(repoRootPath); err != nil {
fatalTestError("util.RemoveAll: %v\n", err)
diff --git a/models/user/avatar.go b/models/user/avatar.go
index 6a44a3bcb3..1c75c7406b 100644
--- a/models/user/avatar.go
+++ b/models/user/avatar.go
@@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models/avatars"
"code.gitea.io/gitea/models/db"
+ system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/avatar"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -67,10 +68,16 @@ func (u *User) AvatarLinkWithSize(size int) string {
useLocalAvatar := false
autoGenerateAvatar := false
+ var disableGravatar bool
+ disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
+ if disableGravatarSetting != nil {
+ disableGravatar = disableGravatarSetting.GetValueBool()
+ }
+
switch {
case u.UseCustomAvatar:
useLocalAvatar = true
- case setting.DisableGravatar, setting.OfflineMode:
+ case disableGravatar, setting.OfflineMode:
useLocalAvatar = true
autoGenerateAvatar = true
}
diff --git a/models/user/setting.go b/models/user/setting.go
index fbb6fbab30..5fe7c2ec23 100644
--- a/models/user/setting.go
+++ b/models/user/setting.go
@@ -31,6 +31,34 @@ func init() {
db.RegisterModel(new(Setting))
}
+// ErrUserSettingIsNotExist represents an error that a setting is not exist with special key
+type ErrUserSettingIsNotExist struct {
+ Key string
+}
+
+// Error implements error
+func (err ErrUserSettingIsNotExist) Error() string {
+ return fmt.Sprintf("Setting[%s] is not exist", err.Key)
+}
+
+// IsErrUserSettingIsNotExist return true if err is ErrSettingIsNotExist
+func IsErrUserSettingIsNotExist(err error) bool {
+ _, ok := err.(ErrUserSettingIsNotExist)
+ return ok
+}
+
+// GetSetting returns specific setting
+func GetSetting(uid int64, key string) (*Setting, error) {
+ v, err := GetUserSettings(uid, []string{key})
+ if err != nil {
+ return nil, err
+ }
+ if len(v) == 0 {
+ return nil, ErrUserSettingIsNotExist{key}
+ }
+ return v[key], nil
+}
+
// GetUserSettings returns specific settings from user
func GetUserSettings(uid int64, keys []string) (map[string]*Setting, error) {
settings := make([]*Setting, 0, len(keys))
diff --git a/modules/repository/commits_test.go b/modules/repository/commits_test.go
index c62e324b66..7bd741d0c8 100644
--- a/modules/repository/commits_test.go
+++ b/modules/repository/commits_test.go
@@ -11,8 +11,10 @@ import (
"time"
repo_model "code.gitea.io/gitea/models/repo"
+ system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
@@ -100,6 +102,14 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) {
assert.EqualValues(t, []string{"readme.md"}, headCommit.Modified)
}
+func enableGravatar(t *testing.T) {
+ err := system_model.SetSettingNoVersion(system_model.KeyPictureDisableGravatar, "false")
+ assert.NoError(t, err)
+ setting.GravatarSource = "https://secure.gravatar.com/avatar"
+ err = system_model.Init()
+ assert.NoError(t, err)
+}
+
func TestPushCommits_AvatarLink(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
@@ -123,6 +133,8 @@ func TestPushCommits_AvatarLink(t *testing.T) {
},
}
+ enableGravatar(t)
+
assert.Equal(t,
"https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon&s=84",
pushCommits.AvatarLink("user2@example.com"))
diff --git a/modules/setting/picture.go b/modules/setting/picture.go
index a6d3447dcc..af9041ade3 100644
--- a/modules/setting/picture.go
+++ b/modules/setting/picture.go
@@ -4,14 +4,6 @@
package setting
-import (
- "net/url"
-
- "code.gitea.io/gitea/modules/log"
-
- "strk.kbt.io/projects/go/libravatar"
-)
-
// settings
var (
// Picture settings
@@ -30,10 +22,8 @@ var (
}
GravatarSource string
- GravatarSourceURL *url.URL
- DisableGravatar bool
- EnableFederatedAvatar bool
- LibravatarService *libravatar.Libravatar
+ DisableGravatar bool // Depreciated: migrated to database
+ EnableFederatedAvatar bool // Depreciated: migrated to database
RepoAvatar = struct {
Storage
@@ -69,38 +59,30 @@ func newPictureService() {
default:
GravatarSource = source
}
- DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool()
- EnableFederatedAvatar = sec.Key("ENABLE_FEDERATED_AVATAR").MustBool(!InstallLock)
- if OfflineMode {
- DisableGravatar = true
- EnableFederatedAvatar = false
- }
- if DisableGravatar {
- EnableFederatedAvatar = false
- }
- if EnableFederatedAvatar || !DisableGravatar {
- var err error
- GravatarSourceURL, err = url.Parse(GravatarSource)
- if err != nil {
- log.Fatal("Failed to parse Gravatar URL(%s): %v",
- GravatarSource, err)
- }
- }
- if EnableFederatedAvatar {
- LibravatarService = libravatar.New()
- if GravatarSourceURL.Scheme == "https" {
- LibravatarService.SetUseHTTPS(true)
- LibravatarService.SetSecureFallbackHost(GravatarSourceURL.Host)
- } else {
- LibravatarService.SetUseHTTPS(false)
- LibravatarService.SetFallbackHost(GravatarSourceURL.Host)
- }
- }
+ DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool(GetDefaultDisableGravatar())
+ deprecatedSettingDB("", "DISABLE_GRAVATAR")
+ EnableFederatedAvatar = sec.Key("ENABLE_FEDERATED_AVATAR").MustBool(GetDefaultEnableFederatedAvatar(DisableGravatar))
+ deprecatedSettingDB("", "ENABLE_FEDERATED_AVATAR")
newRepoAvatarService()
}
+func GetDefaultDisableGravatar() bool {
+ return !OfflineMode
+}
+
+func GetDefaultEnableFederatedAvatar(disableGravatar bool) bool {
+ v := !InstallLock
+ if OfflineMode {
+ v = false
+ }
+ if disableGravatar {
+ v = false
+ }
+ return v
+}
+
func newRepoAvatarService() {
sec := Cfg.Section("picture")
diff --git a/modules/setting/setting.go b/modules/setting/setting.go
index 007e3ef61f..f93be2fbd1 100644
--- a/modules/setting/setting.go
+++ b/modules/setting/setting.go
@@ -606,6 +606,13 @@ func deprecatedSetting(oldSection, oldKey, newSection, newKey string) {
}
}
+// deprecatedSettingDB add a hint that the configuration has been moved to database but still kept in app.ini
+func deprecatedSettingDB(oldSection, oldKey string) {
+ if Cfg.Section(oldSection).HasKey(oldKey) {
+ log.Error("Deprecated `[%s]` `%s` present which has been copied to database table sys_setting", oldSection, oldKey)
+ }
+}
+
// loadFromConf initializes configuration context.
// NOTE: do not print any log except error.
func loadFromConf(allowEmpty bool, extraConfig string) {
diff --git a/modules/appstate/appstate.go b/modules/system/appstate.go
index f65f5367e2..deee8cd029 100644
--- a/modules/appstate/appstate.go
+++ b/modules/system/appstate.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
-package appstate
+package system
// StateStore is the interface to get/set app state items
type StateStore interface {
diff --git a/modules/appstate/appstate_test.go b/modules/system/appstate_test.go
index e4a0d72850..fb0c2aaf9f 100644
--- a/modules/appstate/appstate_test.go
+++ b/modules/system/appstate_test.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
-package appstate
+package system
import (
"path/filepath"
diff --git a/modules/appstate/db.go b/modules/system/db.go
index 2538d1b5c8..b1c283c488 100644
--- a/modules/appstate/db.go
+++ b/modules/system/db.go
@@ -2,10 +2,10 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
-package appstate
+package system
import (
- "code.gitea.io/gitea/models/appstate"
+ "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/json"
"github.com/yuin/goldmark/util"
@@ -16,7 +16,7 @@ type DBStore struct{}
// Get reads the state item
func (f *DBStore) Get(item StateItem) error {
- content, err := appstate.GetAppStateContent(item.Name())
+ content, err := system.GetAppStateContent(item.Name())
if err != nil {
return err
}
@@ -32,5 +32,5 @@ func (f *DBStore) Set(item StateItem) error {
if err != nil {
return err
}
- return appstate.SaveAppStateContent(item.Name(), util.BytesToReadOnlyString(b))
+ return system.SaveAppStateContent(item.Name(), util.BytesToReadOnlyString(b))
}
diff --git a/modules/appstate/item_runtime.go b/modules/system/item_runtime.go
index 7fdc53f642..ef758a5675 100644
--- a/modules/appstate/item_runtime.go
+++ b/modules/system/item_runtime.go
@@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
-package appstate
+package system
// RuntimeState contains app state for runtime, and we can save remote version for update checker here in future
type RuntimeState struct {
diff --git a/modules/system/setting.go b/modules/system/setting.go
new file mode 100644
index 0000000000..aebf24a501
--- /dev/null
+++ b/modules/system/setting.go
@@ -0,0 +1,46 @@
+// Copyright 2021 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 system
+
+import (
+ "strconv"
+
+ "code.gitea.io/gitea/models/system"
+ "code.gitea.io/gitea/modules/cache"
+)
+
+func genKey(key string) string {
+ return "system.setting." + key
+}
+
+// GetSetting returns the setting value via the key
+func GetSetting(key string) (string, error) {
+ return cache.GetString(genKey(key), func() (string, error) {
+ res, err := system.GetSetting(key)
+ if err != nil {
+ return "", err
+ }
+ return res.SettingValue, nil
+ })
+}
+
+// GetSettingBool return bool value of setting,
+// none existing keys and errors are ignored and result in false
+func GetSettingBool(key string) bool {
+ s, _ := GetSetting(key)
+ b, _ := strconv.ParseBool(s)
+ return b
+}
+
+// SetSetting sets the setting value
+func SetSetting(key, value string, version int) error {
+ cache.Remove(genKey(key))
+
+ return system.SetSetting(&system.Setting{
+ SettingKey: key,
+ SettingValue: value,
+ Version: version,
+ })
+}
diff --git a/modules/system/user_setting.go b/modules/system/user_setting.go
new file mode 100644
index 0000000000..eaf146c08d
--- /dev/null
+++ b/modules/system/user_setting.go
@@ -0,0 +1,34 @@
+// Copyright 2021 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 system
+
+import (
+ "fmt"
+
+ "code.gitea.io/gitea/models/user"
+ "code.gitea.io/gitea/modules/cache"
+)
+
+func genUserKey(userID int64, key string) string {
+ return fmt.Sprintf("user_%d.setting.%s", userID, key)
+}
+
+// GetUserSetting returns the user setting value via the key
+func GetUserSetting(userID int64, key string) (string, error) {
+ return cache.GetString(genUserKey(userID, key), func() (string, error) {
+ res, err := user.GetSetting(userID, key)
+ if err != nil {
+ return "", err
+ }
+ return res.SettingValue, nil
+ })
+}
+
+// SetUserSetting sets the user setting value
+func SetUserSetting(userID int64, key, value string) error {
+ cache.Remove(genUserKey(userID, key))
+
+ return user.SetUserSetting(userID, key, value)
+}
diff --git a/modules/templates/helper.go b/modules/templates/helper.go
index e10beae1d9..7bd2bc0a1c 100644
--- a/modules/templates/helper.go
+++ b/modules/templates/helper.go
@@ -29,6 +29,7 @@ import (
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo"
+ system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/emoji"
@@ -41,6 +42,7 @@ import (
"code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/svg"
+ system_module "code.gitea.io/gitea/modules/system"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/gitdiff"
@@ -85,7 +87,7 @@ func NewFuncMap() []template.FuncMap {
return setting.AssetVersion
},
"DisableGravatar": func() bool {
- return setting.DisableGravatar
+ return system_module.GetSettingBool(system_model.KeyPictureDisableGravatar)
},
"DefaultShowFullName": func() bool {
return setting.UI.DefaultShowFullName
diff --git a/modules/updatechecker/update_checker.go b/modules/updatechecker/update_checker.go
index 9c1569b15e..816fb3764c 100644
--- a/modules/updatechecker/update_checker.go
+++ b/modules/updatechecker/update_checker.go
@@ -8,10 +8,10 @@ import (
"io"
"net/http"
- "code.gitea.io/gitea/modules/appstate"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/proxy"
"code.gitea.io/gitea/modules/setting"
+ "code.gitea.io/gitea/modules/system"
"github.com/hashicorp/go-version"
)
@@ -64,13 +64,13 @@ func GiteaUpdateChecker(httpEndpoint string) error {
// UpdateRemoteVersion updates the latest available version of Gitea
func UpdateRemoteVersion(version string) (err error) {
- return appstate.AppState.Set(&CheckerState{LatestVersion: version})
+ return system.AppState.Set(&CheckerState{LatestVersion: version})
}
// GetRemoteVersion returns the current remote version (or currently installed version if fail to fetch from DB)
func GetRemoteVersion() string {
item := new(CheckerState)
- if err := appstate.AppState.Get(item); err != nil {
+ if err := system.AppState.Get(item); err != nil {
return ""
}
return item.LatestVersion
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index fbf9b70643..e5da074f64 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -2879,6 +2879,9 @@ config.access_log_template = Template
config.xorm_log_mode = XORM Log Mode
config.xorm_log_sql = Log SQL
+config.get_setting_failed = Get setting %s failed
+config.set_setting_failed = Set setting %s failed
+
monitor.cron = Cron Tasks
monitor.name = Name
monitor.schedule = Schedule
diff --git a/routers/init.go b/routers/init.go
index 85a38899e3..0f2e993413 100644
--- a/routers/init.go
+++ b/routers/init.go
@@ -11,7 +11,6 @@ import (
"code.gitea.io/gitea/models"
asymkey_model "code.gitea.io/gitea/models/asymkey"
- "code.gitea.io/gitea/modules/appstate"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/eventsource"
"code.gitea.io/gitea/modules/git"
@@ -27,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/ssh"
"code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/svg"
+ "code.gitea.io/gitea/modules/system"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util"
@@ -76,8 +76,8 @@ func InitGitServices() {
}
func syncAppPathForGit(ctx context.Context) error {
- runtimeState := new(appstate.RuntimeState)
- if err := appstate.AppState.Get(runtimeState); err != nil {
+ runtimeState := new(system.RuntimeState)
+ if err := system.AppState.Get(runtimeState); err != nil {
return err
}
if runtimeState.LastAppPath != setting.AppPath {
@@ -90,7 +90,7 @@ func syncAppPathForGit(ctx context.Context) error {
mustInit(asymkey_model.RewriteAllPublicKeys)
runtimeState.LastAppPath = setting.AppPath
- return appstate.AppState.Set(runtimeState)
+ return system.AppState.Set(runtimeState)
}
return nil
}
@@ -133,10 +133,10 @@ func GlobalInitInstalled(ctx context.Context) {
mustInitCtx(ctx, common.InitDBEngine)
log.Info("ORM engine initialization successful!")
- mustInit(appstate.Init)
+ mustInit(system.Init)
mustInit(oauth2.Init)
- models.NewRepoContext()
+ mustInit(models.Init)
mustInit(repo_service.Init)
// Booting long running goroutines.
diff --git a/routers/install/install.go b/routers/install/install.go
index 890725b9a7..8a0d34d976 100644
--- a/routers/install/install.go
+++ b/routers/install/install.go
@@ -12,12 +12,14 @@ import (
"os"
"os/exec"
"path/filepath"
+ "strconv"
"strings"
"time"
"code.gitea.io/gitea/models/db"
db_install "code.gitea.io/gitea/models/db/install"
"code.gitea.io/gitea/models/migrations"
+ system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
@@ -147,8 +149,19 @@ func Install(ctx *context.Context) {
// Server and other services settings
form.OfflineMode = setting.OfflineMode
- form.DisableGravatar = setting.DisableGravatar
- form.EnableFederatedAvatar = setting.EnableFederatedAvatar
+ disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
+ if disableGravatarSetting != nil {
+ form.DisableGravatar = disableGravatarSetting.GetValueBool()
+ } else {
+ form.DisableGravatar = false
+ }
+
+ enableFederatedAvatarSetting, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
+ if enableFederatedAvatarSetting != nil {
+ form.EnableFederatedAvatar = enableFederatedAvatarSetting.GetValueBool()
+ } else {
+ form.EnableFederatedAvatar = false
+ }
form.EnableOpenIDSignIn = setting.Service.EnableOpenIDSignIn
form.EnableOpenIDSignUp = setting.Service.EnableOpenIDSignUp
form.DisableRegistration = setting.Service.DisableRegistration
@@ -439,7 +452,11 @@ func SubmitInstall(ctx *context.Context) {
cfg.Section("service").Key("ENABLE_NOTIFY_MAIL").SetValue(fmt.Sprint(form.MailNotify))
cfg.Section("server").Key("OFFLINE_MODE").SetValue(fmt.Sprint(form.OfflineMode))
- cfg.Section("picture").Key("DISABLE_GRAVATAR").SetValue(fmt.Sprint(form.DisableGravatar))
+ // if you are reinstalling, this maybe not right because of missing version
+ if err := system_model.SetSettingNoVersion(system_model.KeyPictureDisableGravatar, strconv.FormatBool(form.DisableGravatar)); err != nil {
+ ctx.RenderWithErr(ctx.Tr("install.secret_key_failed", err), tplInstall, &form)
+ return
+ }
cfg.Section("picture").Key("ENABLE_FEDERATED_AVATAR").SetValue(fmt.Sprint(form.EnableFederatedAvatar))
cfg.Section("openid").Key("ENABLE_OPENID_SIGNIN").SetValue(fmt.Sprint(form.EnableOpenIDSignIn))
cfg.Section("openid").Key("ENABLE_OPENID_SIGNUP").SetValue(fmt.Sprint(form.EnableOpenIDSignUp))
diff --git a/routers/web/admin/admin.go b/routers/web/admin/admin.go
index 17471ef8a6..d0664eb780 100644
--- a/routers/web/admin/admin.go
+++ b/routers/web/admin/admin.go
@@ -8,18 +8,13 @@ package admin
import (
"fmt"
"net/http"
- "net/url"
- "os"
"runtime"
"strconv"
- "strings"
"time"
activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
- "code.gitea.io/gitea/modules/git"
- "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/queue"
@@ -27,18 +22,13 @@ import (
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/updatechecker"
- "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/cron"
"code.gitea.io/gitea/services/forms"
- "code.gitea.io/gitea/services/mailer"
-
- "gitea.com/go-chi/session"
)
const (
tplDashboard base.TplName = "admin/dashboard"
- tplConfig base.TplName = "admin/config"
tplMonitor base.TplName = "admin/monitor"
tplStacktrace base.TplName = "admin/stacktrace"
tplQueue base.TplName = "admin/queue"
@@ -165,165 +155,6 @@ func DashboardPost(ctx *context.Context) {
}
}
-// SendTestMail send test mail to confirm mail service is OK
-func SendTestMail(ctx *context.Context) {
- email := ctx.FormString("email")
- // Send a test email to the user's email address and redirect back to Config
- if err := mailer.SendTestMail(email); err != nil {
- ctx.Flash.Error(ctx.Tr("admin.config.test_mail_failed", email, err))
- } else {
- ctx.Flash.Info(ctx.Tr("admin.config.test_mail_sent", email))
- }
-
- ctx.Redirect(setting.AppSubURL + "/admin/config")
-}
-
-func shadowPasswordKV(cfgItem, splitter string) string {
- fields := strings.Split(cfgItem, splitter)
- for i := 0; i < len(fields); i++ {
- if strings.HasPrefix(fields[i], "password=") {
- fields[i] = "password=******"
- break
- }
- }
- return strings.Join(fields, splitter)
-}
-
-func shadowURL(provider, cfgItem string) string {
- u, err := url.Parse(cfgItem)
- if err != nil {
- log.Error("Shadowing Password for %v failed: %v", provider, err)
- return cfgItem
- }
- if u.User != nil {
- atIdx := strings.Index(cfgItem, "@")
- if atIdx > 0 {
- colonIdx := strings.LastIndex(cfgItem[:atIdx], ":")
- if colonIdx > 0 {
- return cfgItem[:colonIdx+1] + "******" + cfgItem[atIdx:]
- }
- }
- }
- return cfgItem
-}
-
-func shadowPassword(provider, cfgItem string) string {
- switch provider {
- case "redis":
- return shadowPasswordKV(cfgItem, ",")
- case "mysql":
- // root:@tcp(localhost:3306)/macaron?charset=utf8
- atIdx := strings.Index(cfgItem, "@")
- if atIdx > 0 {
- colonIdx := strings.Index(cfgItem[:atIdx], ":")
- if colonIdx > 0 {
- return cfgItem[:colonIdx+1] + "******" + cfgItem[atIdx:]
- }
- }
- return cfgItem
- case "postgres":
- // user=jiahuachen dbname=macaron port=5432 sslmode=disable
- if !strings.HasPrefix(cfgItem, "postgres://") {
- return shadowPasswordKV(cfgItem, " ")
- }
- fallthrough
- case "couchbase":
- return shadowURL(provider, cfgItem)
- // postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full
- // Notice: use shadowURL
- }
- return cfgItem
-}
-
-// Config show admin config page
-func Config(ctx *context.Context) {
- ctx.Data["Title"] = ctx.Tr("admin.config")
- ctx.Data["PageIsAdmin"] = true
- ctx.Data["PageIsAdminConfig"] = true
-
- ctx.Data["CustomConf"] = setting.CustomConf
- ctx.Data["AppUrl"] = setting.AppURL
- ctx.Data["Domain"] = setting.Domain
- ctx.Data["OfflineMode"] = setting.OfflineMode
- ctx.Data["DisableRouterLog"] = setting.DisableRouterLog
- ctx.Data["RunUser"] = setting.RunUser
- ctx.Data["RunMode"] = util.ToTitleCase(setting.RunMode)
- ctx.Data["GitVersion"] = git.VersionInfo()
-
- ctx.Data["RepoRootPath"] = setting.RepoRootPath
- ctx.Data["CustomRootPath"] = setting.CustomPath
- ctx.Data["StaticRootPath"] = setting.StaticRootPath
- ctx.Data["LogRootPath"] = setting.LogRootPath
- ctx.Data["ScriptType"] = setting.ScriptType
- ctx.Data["ReverseProxyAuthUser"] = setting.ReverseProxyAuthUser
- ctx.Data["ReverseProxyAuthEmail"] = setting.ReverseProxyAuthEmail
- ctx.Data["ReverseProxyAuthFullName"] = setting.ReverseProxyAuthFullName
-
- ctx.Data["SSH"] = setting.SSH
- ctx.Data["LFS"] = setting.LFS
-
- ctx.Data["Service"] = setting.Service
- ctx.Data["DbCfg"] = setting.Database
- ctx.Data["Webhook"] = setting.Webhook
-
- ctx.Data["MailerEnabled"] = false
- if setting.MailService != nil {
- ctx.Data["MailerEnabled"] = true
- ctx.Data["Mailer"] = setting.MailService
- }
-
- ctx.Data["CacheAdapter"] = setting.CacheService.Adapter
- ctx.Data["CacheInterval"] = setting.CacheService.Interval
-
- ctx.Data["CacheConn"] = shadowPassword(setting.CacheService.Adapter, setting.CacheService.Conn)
- ctx.Data["CacheItemTTL"] = setting.CacheService.TTL
-
- sessionCfg := setting.SessionConfig
- if sessionCfg.Provider == "VirtualSession" {
- var realSession session.Options
- if err := json.Unmarshal([]byte(sessionCfg.ProviderConfig), &realSession); err != nil {
- log.Error("Unable to unmarshall session config for virtualed provider config: %s\nError: %v", sessionCfg.ProviderConfig, err)
- }
- sessionCfg.Provider = realSession.Provider
- sessionCfg.ProviderConfig = realSession.ProviderConfig
- sessionCfg.CookieName = realSession.CookieName
- sessionCfg.CookiePath = realSession.CookiePath
- sessionCfg.Gclifetime = realSession.Gclifetime
- sessionCfg.Maxlifetime = realSession.Maxlifetime
- sessionCfg.Secure = realSession.Secure
- sessionCfg.Domain = realSession.Domain
- }
- sessionCfg.ProviderConfig = shadowPassword(sessionCfg.Provider, sessionCfg.ProviderConfig)
- ctx.Data["SessionConfig"] = sessionCfg
-
- ctx.Data["DisableGravatar"] = setting.DisableGravatar
- ctx.Data["EnableFederatedAvatar"] = setting.EnableFederatedAvatar
-
- ctx.Data["Git"] = setting.Git
-
- type envVar struct {
- Name, Value string
- }
-
- envVars := map[string]*envVar{}
- if len(os.Getenv("GITEA_WORK_DIR")) > 0 {
- envVars["GITEA_WORK_DIR"] = &envVar{"GITEA_WORK_DIR", os.Getenv("GITEA_WORK_DIR")}
- }
- if len(os.Getenv("GITEA_CUSTOM")) > 0 {
- envVars["GITEA_CUSTOM"] = &envVar{"GITEA_CUSTOM", os.Getenv("GITEA_CUSTOM")}
- }
-
- ctx.Data["EnvVars"] = envVars
- ctx.Data["Loggers"] = setting.GetLogDescriptions()
- ctx.Data["EnableAccessLog"] = setting.EnableAccessLog
- ctx.Data["AccessLogTemplate"] = setting.AccessLogTemplate
- ctx.Data["DisableRouterLog"] = setting.DisableRouterLog
- ctx.Data["EnableXORMLog"] = setting.EnableXORMLog
- ctx.Data["LogSQL"] = setting.Database.LogSQL
-
- ctx.HTML(http.StatusOK, tplConfig)
-}
-
// Monitor show admin monitor page
func Monitor(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.monitor")
diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go
new file mode 100644
index 0000000000..614d3d4f66
--- /dev/null
+++ b/routers/web/admin/config.go
@@ -0,0 +1,217 @@
+// Copyright 2014 The Gogs Authors. All rights reserved.
+// Copyright 2019 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 admin
+
+import (
+ "net/http"
+ "net/url"
+ "os"
+ "strings"
+
+ system_model "code.gitea.io/gitea/models/system"
+ "code.gitea.io/gitea/modules/base"
+ "code.gitea.io/gitea/modules/context"
+ "code.gitea.io/gitea/modules/git"
+ "code.gitea.io/gitea/modules/json"
+ "code.gitea.io/gitea/modules/log"
+ "code.gitea.io/gitea/modules/setting"
+ system_module "code.gitea.io/gitea/modules/system"
+ "code.gitea.io/gitea/modules/util"
+ "code.gitea.io/gitea/services/mailer"
+
+ "gitea.com/go-chi/session"
+)
+
+const tplConfig base.TplName = "admin/config"
+
+// SendTestMail send test mail to confirm mail service is OK
+func SendTestMail(ctx *context.Context) {
+ email := ctx.FormString("email")
+ // Send a test email to the user's email address and redirect back to Config
+ if err := mailer.SendTestMail(email); err != nil {
+ ctx.Flash.Error(ctx.Tr("admin.config.test_mail_failed", email, err))
+ } else {
+ ctx.Flash.Info(ctx.Tr("admin.config.test_mail_sent", email))
+ }
+
+ ctx.Redirect(setting.AppSubURL + "/admin/config")
+}
+
+func shadowPasswordKV(cfgItem, splitter string) string {
+ fields := strings.Split(cfgItem, splitter)
+ for i := 0; i < len(fields); i++ {
+ if strings.HasPrefix(fields[i], "password=") {
+ fields[i] = "password=******"
+ break
+ }
+ }
+ return strings.Join(fields, splitter)
+}
+
+func shadowURL(provider, cfgItem string) string {
+ u, err := url.Parse(cfgItem)
+ if err != nil {
+ log.Error("Shadowing Password for %v failed: %v", provider, err)
+ return cfgItem
+ }
+ if u.User != nil {
+ atIdx := strings.Index(cfgItem, "@")
+ if atIdx > 0 {
+ colonIdx := strings.LastIndex(cfgItem[:atIdx], ":")
+ if colonIdx > 0 {
+ return cfgItem[:colonIdx+1] + "******" + cfgItem[atIdx:]
+ }
+ }
+ }
+ return cfgItem
+}
+
+func shadowPassword(provider, cfgItem string) string {
+ switch provider {
+ case "redis":
+ return shadowPasswordKV(cfgItem, ",")
+ case "mysql":
+ // root:@tcp(localhost:3306)/macaron?charset=utf8
+ atIdx := strings.Index(cfgItem, "@")
+ if atIdx > 0 {
+ colonIdx := strings.Index(cfgItem[:atIdx], ":")
+ if colonIdx > 0 {
+ return cfgItem[:colonIdx+1] + "******" + cfgItem[atIdx:]
+ }
+ }
+ return cfgItem
+ case "postgres":
+ // user=jiahuachen dbname=macaron port=5432 sslmode=disable
+ if !strings.HasPrefix(cfgItem, "postgres://") {
+ return shadowPasswordKV(cfgItem, " ")
+ }
+ fallthrough
+ case "couchbase":
+ return shadowURL(provider, cfgItem)
+ // postgres://pqgotest:password@localhost/pqgotest?sslmode=verify-full
+ // Notice: use shadowURL
+ }
+ return cfgItem
+}
+
+// Config show admin config page
+func Config(ctx *context.Context) {
+ ctx.Data["Title"] = ctx.Tr("admin.config")
+ ctx.Data["PageIsAdmin"] = true
+ ctx.Data["PageIsAdminConfig"] = true
+
+ systemSettings, err := system_model.GetAllSettings()
+ if err != nil {
+ ctx.ServerError("system_model.GetAllSettings", err)
+ return
+ }
+
+ // All editable settings from UI
+ ctx.Data["SystemSettings"] = systemSettings
+ ctx.PageData["adminConfigPage"] = true
+
+ ctx.Data["CustomConf"] = setting.CustomConf
+ ctx.Data["AppUrl"] = setting.AppURL
+ ctx.Data["Domain"] = setting.Domain
+ ctx.Data["OfflineMode"] = setting.OfflineMode
+ ctx.Data["DisableRouterLog"] = setting.DisableRouterLog
+ ctx.Data["RunUser"] = setting.RunUser
+ ctx.Data["RunMode"] = util.ToTitleCase(setting.RunMode)
+ ctx.Data["GitVersion"] = git.VersionInfo()
+
+ ctx.Data["RepoRootPath"] = setting.RepoRootPath
+ ctx.Data["CustomRootPath"] = setting.CustomPath
+ ctx.Data["StaticRootPath"] = setting.StaticRootPath
+ ctx.Data["LogRootPath"] = setting.LogRootPath
+ ctx.Data["ScriptType"] = setting.ScriptType
+ ctx.Data["ReverseProxyAuthUser"] = setting.ReverseProxyAuthUser
+ ctx.Data["ReverseProxyAuthEmail"] = setting.ReverseProxyAuthEmail
+
+ ctx.Data["SSH"] = setting.SSH
+ ctx.Data["LFS"] = setting.LFS
+
+ ctx.Data["Service"] = setting.Service
+ ctx.Data["DbCfg"] = setting.Database
+ ctx.Data["Webhook"] = setting.Webhook
+
+ ctx.Data["MailerEnabled"] = false
+ if setting.MailService != nil {
+ ctx.Data["MailerEnabled"] = true
+ ctx.Data["Mailer"] = setting.MailService
+ }
+
+ ctx.Data["CacheAdapter"] = setting.CacheService.Adapter
+ ctx.Data["CacheInterval"] = setting.CacheService.Interval
+
+ ctx.Data["CacheConn"] = shadowPassword(setting.CacheService.Adapter, setting.CacheService.Conn)
+ ctx.Data["CacheItemTTL"] = setting.CacheService.TTL
+
+ sessionCfg := setting.SessionConfig
+ if sessionCfg.Provider == "VirtualSession" {
+ var realSession session.Options
+ if err := json.Unmarshal([]byte(sessionCfg.ProviderConfig), &realSession); err != nil {
+ log.Error("Unable to unmarshall session config for virtual provider config: %s\nError: %v", sessionCfg.ProviderConfig, err)
+ }
+ sessionCfg.Provider = realSession.Provider
+ sessionCfg.ProviderConfig = realSession.ProviderConfig
+ sessionCfg.CookieName = realSession.CookieName
+ sessionCfg.CookiePath = realSession.CookiePath
+ sessionCfg.Gclifetime = realSession.Gclifetime
+ sessionCfg.Maxlifetime = realSession.Maxlifetime
+ sessionCfg.Secure = realSession.Secure
+ sessionCfg.Domain = realSession.Domain
+ }
+ sessionCfg.ProviderConfig = shadowPassword(sessionCfg.Provider, sessionCfg.ProviderConfig)
+ ctx.Data["SessionConfig"] = sessionCfg
+
+ ctx.Data["Git"] = setting.Git
+
+ type envVar struct {
+ Name, Value string
+ }
+
+ envVars := map[string]*envVar{}
+ if len(os.Getenv("GITEA_WORK_DIR")) > 0 {
+ envVars["GITEA_WORK_DIR"] = &envVar{"GITEA_WORK_DIR", os.Getenv("GITEA_WORK_DIR")}
+ }
+ if len(os.Getenv("GITEA_CUSTOM")) > 0 {
+ envVars["GITEA_CUSTOM"] = &envVar{"GITEA_CUSTOM", os.Getenv("GITEA_CUSTOM")}
+ }
+
+ ctx.Data["EnvVars"] = envVars
+ ctx.Data["Loggers"] = setting.GetLogDescriptions()
+ ctx.Data["EnableAccessLog"] = setting.EnableAccessLog
+ ctx.Data["AccessLogTemplate"] = setting.AccessLogTemplate
+ ctx.Data["DisableRouterLog"] = setting.DisableRouterLog
+ ctx.Data["EnableXORMLog"] = setting.EnableXORMLog
+ ctx.Data["LogSQL"] = setting.Database.LogSQL
+
+ ctx.HTML(http.StatusOK, tplConfig)
+}
+
+func ChangeConfig(ctx *context.Context) {
+ key := strings.TrimSpace(ctx.FormString("key"))
+ if key == "" {
+ ctx.JSON(http.StatusOK, map[string]string{
+ "redirect": ctx.Req.URL.String(),
+ })
+ return
+ }
+ value := ctx.FormString("value")
+ version := ctx.FormInt("version")
+
+ if err := system_module.SetSetting(key, value, version); err != nil {
+ log.Error("set setting failed: %v", err)
+ ctx.JSON(http.StatusOK, map[string]string{
+ "err": ctx.Tr("admin.config.set_setting_failed", key),
+ })
+ return
+ }
+
+ ctx.JSON(http.StatusOK, map[string]interface{}{
+ "version": version + 1,
+ })
+}
diff --git a/routers/web/admin/notice.go b/routers/web/admin/notice.go
index b50549b804..f5ec294cc3 100644
--- a/routers/web/admin/notice.go
+++ b/routers/web/admin/notice.go
@@ -9,7 +9,7 @@ import (
"net/http"
"strconv"
- admin_model "code.gitea.io/gitea/models/admin"
+ system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log"
@@ -26,13 +26,13 @@ func Notices(ctx *context.Context) {
ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminNotices"] = true
- total := admin_model.CountNotices()
+ total := system_model.CountNotices()
page := ctx.FormInt("page")
if page <= 1 {
page = 1
}
- notices, err := admin_model.Notices(page, setting.UI.Admin.NoticePagingNum)
+ notices, err := system_model.Notices(page, setting.UI.Admin.NoticePagingNum)
if err != nil {
ctx.ServerError("Notices", err)
return
@@ -57,7 +57,7 @@ func DeleteNotices(ctx *context.Context) {
}
}
- if err := admin_model.DeleteNoticesByIDs(ids); err != nil {
+ if err := system_model.DeleteNoticesByIDs(ids); err != nil {
ctx.Flash.Error("DeleteNoticesByIDs: " + err.Error())
ctx.Status(http.StatusInternalServerError)
} else {
@@ -68,7 +68,7 @@ func DeleteNotices(ctx *context.Context) {
// EmptyNotices delete all the notices
func EmptyNotices(ctx *context.Context) {
- if err := admin_model.DeleteNotices(0, 0); err != nil {
+ if err := system_model.DeleteNotices(0, 0); err != nil {
ctx.ServerError("DeleteNotices", err)
return
}
diff --git a/routers/web/repo/middlewares.go b/routers/web/repo/middlewares.go
index ae4177cf1e..c9e8eb4a89 100644
--- a/routers/web/repo/middlewares.go
+++ b/routers/web/repo/middlewares.go
@@ -7,7 +7,7 @@ package repo
import (
"fmt"
- admin_model "code.gitea.io/gitea/models/admin"
+ system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
@@ -24,7 +24,7 @@ func SetEditorconfigIfExists(ctx *context.Context) {
if err != nil && !git.IsErrNotExist(err) {
description := fmt.Sprintf("Error while getting .editorconfig file: %v", err)
- if err := admin_model.CreateRepositoryNotice(description); err != nil {
+ if err := system_model.CreateRepositoryNotice(description); err != nil {
ctx.ServerError("ErrCreatingReporitoryNotice", err)
}
return
diff --git a/routers/web/web.go b/routers/web/web.go
index c01a2bce40..8859ec5850 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -473,8 +473,13 @@ func RegisterRoutes(m *web.Route) {
m.Group("/admin", func() {
m.Get("", adminReq, admin.Dashboard)
m.Post("", adminReq, bindIgnErr(forms.AdminDashboardForm{}), admin.DashboardPost)
- m.Get("/config", admin.Config)
- m.Post("/config/test_mail", admin.SendTestMail)
+
+ m.Group("/config", func() {
+ m.Get("", admin.Config)
+ m.Post("", admin.ChangeConfig)
+ m.Post("/test_mail", admin.SendTestMail)
+ })
+
m.Group("/monitor", func() {
m.Get("", admin.Monitor)
m.Get("/stacktrace", admin.GoroutineStacktrace)
diff --git a/services/cron/tasks.go b/services/cron/tasks.go
index c26e47e0ce..6ff5964d1e 100644
--- a/services/cron/tasks.go
+++ b/services/cron/tasks.go
@@ -10,8 +10,8 @@ import (
"reflect"
"sync"
- admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db"
+ system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log"
@@ -114,7 +114,7 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) {
t.LastDoer = doerName
t.lock.Unlock()
- if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage(translation.NewLocale("en-US"), t.Name, "cancelled", doerName, message)); err != nil {
+ if err := system_model.CreateNotice(ctx, system_model.NoticeTask, config.FormatMessage(translation.NewLocale("en-US"), t.Name, "cancelled", doerName, message)); err != nil {
log.Error("CreateNotice: %v", err)
}
return
@@ -127,7 +127,7 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) {
t.lock.Unlock()
if config.DoNoticeOnSuccess() {
- if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage(translation.NewLocale("en-US"), t.Name, "finished", doerName)); err != nil {
+ if err := system_model.CreateNotice(ctx, system_model.NoticeTask, config.FormatMessage(translation.NewLocale("en-US"), t.Name, "finished", doerName)); err != nil {
log.Error("CreateNotice: %v", err)
}
}
diff --git a/services/cron/tasks_extended.go b/services/cron/tasks_extended.go
index c3455ec327..04b9560be3 100644
--- a/services/cron/tasks_extended.go
+++ b/services/cron/tasks_extended.go
@@ -9,9 +9,9 @@ import (
"time"
activities_model "code.gitea.io/gitea/models/activities"
- "code.gitea.io/gitea/models/admin"
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
+ "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/updatechecker"
@@ -166,7 +166,7 @@ func registerDeleteOldSystemNotices() {
OlderThan: 365 * 24 * time.Hour,
}, func(ctx context.Context, _ *user_model.User, config Config) error {
olderThanConfig := config.(*OlderThanConfig)
- return admin.DeleteOldSystemNotices(olderThanConfig.OlderThan)
+ return system.DeleteOldSystemNotices(olderThanConfig.OlderThan)
})
}
diff --git a/services/issue/issue.go b/services/issue/issue.go
index bbd0278792..69b87686c1 100644
--- a/services/issue/issue.go
+++ b/services/issue/issue.go
@@ -8,12 +8,12 @@ import (
"fmt"
activities_model "code.gitea.io/gitea/models/activities"
- admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo"
+ system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/notification"
@@ -234,7 +234,7 @@ func deleteIssue(issue *issues_model.Issue) error {
}
for i := range issue.Attachments {
- admin_model.RemoveStorageWithNotice(ctx, storage.Attachments, "Delete issue attachment", issue.Attachments[i].RelativePath())
+ system_model.RemoveStorageWithNotice(ctx, storage.Attachments, "Delete issue attachment", issue.Attachments[i].RelativePath())
}
// delete all database data still assigned to this issue
diff --git a/services/migrations/common.go b/services/migrations/common.go
index 305ae89b2d..052975c9e7 100644
--- a/services/migrations/common.go
+++ b/services/migrations/common.go
@@ -8,7 +8,7 @@ import (
"fmt"
"strings"
- admin_model "code.gitea.io/gitea/models/admin"
+ system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration"
@@ -17,7 +17,7 @@ import (
// WarnAndNotice will log the provided message and send a repository notice
func WarnAndNotice(fmtStr string, args ...interface{}) {
log.Warn(fmtStr, args...)
- if err := admin_model.CreateRepositoryNotice(fmt.Sprintf(fmtStr, args...)); err != nil {
+ if err := system_model.CreateRepositoryNotice(fmt.Sprintf(fmtStr, args...)); err != nil {
log.Error("create repository notice failed: ", err)
}
}
diff --git a/services/migrations/migrate.go b/services/migrations/migrate.go
index 040f0aebb1..dfb21b884b 100644
--- a/services/migrations/migrate.go
+++ b/services/migrations/migrate.go
@@ -14,8 +14,8 @@ import (
"strings"
"code.gitea.io/gitea/models"
- admin_model "code.gitea.io/gitea/models/admin"
repo_model "code.gitea.io/gitea/models/repo"
+ system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/hostmatcher"
"code.gitea.io/gitea/modules/log"
@@ -132,7 +132,7 @@ func MigrateRepository(ctx context.Context, doer *user_model.User, ownerName str
if err1 := uploader.Rollback(); err1 != nil {
log.Error("rollback failed: %v", err1)
}
- if err2 := admin_model.CreateRepositoryNotice(fmt.Sprintf("Migrate repository from %s failed: %v", opts.OriginalURL, err)); err2 != nil {
+ if err2 := system_model.CreateRepositoryNotice(fmt.Sprintf("Migrate repository from %s failed: %v", opts.OriginalURL, err)); err2 != nil {
log.Error("create respotiry notice failed: ", err2)
}
return nil, err
diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go
index f4c527bbdc..a72e7f72eb 100644
--- a/services/mirror/mirror_pull.go
+++ b/services/mirror/mirror_pull.go
@@ -10,9 +10,9 @@ import (
"strings"
"time"
- admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
+ system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/lfs"
@@ -188,7 +188,7 @@ func pruneBrokenReferences(ctx context.Context,
log.Error("Failed to prune mirror repository %s%-v references:\nStdout: %s\nStderr: %s\nErr: %v", wiki, m.Repo, stdoutMessage, stderrMessage, pruneErr)
desc := fmt.Sprintf("Failed to prune mirror repository %s'%s' references: %s", wiki, repoPath, stderrMessage)
- if err := admin_model.CreateRepositoryNotice(desc); err != nil {
+ if err := system_model.CreateRepositoryNotice(desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
// this if will only be reached on a successful prune so try to get the mirror again
@@ -267,7 +267,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
if err != nil {
log.Error("SyncMirrors [repo: %-v]: failed to update mirror repository:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
desc := fmt.Sprintf("Failed to update mirror repository '%s': %s", repoPath, stderrMessage)
- if err = admin_model.CreateRepositoryNotice(desc); err != nil {
+ if err = system_model.CreateRepositoryNotice(desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
return nil, false
@@ -356,7 +356,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
if err != nil {
log.Error("SyncMirrors [repo: %-v Wiki]: failed to update mirror repository wiki:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err)
desc := fmt.Sprintf("Failed to update mirror repository wiki '%s': %s", wikiPath, stderrMessage)
- if err = admin_model.CreateRepositoryNotice(desc); err != nil {
+ if err = system_model.CreateRepositoryNotice(desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
return nil, false
@@ -568,7 +568,7 @@ func checkAndUpdateEmptyRepository(m *repo_model.Mirror, gitRepo *git.Repository
if !git.IsErrUnsupportedVersion(err) {
log.Error("Failed to update default branch of underlying git repository %-v. Error: %v", m.Repo, err)
desc := fmt.Sprintf("Failed to uupdate default branch of underlying git repository '%s': %v", m.Repo.RepoPath(), err)
- if err = admin_model.CreateRepositoryNotice(desc); err != nil {
+ if err = system_model.CreateRepositoryNotice(desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
return false
@@ -579,7 +579,7 @@ func checkAndUpdateEmptyRepository(m *repo_model.Mirror, gitRepo *git.Repository
if err := repo_model.UpdateRepositoryCols(db.DefaultContext, m.Repo, "default_branch", "is_empty"); err != nil {
log.Error("Failed to update default branch of repository %-v. Error: %v", m.Repo, err)
desc := fmt.Sprintf("Failed to uupdate default branch of repository '%s': %v", m.Repo.RepoPath(), err)
- if err = admin_model.CreateRepositoryNotice(desc); err != nil {
+ if err = system_model.CreateRepositoryNotice(desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
return false
diff --git a/services/repository/check.go b/services/repository/check.go
index 17bdf2fac1..2417db6a27 100644
--- a/services/repository/check.go
+++ b/services/repository/check.go
@@ -11,9 +11,9 @@ import (
"time"
"code.gitea.io/gitea/models"
- admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo"
+ system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
@@ -42,7 +42,7 @@ func GitFsck(ctx context.Context, timeout time.Duration, args []string) error {
repoPath := repo.RepoPath()
if err := git.Fsck(ctx, repoPath, timeout, args...); err != nil {
log.Warn("Failed to health check repository (%v): %v", repo, err)
- if err = admin_model.CreateRepositoryNotice("Failed to health check repository (%s): %v", repo.FullName(), err); err != nil {
+ if err = system_model.CreateRepositoryNotice("Failed to health check repository (%s): %v", repo.FullName(), err); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
}
@@ -83,7 +83,7 @@ func GitGcRepos(ctx context.Context, timeout time.Duration, args ...string) erro
if err != nil {
log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err)
desc := fmt.Sprintf("Repository garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
- if err = admin_model.CreateRepositoryNotice(desc); err != nil {
+ if err = system_model.CreateRepositoryNotice(desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %v", repo.FullName(), err)
@@ -93,7 +93,7 @@ func GitGcRepos(ctx context.Context, timeout time.Duration, args ...string) erro
if err := repo_module.UpdateRepoSize(ctx, repo); err != nil {
log.Error("Updating size as part of garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err)
desc := fmt.Sprintf("Updating size as part of garbage collection failed for %s. Stdout: %s\nError: %v", repo.RepoPath(), stdout, err)
- if err = admin_model.CreateRepositoryNotice(desc); err != nil {
+ if err = system_model.CreateRepositoryNotice(desc); err != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
return fmt.Errorf("Updating size as part of garbage collection failed in repo: %s: Error: %v", repo.FullName(), err)
@@ -135,7 +135,7 @@ func gatherMissingRepoRecords(ctx context.Context) ([]*repo_model.Repository, er
if strings.HasPrefix(err.Error(), "Aborted gathering missing repo") {
return nil, err
}
- if err2 := admin_model.CreateRepositoryNotice("gatherMissingRepoRecords: %v", err); err2 != nil {
+ if err2 := system_model.CreateRepositoryNotice("gatherMissingRepoRecords: %v", err); err2 != nil {
log.Error("CreateRepositoryNotice: %v", err2)
}
return nil, err
@@ -163,7 +163,7 @@ func DeleteMissingRepositories(ctx context.Context, doer *user_model.User) error
log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil {
log.Error("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err)
- if err2 := admin_model.CreateRepositoryNotice("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil {
+ if err2 := system_model.CreateRepositoryNotice("Failed to DeleteRepository %s [%d]: Error: %v", repo.FullName(), repo.ID, err); err2 != nil {
log.Error("CreateRepositoryNotice: %v", err)
}
}
@@ -191,7 +191,7 @@ func ReinitMissingRepositories(ctx context.Context) error {
log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID)
if err := git.InitRepository(ctx, repo.RepoPath(), true); err != nil {
log.Error("Unable (re)initialize repository %d at %s. Error: %v", repo.ID, repo.RepoPath(), err)
- if err2 := admin_model.CreateRepositoryNotice("InitRepository [%d]: %v", repo.ID, err); err2 != nil {
+ if err2 := system_model.CreateRepositoryNotice("InitRepository [%d]: %v", repo.ID, err); err2 != nil {
log.Error("CreateRepositoryNotice: %v", err2)
}
}
diff --git a/services/repository/repository.go b/services/repository/repository.go
index d530358360..47687d9a73 100644
--- a/services/repository/repository.go
+++ b/services/repository/repository.go
@@ -9,12 +9,12 @@ import (
"fmt"
"code.gitea.io/gitea/models"
- admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization"
packages_model "code.gitea.io/gitea/models/packages"
repo_model "code.gitea.io/gitea/models/repo"
+ system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
@@ -83,8 +83,8 @@ func PushCreateRepo(authUser, owner *user_model.User, repoName string) (*repo_mo
// Init start repository service
func Init() error {
repo_module.LoadRepoConfig()
- admin_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath)
- admin_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repositories", repo_module.LocalCopyPath())
+ system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath)
+ system_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repositories", repo_module.LocalCopyPath())
return initPushQueue()
}
diff --git a/services/user/user.go b/services/user/user.go
index 1edd9294cb..dab9ac61a4 100644
--- a/services/user/user.go
+++ b/services/user/user.go
@@ -13,12 +13,12 @@ import (
"time"
"code.gitea.io/gitea/models"
- admin_model "code.gitea.io/gitea/models/admin"
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
packages_model "code.gitea.io/gitea/models/packages"
repo_model "code.gitea.io/gitea/models/repo"
+ system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/avatar"
"code.gitea.io/gitea/modules/eventsource"
@@ -186,7 +186,7 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error {
path := user_model.UserPath(u.Name)
if err := util.RemoveAll(path); err != nil {
err = fmt.Errorf("Failed to RemoveAll %s: %v", path, err)
- _ = admin_model.CreateNotice(ctx, admin_model.NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err))
+ _ = system_model.CreateNotice(ctx, system_model.NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err))
return err
}
@@ -194,7 +194,7 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error {
avatarPath := u.CustomAvatarRelativePath()
if err := storage.Avatars.Delete(avatarPath); err != nil {
err = fmt.Errorf("Failed to remove %s: %v", avatarPath, err)
- _ = admin_model.CreateNotice(ctx, admin_model.NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err))
+ _ = system_model.CreateNotice(ctx, system_model.NoticeTask, fmt.Sprintf("delete user '%s': %v", u.Name, err))
return err
}
}
diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go
index d2374ade5a..8faada4d59 100644
--- a/services/wiki/wiki.go
+++ b/services/wiki/wiki.go
@@ -12,8 +12,8 @@ import (
"os"
"strings"
- admin_model "code.gitea.io/gitea/models/admin"
repo_model "code.gitea.io/gitea/models/repo"
+ system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
@@ -384,6 +384,6 @@ func DeleteWiki(ctx context.Context, repo *repo_model.Repository) error {
return err
}
- admin_model.RemoveAllWithNotice(ctx, "Delete repository wiki", repo.WikiPath())
+ system_model.RemoveAllWithNotice(ctx, "Delete repository wiki", repo.WikiPath())
return nil
}
diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl
index 0d9432b395..982cfb2800 100644
--- a/templates/admin/config.tmpl
+++ b/templates/admin/config.tmpl
@@ -301,10 +301,18 @@
<div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal">
<dt>{{.locale.Tr "admin.config.disable_gravatar"}}</dt>
- <dd>{{if .DisableGravatar}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
+ <dd>
+ <div class="ui toggle checkbox">
+ <input type="checkbox" name="picture.disable_gravatar" version="{{.SystemSettings.GetVersion "picture.disable_gravatar"}}"{{if .SystemSettings.GetBool "picture.disable_gravatar"}} checked{{end}}>
+ </div>
+ </dd>
<div class="ui divider"></div>
<dt>{{.locale.Tr "admin.config.enable_federated_avatar"}}</dt>
- <dd>{{if .EnableFederatedAvatar}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd>
+ <dd>
+ <div class="ui toggle checkbox">
+ <input type="checkbox" name="picture.enable_federated_avatar" version="{{.SystemSettings.GetVersion "picture.enable_federated_avatar"}}"{{if .SystemSettings.GetBool "picture.enable_federated_avatar"}} checked{{end}}>
+ </div>
+ </dd>
</dl>
</div>
diff --git a/web_src/js/features/admin-common.js b/web_src/js/features/admin/common.js
index 2438fcf62b..2438fcf62b 100644
--- a/web_src/js/features/admin-common.js
+++ b/web_src/js/features/admin/common.js
diff --git a/web_src/js/features/admin/config.js b/web_src/js/features/admin/config.js
new file mode 100644
index 0000000000..f5d8fae8fa
--- /dev/null
+++ b/web_src/js/features/admin/config.js
@@ -0,0 +1,37 @@
+import $ from 'jquery';
+import {showTemporaryTooltip} from '../../modules/tippy.js';
+
+const {appSubUrl, csrfToken, pageData} = window.config;
+
+export function initAdminConfigs() {
+ const isAdminConfigPage = pageData?.adminConfigPage;
+ if (!isAdminConfigPage) return;
+
+ $("input[type='checkbox']").on('change', (e) => {
+ const $this = $(e.currentTarget);
+ $.ajax({
+ url: `${appSubUrl}/admin/config`,
+ type: 'POST',
+ data: {
+ _csrf: csrfToken,
+ key: $this.attr('name'),
+ value: $this.is(':checked'),
+ version: $this.attr('version'),
+ }
+ }).done((resp) => {
+ if (resp) {
+ if (resp.redirect) {
+ window.location.href = resp.redirect;
+ } else if (resp.version) {
+ $this.attr('version', resp.version);
+ } else if (resp.err) {
+ showTemporaryTooltip(e.currentTarget, resp.err);
+ $this.prop('checked', !$this.is(':checked'));
+ }
+ }
+ });
+
+ e.preventDefault();
+ return false;
+ });
+}
diff --git a/web_src/js/features/admin-emails.js b/web_src/js/features/admin/emails.js
index 46fafa7eff..46fafa7eff 100644
--- a/web_src/js/features/admin-emails.js
+++ b/web_src/js/features/admin/emails.js
diff --git a/web_src/js/features/admin-users.js b/web_src/js/features/admin/users.js
index 2221fc4929..2221fc4929 100644
--- a/web_src/js/features/admin-users.js
+++ b/web_src/js/features/admin/users.js
diff --git a/web_src/js/index.js b/web_src/js/index.js
index b13ad0e13a..a829deaf11 100644
--- a/web_src/js/index.js
+++ b/web_src/js/index.js
@@ -16,7 +16,8 @@ import initRepoMigration from './features/repo-migration.js';
import initRepoProject from './features/repo-projects.js';
import initServiceWorker from './features/serviceworker.js';
import initTableSort from './features/tablesort.js';
-import {initAdminUserListSearchForm} from './features/admin-users.js';
+import {initAdminUserListSearchForm} from './features/admin/users.js';
+import {initAdminConfigs} from './features/admin/config.js';
import {initMarkupAnchors} from './markup/anchors.js';
import {initNotificationCount, initNotificationsTable} from './features/notification.js';
import {initRepoIssueContentHistory} from './features/repo-issue-content.js';
@@ -60,8 +61,8 @@ import {
initGlobalTooltips,
} from './features/common-global.js';
import {initRepoTopicBar} from './features/repo-home.js';
-import {initAdminEmails} from './features/admin-emails.js';
-import {initAdminCommon} from './features/admin-common.js';
+import {initAdminEmails} from './features/admin/emails.js';
+import {initAdminCommon} from './features/admin/common.js';
import {initRepoTemplateSearch} from './features/repo-template.js';
import {initRepoCodeView} from './features/repo-code.js';
import {initSshKeyFormParser} from './features/sshkey-helper.js';
@@ -139,6 +140,7 @@ $(document).ready(() => {
initAdminCommon();
initAdminEmails();
initAdminUserListSearchForm();
+ initAdminConfigs();
initDashboardRepoList();
diff --git a/web_src/less/_base.less b/web_src/less/_base.less
index 2abebe5c7d..bfc6e0cf96 100644
--- a/web_src/less/_base.less
+++ b/web_src/less/_base.less
@@ -131,6 +131,7 @@
--color-timeline: #ececec;
--color-input-text: #212121;
--color-input-background: #ffffff;
+ --color-input-toggle-background: #dedede;
--color-input-border: #dedede;
--color-input-border-hover: #cecece;
--color-navbar: #f8f8f8;
diff --git a/web_src/less/_form.less b/web_src/less/_form.less
index 08e1f324b4..3d2ec9fb8a 100644
--- a/web_src/less/_form.less
+++ b/web_src/less/_form.less
@@ -113,7 +113,7 @@ textarea:focus,
}
.ui.toggle.checkbox label::before {
- background: var(--color-input-background);
+ background: var(--color-input-toggle-background);
}
.ui.toggle.checkbox label,
diff --git a/web_src/less/themes/theme-arc-green.less b/web_src/less/themes/theme-arc-green.less
index 12dba79266..fe83162154 100644
--- a/web_src/less/themes/theme-arc-green.less
+++ b/web_src/less/themes/theme-arc-green.less
@@ -106,6 +106,7 @@
--color-timeline: #4c525e;
--color-input-text: #d5dbe6;
--color-input-background: #232933;
+ --color-input-toggle-background: #454a57;
--color-input-border: #454a57;
--color-input-border-hover: #505667;
--color-navbar: #2a2e3a;