Add system setting table with cache and also add cache supports for user setting (#18058)

This commit is contained in:
Lunny Xiao 2022-10-17 07:29:26 +08:00 committed by GitHub
parent 5d3dbffa15
commit f860a6d2e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 1117 additions and 436 deletions

View File

@ -14,7 +14,6 @@ import (
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"net/url"
"os" "os"
"os/exec" "os/exec"
"os/user" "os/user"
@ -62,11 +61,7 @@ func runPR() {
} }
setting.AppWorkPath = curDir setting.AppWorkPath = curDir
setting.StaticRootPath = curDir setting.StaticRootPath = curDir
setting.GravatarSourceURL, err = url.Parse("https://secure.gravatar.com/avatar/") setting.GravatarSource = "https://secure.gravatar.com/avatar/"
if err != nil {
log.Fatalf("url.Parse: %v\n", err)
}
setting.AppURL = "http://localhost:8080/" setting.AppURL = "http://localhost:8080/"
setting.HTTPPort = "8080" setting.HTTPPort = "8080"
setting.SSH.Domain = "localhost" setting.SSH.Domain = "localhost"

View File

@ -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})
}

View File

@ -13,6 +13,7 @@ import (
"sync" "sync"
"code.gitea.io/gitea/models/db" "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/base"
"code.gitea.io/gitea/modules/cache" "code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/log" "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. // 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. // This function should only be called if a federated avatar service is enabled.
func LibravatarURL(email string) (*url.URL, error) { func LibravatarURL(email string) (*url.URL, error) {
urlStr, err := setting.LibravatarService.FromEmail(email) urlStr, err := system_model.LibravatarService.FromEmail(email)
if err != nil { if err != nil {
log.Error("LibravatarService.FromEmail(email=%s): error %v", email, err) log.Error("LibravatarService.FromEmail(email=%s): error %v", email, err)
return nil, err return nil, err
@ -149,8 +150,10 @@ func generateEmailAvatarLink(email string, size int, final bool) string {
return DefaultAvatarLink() return DefaultAvatarLink()
} }
enableFederatedAvatar, _ := system_model.GetSetting(system_model.KeyPictureEnableFederatedAvatar)
var err error var err error
if setting.EnableFederatedAvatar && setting.LibravatarService != nil { if enableFederatedAvatar != nil && enableFederatedAvatar.GetValueBool() && system_model.LibravatarService != nil {
emailHash := saveEmailHash(email) emailHash := saveEmailHash(email)
if final { if final {
// for final link, we can spend more time on slow external query // 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) urlStr += "?size=" + strconv.Itoa(size)
} }
return urlStr 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. // copy GravatarSourceURL, because we will modify its Path.
avatarURLCopy := *setting.GravatarSourceURL avatarURLCopy := *system_model.GravatarSourceURL
avatarURLCopy.Path = path.Join(avatarURLCopy.Path, HashEmail(email)) avatarURLCopy.Path = path.Join(avatarURLCopy.Path, HashEmail(email))
return generateRecognizedAvatarURL(avatarURLCopy, size) return generateRecognizedAvatarURL(avatarURLCopy, size)
} }
return DefaultAvatarLink() return DefaultAvatarLink()
} }

View File

@ -2,12 +2,13 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package avatars package avatars_test
import ( import (
"net/url"
"testing" "testing"
avatars_model "code.gitea.io/gitea/models/avatars"
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -15,40 +16,43 @@ import (
const gravatarSource = "https://secure.gravatar.com/avatar/" const gravatarSource = "https://secure.gravatar.com/avatar/"
func disableGravatar() { func disableGravatar(t *testing.T) {
setting.EnableFederatedAvatar = false err := system_model.SetSettingNoVersion(system_model.KeyPictureEnableFederatedAvatar, "false")
setting.LibravatarService = nil assert.NoError(t, err)
setting.DisableGravatar = true err = system_model.SetSettingNoVersion(system_model.KeyPictureDisableGravatar, "true")
assert.NoError(t, err)
system_model.LibravatarService = nil
} }
func enableGravatar(t *testing.T) { func enableGravatar(t *testing.T) {
setting.DisableGravatar = false err := system_model.SetSettingNoVersion(system_model.KeyPictureDisableGravatar, "false")
var err error assert.NoError(t, err)
setting.GravatarSourceURL, err = url.Parse(gravatarSource) setting.GravatarSource = gravatarSource
err = system_model.Init()
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestHashEmail(t *testing.T) { func TestHashEmail(t *testing.T) {
assert.Equal(t, assert.Equal(t,
"d41d8cd98f00b204e9800998ecf8427e", "d41d8cd98f00b204e9800998ecf8427e",
HashEmail(""), avatars_model.HashEmail(""),
) )
assert.Equal(t, assert.Equal(t,
"353cbad9b58e69c96154ad99f92bedc7", "353cbad9b58e69c96154ad99f92bedc7",
HashEmail("gitea@example.com"), avatars_model.HashEmail("gitea@example.com"),
) )
} }
func TestSizedAvatarLink(t *testing.T) { func TestSizedAvatarLink(t *testing.T) {
setting.AppSubURL = "/testsuburl" setting.AppSubURL = "/testsuburl"
disableGravatar() disableGravatar(t)
assert.Equal(t, "/testsuburl/assets/img/avatar_default.png", assert.Equal(t, "/testsuburl/assets/img/avatar_default.png",
GenerateEmailAvatarFastLink("gitea@example.com", 100)) avatars_model.GenerateEmailAvatarFastLink("gitea@example.com", 100))
enableGravatar(t) enableGravatar(t)
assert.Equal(t, assert.Equal(t,
"https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon&s=100", "https://secure.gravatar.com/avatar/353cbad9b58e69c96154ad99f92bedc7?d=identicon&s=100",
GenerateEmailAvatarFastLink("gitea@example.com", 100), avatars_model.GenerateEmailAvatarFastLink("gitea@example.com", 100),
) )
} }

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package admin_test package avatars_test
import ( import (
"path/filepath" "path/filepath"

View File

@ -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

View File

@ -13,7 +13,6 @@ import (
"strconv" "strconv"
"strings" "strings"
admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/foreignreference" "code.gitea.io/gitea/models/foreignreference"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
@ -21,6 +20,7 @@ import (
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project" project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
@ -2470,7 +2470,7 @@ func DeleteOrphanedIssues() error {
// Remove issue attachment files. // Remove issue attachment files.
for i := range attachmentPaths { 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 return nil
} }

View File

@ -14,6 +14,8 @@ import (
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
_ "code.gitea.io/gitea/models/system"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )

View File

@ -415,6 +415,8 @@ var migrations = []Migration{
NewMigration("Alter gpg_key/public_key content TEXT fields to MEDIUMTEXT", alterPublicGPGKeyContentFieldsToMediumText), NewMigration("Alter gpg_key/public_key content TEXT fields to MEDIUMTEXT", alterPublicGPGKeyContentFieldsToMediumText),
// v226 -> v227 // v226 -> v227
NewMigration("Conan and generic packages do not need to be semantically versioned", fixPackageSemverField), 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 // GetCurrentDBVersion returns the current db version

64
models/migrations/v227.go Normal file
View File

@ -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)
}

View File

@ -22,6 +22,7 @@ import (
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project" project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/models/webhook"
@ -32,9 +33,13 @@ import (
"xorm.io/builder" "xorm.io/builder"
) )
// NewRepoContext creates a new repository context // ItemsPerPage maximum items per page in forks, watchers and stars of a repo
func NewRepoContext() { var ItemsPerPage = 40
// Init initialize model
func Init() error {
unit.LoadUnitConfig() unit.LoadUnitConfig()
return system_model.Init()
} }
// DeleteRepository deletes a repository for a user or organization. // 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. // Remove repository files.
repoPath := repo.RepoPath() repoPath := repo.RepoPath()
admin_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository files", repoPath) system_model.RemoveAllWithNotice(db.DefaultContext, "Delete repository files", repoPath)
// Remove wiki files // Remove wiki files
if repo.HasWiki() { 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 // Remove archives
for _, archive := range archivePaths { 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 // Remove lfs objects
for _, lfsObj := range lfsPaths { 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. // Remove issue attachment files.
for _, attachment := range attachmentPaths { 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. // Remove release attachment files.
for _, releaseAttachment := range releaseAttachments { 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. // Remove attachment with no issue_id and release_id.
for _, newAttachment := range newAttachmentPaths { 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 { if len(repo.Avatar) > 0 {

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package appstate package system
import ( import (
"context" "context"

View File

@ -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("..", ".."),
})
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package admin package system
import ( import (
"context" "context"

View File

@ -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})
}

261
models/system/setting.go Normal file
View File

@ -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
}

View File

@ -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"
)

View File

@ -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)
}

View File

@ -7,12 +7,12 @@ package unittest
import ( import (
"context" "context"
"fmt" "fmt"
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"
"code.gitea.io/gitea/models/db" "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/base"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -91,10 +91,8 @@ func MainTest(m *testing.M, testOpts *TestOptions) {
setting.AppDataPath = appDataPath setting.AppDataPath = appDataPath
setting.AppWorkPath = testOpts.GiteaRootPath setting.AppWorkPath = testOpts.GiteaRootPath
setting.StaticRootPath = testOpts.GiteaRootPath setting.StaticRootPath = testOpts.GiteaRootPath
setting.GravatarSourceURL, err = url.Parse("https://secure.gravatar.com/avatar/") setting.GravatarSource = "https://secure.gravatar.com/avatar/"
if err != nil {
fatalTestError("url.Parse: %v\n", err)
}
setting.Attachment.Storage.Path = filepath.Join(setting.AppDataPath, "attachments") setting.Attachment.Storage.Path = filepath.Join(setting.AppDataPath, "attachments")
setting.LFS.Storage.Path = filepath.Join(setting.AppDataPath, "lfs") 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 { if err = storage.Init(); err != nil {
fatalTestError("storage.Init: %v\n", err) 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 { if err = util.RemoveAll(repoRootPath); err != nil {
fatalTestError("util.RemoveAll: %v\n", err) fatalTestError("util.RemoveAll: %v\n", err)

View File

@ -14,6 +14,7 @@ import (
"code.gitea.io/gitea/models/avatars" "code.gitea.io/gitea/models/avatars"
"code.gitea.io/gitea/models/db" "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/avatar"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -67,10 +68,16 @@ func (u *User) AvatarLinkWithSize(size int) string {
useLocalAvatar := false useLocalAvatar := false
autoGenerateAvatar := false autoGenerateAvatar := false
var disableGravatar bool
disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
if disableGravatarSetting != nil {
disableGravatar = disableGravatarSetting.GetValueBool()
}
switch { switch {
case u.UseCustomAvatar: case u.UseCustomAvatar:
useLocalAvatar = true useLocalAvatar = true
case setting.DisableGravatar, setting.OfflineMode: case disableGravatar, setting.OfflineMode:
useLocalAvatar = true useLocalAvatar = true
autoGenerateAvatar = true autoGenerateAvatar = true
} }

View File

@ -31,6 +31,34 @@ func init() {
db.RegisterModel(new(Setting)) 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 // GetUserSettings returns specific settings from user
func GetUserSettings(uid int64, keys []string) (map[string]*Setting, error) { func GetUserSettings(uid int64, keys []string) (map[string]*Setting, error) {
settings := make([]*Setting, 0, len(keys)) settings := make([]*Setting, 0, len(keys))

View File

@ -11,8 +11,10 @@ import (
"time" "time"
repo_model "code.gitea.io/gitea/models/repo" 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/models/unittest"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -100,6 +102,14 @@ func TestPushCommits_ToAPIPayloadCommits(t *testing.T) {
assert.EqualValues(t, []string{"readme.md"}, headCommit.Modified) 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) { func TestPushCommits_AvatarLink(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase()) assert.NoError(t, unittest.PrepareTestDatabase())
@ -123,6 +133,8 @@ func TestPushCommits_AvatarLink(t *testing.T) {
}, },
} }
enableGravatar(t)
assert.Equal(t, assert.Equal(t,
"https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon&s=84", "https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon&s=84",
pushCommits.AvatarLink("user2@example.com")) pushCommits.AvatarLink("user2@example.com"))

View File

@ -4,14 +4,6 @@
package setting package setting
import (
"net/url"
"code.gitea.io/gitea/modules/log"
"strk.kbt.io/projects/go/libravatar"
)
// settings // settings
var ( var (
// Picture settings // Picture settings
@ -30,10 +22,8 @@ var (
} }
GravatarSource string GravatarSource string
GravatarSourceURL *url.URL DisableGravatar bool // Depreciated: migrated to database
DisableGravatar bool EnableFederatedAvatar bool // Depreciated: migrated to database
EnableFederatedAvatar bool
LibravatarService *libravatar.Libravatar
RepoAvatar = struct { RepoAvatar = struct {
Storage Storage
@ -69,38 +59,30 @@ func newPictureService() {
default: default:
GravatarSource = source 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 { DisableGravatar = sec.Key("DISABLE_GRAVATAR").MustBool(GetDefaultDisableGravatar())
LibravatarService = libravatar.New() deprecatedSettingDB("", "DISABLE_GRAVATAR")
if GravatarSourceURL.Scheme == "https" { EnableFederatedAvatar = sec.Key("ENABLE_FEDERATED_AVATAR").MustBool(GetDefaultEnableFederatedAvatar(DisableGravatar))
LibravatarService.SetUseHTTPS(true) deprecatedSettingDB("", "ENABLE_FEDERATED_AVATAR")
LibravatarService.SetSecureFallbackHost(GravatarSourceURL.Host)
} else {
LibravatarService.SetUseHTTPS(false)
LibravatarService.SetFallbackHost(GravatarSourceURL.Host)
}
}
newRepoAvatarService() 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() { func newRepoAvatarService() {
sec := Cfg.Section("picture") sec := Cfg.Section("picture")

View File

@ -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. // loadFromConf initializes configuration context.
// NOTE: do not print any log except error. // NOTE: do not print any log except error.
func loadFromConf(allowEmpty bool, extraConfig string) { func loadFromConf(allowEmpty bool, extraConfig string) {

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package appstate package system
// StateStore is the interface to get/set app state items // StateStore is the interface to get/set app state items
type StateStore interface { type StateStore interface {

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package appstate package system
import ( import (
"path/filepath" "path/filepath"

View File

@ -2,10 +2,10 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package appstate package system
import ( import (
"code.gitea.io/gitea/models/appstate" "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"github.com/yuin/goldmark/util" "github.com/yuin/goldmark/util"
@ -16,7 +16,7 @@ type DBStore struct{}
// Get reads the state item // Get reads the state item
func (f *DBStore) Get(item StateItem) error { func (f *DBStore) Get(item StateItem) error {
content, err := appstate.GetAppStateContent(item.Name()) content, err := system.GetAppStateContent(item.Name())
if err != nil { if err != nil {
return err return err
} }
@ -32,5 +32,5 @@ func (f *DBStore) Set(item StateItem) error {
if err != nil { if err != nil {
return err return err
} }
return appstate.SaveAppStateContent(item.Name(), util.BytesToReadOnlyString(b)) return system.SaveAppStateContent(item.Name(), util.BytesToReadOnlyString(b))
} }

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // 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 // RuntimeState contains app state for runtime, and we can save remote version for update checker here in future
type RuntimeState struct { type RuntimeState struct {

46
modules/system/setting.go Normal file
View File

@ -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,
})
}

View File

@ -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)
}

View File

@ -29,6 +29,7 @@ import (
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/emoji" "code.gitea.io/gitea/modules/emoji"
@ -41,6 +42,7 @@ import (
"code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/repository"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/svg" "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/timeutil"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/services/gitdiff" "code.gitea.io/gitea/services/gitdiff"
@ -85,7 +87,7 @@ func NewFuncMap() []template.FuncMap {
return setting.AssetVersion return setting.AssetVersion
}, },
"DisableGravatar": func() bool { "DisableGravatar": func() bool {
return setting.DisableGravatar return system_module.GetSettingBool(system_model.KeyPictureDisableGravatar)
}, },
"DefaultShowFullName": func() bool { "DefaultShowFullName": func() bool {
return setting.UI.DefaultShowFullName return setting.UI.DefaultShowFullName

View File

@ -8,10 +8,10 @@ import (
"io" "io"
"net/http" "net/http"
"code.gitea.io/gitea/modules/appstate"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/proxy" "code.gitea.io/gitea/modules/proxy"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/system"
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"
) )
@ -64,13 +64,13 @@ func GiteaUpdateChecker(httpEndpoint string) error {
// UpdateRemoteVersion updates the latest available version of Gitea // UpdateRemoteVersion updates the latest available version of Gitea
func UpdateRemoteVersion(version string) (err error) { 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) // GetRemoteVersion returns the current remote version (or currently installed version if fail to fetch from DB)
func GetRemoteVersion() string { func GetRemoteVersion() string {
item := new(CheckerState) item := new(CheckerState)
if err := appstate.AppState.Get(item); err != nil { if err := system.AppState.Get(item); err != nil {
return "" return ""
} }
return item.LatestVersion return item.LatestVersion

View File

@ -2879,6 +2879,9 @@ config.access_log_template = Template
config.xorm_log_mode = XORM Log Mode config.xorm_log_mode = XORM Log Mode
config.xorm_log_sql = Log SQL 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.cron = Cron Tasks
monitor.name = Name monitor.name = Name
monitor.schedule = Schedule monitor.schedule = Schedule

View File

@ -11,7 +11,6 @@ import (
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
asymkey_model "code.gitea.io/gitea/models/asymkey" 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/cache"
"code.gitea.io/gitea/modules/eventsource" "code.gitea.io/gitea/modules/eventsource"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
@ -27,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/ssh" "code.gitea.io/gitea/modules/ssh"
"code.gitea.io/gitea/modules/storage" "code.gitea.io/gitea/modules/storage"
"code.gitea.io/gitea/modules/svg" "code.gitea.io/gitea/modules/svg"
"code.gitea.io/gitea/modules/system"
"code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
@ -76,8 +76,8 @@ func InitGitServices() {
} }
func syncAppPathForGit(ctx context.Context) error { func syncAppPathForGit(ctx context.Context) error {
runtimeState := new(appstate.RuntimeState) runtimeState := new(system.RuntimeState)
if err := appstate.AppState.Get(runtimeState); err != nil { if err := system.AppState.Get(runtimeState); err != nil {
return err return err
} }
if runtimeState.LastAppPath != setting.AppPath { if runtimeState.LastAppPath != setting.AppPath {
@ -90,7 +90,7 @@ func syncAppPathForGit(ctx context.Context) error {
mustInit(asymkey_model.RewriteAllPublicKeys) mustInit(asymkey_model.RewriteAllPublicKeys)
runtimeState.LastAppPath = setting.AppPath runtimeState.LastAppPath = setting.AppPath
return appstate.AppState.Set(runtimeState) return system.AppState.Set(runtimeState)
} }
return nil return nil
} }
@ -133,10 +133,10 @@ func GlobalInitInstalled(ctx context.Context) {
mustInitCtx(ctx, common.InitDBEngine) mustInitCtx(ctx, common.InitDBEngine)
log.Info("ORM engine initialization successful!") log.Info("ORM engine initialization successful!")
mustInit(appstate.Init) mustInit(system.Init)
mustInit(oauth2.Init) mustInit(oauth2.Init)
models.NewRepoContext() mustInit(models.Init)
mustInit(repo_service.Init) mustInit(repo_service.Init)
// Booting long running goroutines. // Booting long running goroutines.

View File

@ -12,12 +12,14 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"time" "time"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
db_install "code.gitea.io/gitea/models/db/install" db_install "code.gitea.io/gitea/models/db/install"
"code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/models/migrations"
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
@ -147,8 +149,19 @@ func Install(ctx *context.Context) {
// Server and other services settings // Server and other services settings
form.OfflineMode = setting.OfflineMode form.OfflineMode = setting.OfflineMode
form.DisableGravatar = setting.DisableGravatar disableGravatarSetting, _ := system_model.GetSetting(system_model.KeyPictureDisableGravatar)
form.EnableFederatedAvatar = setting.EnableFederatedAvatar 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.EnableOpenIDSignIn = setting.Service.EnableOpenIDSignIn
form.EnableOpenIDSignUp = setting.Service.EnableOpenIDSignUp form.EnableOpenIDSignUp = setting.Service.EnableOpenIDSignUp
form.DisableRegistration = setting.Service.DisableRegistration 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("service").Key("ENABLE_NOTIFY_MAIL").SetValue(fmt.Sprint(form.MailNotify))
cfg.Section("server").Key("OFFLINE_MODE").SetValue(fmt.Sprint(form.OfflineMode)) 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("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_SIGNIN").SetValue(fmt.Sprint(form.EnableOpenIDSignIn))
cfg.Section("openid").Key("ENABLE_OPENID_SIGNUP").SetValue(fmt.Sprint(form.EnableOpenIDSignUp)) cfg.Section("openid").Key("ENABLE_OPENID_SIGNUP").SetValue(fmt.Sprint(form.EnableOpenIDSignUp))

View File

@ -8,18 +8,13 @@ package admin
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"os"
"runtime" "runtime"
"strconv" "strconv"
"strings"
"time" "time"
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/context" "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/log"
"code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/queue"
@ -27,18 +22,13 @@ import (
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation" "code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/updatechecker" "code.gitea.io/gitea/modules/updatechecker"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/cron" "code.gitea.io/gitea/services/cron"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
"code.gitea.io/gitea/services/mailer"
"gitea.com/go-chi/session"
) )
const ( const (
tplDashboard base.TplName = "admin/dashboard" tplDashboard base.TplName = "admin/dashboard"
tplConfig base.TplName = "admin/config"
tplMonitor base.TplName = "admin/monitor" tplMonitor base.TplName = "admin/monitor"
tplStacktrace base.TplName = "admin/stacktrace" tplStacktrace base.TplName = "admin/stacktrace"
tplQueue base.TplName = "admin/queue" 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 // Monitor show admin monitor page
func Monitor(ctx *context.Context) { func Monitor(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("admin.monitor") ctx.Data["Title"] = ctx.Tr("admin.monitor")

217
routers/web/admin/config.go Normal file
View File

@ -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,
})
}

View File

@ -9,7 +9,7 @@ import (
"net/http" "net/http"
"strconv" "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/base"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -26,13 +26,13 @@ func Notices(ctx *context.Context) {
ctx.Data["PageIsAdmin"] = true ctx.Data["PageIsAdmin"] = true
ctx.Data["PageIsAdminNotices"] = true ctx.Data["PageIsAdminNotices"] = true
total := admin_model.CountNotices() total := system_model.CountNotices()
page := ctx.FormInt("page") page := ctx.FormInt("page")
if page <= 1 { if page <= 1 {
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 { if err != nil {
ctx.ServerError("Notices", err) ctx.ServerError("Notices", err)
return 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.Flash.Error("DeleteNoticesByIDs: " + err.Error())
ctx.Status(http.StatusInternalServerError) ctx.Status(http.StatusInternalServerError)
} else { } else {
@ -68,7 +68,7 @@ func DeleteNotices(ctx *context.Context) {
// EmptyNotices delete all the notices // EmptyNotices delete all the notices
func EmptyNotices(ctx *context.Context) { 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) ctx.ServerError("DeleteNotices", err)
return return
} }

View File

@ -7,7 +7,7 @@ package repo
import ( import (
"fmt" "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" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
@ -24,7 +24,7 @@ func SetEditorconfigIfExists(ctx *context.Context) {
if err != nil && !git.IsErrNotExist(err) { if err != nil && !git.IsErrNotExist(err) {
description := fmt.Sprintf("Error while getting .editorconfig file: %v", 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) ctx.ServerError("ErrCreatingReporitoryNotice", err)
} }
return return

View File

@ -473,8 +473,13 @@ func RegisterRoutes(m *web.Route) {
m.Group("/admin", func() { m.Group("/admin", func() {
m.Get("", adminReq, admin.Dashboard) m.Get("", adminReq, admin.Dashboard)
m.Post("", adminReq, bindIgnErr(forms.AdminDashboardForm{}), admin.DashboardPost) 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.Group("/monitor", func() {
m.Get("", admin.Monitor) m.Get("", admin.Monitor)
m.Get("/stacktrace", admin.GoroutineStacktrace) m.Get("/stacktrace", admin.GoroutineStacktrace)

View File

@ -10,8 +10,8 @@ import (
"reflect" "reflect"
"sync" "sync"
admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/graceful"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -114,7 +114,7 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) {
t.LastDoer = doerName t.LastDoer = doerName
t.lock.Unlock() t.lock.Unlock()
if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage(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) log.Error("CreateNotice: %v", err)
} }
return return
@ -127,7 +127,7 @@ func (t *Task) RunWithUser(doer *user_model.User, config Config) {
t.lock.Unlock() t.lock.Unlock()
if config.DoNoticeOnSuccess() { if config.DoNoticeOnSuccess() {
if err := admin_model.CreateNotice(ctx, admin_model.NoticeTask, config.FormatMessage(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) log.Error("CreateNotice: %v", err)
} }
} }

View File

@ -9,9 +9,9 @@ import (
"time" "time"
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
"code.gitea.io/gitea/models/admin"
asymkey_model "code.gitea.io/gitea/models/asymkey" asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/updatechecker" "code.gitea.io/gitea/modules/updatechecker"
@ -166,7 +166,7 @@ func registerDeleteOldSystemNotices() {
OlderThan: 365 * 24 * time.Hour, OlderThan: 365 * 24 * time.Hour,
}, func(ctx context.Context, _ *user_model.User, config Config) error { }, func(ctx context.Context, _ *user_model.User, config Config) error {
olderThanConfig := config.(*OlderThanConfig) olderThanConfig := config.(*OlderThanConfig)
return admin.DeleteOldSystemNotices(olderThanConfig.OlderThan) return system.DeleteOldSystemNotices(olderThanConfig.OlderThan)
}) })
} }

View File

@ -8,12 +8,12 @@ import (
"fmt" "fmt"
activities_model "code.gitea.io/gitea/models/activities" activities_model "code.gitea.io/gitea/models/activities"
admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
access_model "code.gitea.io/gitea/models/perm/access" access_model "code.gitea.io/gitea/models/perm/access"
project_model "code.gitea.io/gitea/models/project" project_model "code.gitea.io/gitea/models/project"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/notification" "code.gitea.io/gitea/modules/notification"
@ -234,7 +234,7 @@ func deleteIssue(issue *issues_model.Issue) error {
} }
for i := range issue.Attachments { 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 // delete all database data still assigned to this issue

View File

@ -8,7 +8,7 @@ import (
"fmt" "fmt"
"strings" "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/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
base "code.gitea.io/gitea/modules/migration" base "code.gitea.io/gitea/modules/migration"
@ -17,7 +17,7 @@ import (
// WarnAndNotice will log the provided message and send a repository notice // WarnAndNotice will log the provided message and send a repository notice
func WarnAndNotice(fmtStr string, args ...interface{}) { func WarnAndNotice(fmtStr string, args ...interface{}) {
log.Warn(fmtStr, args...) 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) log.Error("create repository notice failed: ", err)
} }
} }

View File

@ -14,8 +14,8 @@ import (
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
admin_model "code.gitea.io/gitea/models/admin"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/hostmatcher" "code.gitea.io/gitea/modules/hostmatcher"
"code.gitea.io/gitea/modules/log" "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 { if err1 := uploader.Rollback(); err1 != nil {
log.Error("rollback failed: %v", err1) 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) log.Error("create respotiry notice failed: ", err2)
} }
return nil, err return nil, err

View File

@ -10,9 +10,9 @@ import (
"strings" "strings"
"time" "time"
admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" 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/cache"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/lfs" "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) 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) 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) log.Error("CreateRepositoryNotice: %v", err)
} }
// this if will only be reached on a successful prune so try to get the mirror again // 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 { if err != nil {
log.Error("SyncMirrors [repo: %-v]: failed to update mirror repository:\nStdout: %s\nStderr: %s\nErr: %v", m.Repo, stdoutMessage, stderrMessage, err) 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) 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) log.Error("CreateRepositoryNotice: %v", err)
} }
return nil, false return nil, false
@ -356,7 +356,7 @@ func runSync(ctx context.Context, m *repo_model.Mirror) ([]*mirrorSyncResult, bo
if err != nil { 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) 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) 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) log.Error("CreateRepositoryNotice: %v", err)
} }
return nil, false return nil, false
@ -568,7 +568,7 @@ func checkAndUpdateEmptyRepository(m *repo_model.Mirror, gitRepo *git.Repository
if !git.IsErrUnsupportedVersion(err) { if !git.IsErrUnsupportedVersion(err) {
log.Error("Failed to update default branch of underlying git repository %-v. Error: %v", m.Repo, 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) 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) log.Error("CreateRepositoryNotice: %v", err)
} }
return false 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 { 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) 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) 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) log.Error("CreateRepositoryNotice: %v", err)
} }
return false return false

View File

@ -11,9 +11,9 @@ import (
"time" "time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
@ -42,7 +42,7 @@ func GitFsck(ctx context.Context, timeout time.Duration, args []string) error {
repoPath := repo.RepoPath() repoPath := repo.RepoPath()
if err := git.Fsck(ctx, repoPath, timeout, args...); err != nil { if err := git.Fsck(ctx, repoPath, timeout, args...); err != nil {
log.Warn("Failed to health check repository (%v): %v", repo, err) 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) log.Error("CreateRepositoryNotice: %v", err)
} }
} }
@ -83,7 +83,7 @@ func GitGcRepos(ctx context.Context, timeout time.Duration, args ...string) erro
if err != nil { if err != nil {
log.Error("Repository garbage collection failed for %v. Stdout: %s\nError: %v", repo, stdout, err) 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) 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) log.Error("CreateRepositoryNotice: %v", err)
} }
return fmt.Errorf("Repository garbage collection failed in repo: %s: Error: %v", repo.FullName(), 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 { 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) 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) 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) log.Error("CreateRepositoryNotice: %v", err)
} }
return fmt.Errorf("Updating size as part of garbage collection failed in repo: %s: Error: %v", repo.FullName(), 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") { if strings.HasPrefix(err.Error(), "Aborted gathering missing repo") {
return nil, err 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) log.Error("CreateRepositoryNotice: %v", err2)
} }
return nil, err 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) log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
if err := models.DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil { 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) 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) log.Error("CreateRepositoryNotice: %v", err)
} }
} }
@ -191,7 +191,7 @@ func ReinitMissingRepositories(ctx context.Context) error {
log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID) log.Trace("Initializing %d/%d...", repo.OwnerID, repo.ID)
if err := git.InitRepository(ctx, repo.RepoPath(), true); err != nil { 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) 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) log.Error("CreateRepositoryNotice: %v", err2)
} }
} }

View File

@ -9,12 +9,12 @@ import (
"fmt" "fmt"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
admin_model "code.gitea.io/gitea/models/admin"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
issues_model "code.gitea.io/gitea/models/issues" issues_model "code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log" "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 // Init start repository service
func Init() error { func Init() error {
repo_module.LoadRepoConfig() repo_module.LoadRepoConfig()
admin_model.RemoveAllWithNotice(db.DefaultContext, "Clean up temporary repository uploads", setting.Repository.Upload.TempPath) system_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 repositories", repo_module.LocalCopyPath())
return initPushQueue() return initPushQueue()
} }

View File

@ -13,12 +13,12 @@ import (
"time" "time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
admin_model "code.gitea.io/gitea/models/admin"
asymkey_model "code.gitea.io/gitea/models/asymkey" asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization" "code.gitea.io/gitea/models/organization"
packages_model "code.gitea.io/gitea/models/packages" packages_model "code.gitea.io/gitea/models/packages"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/avatar" "code.gitea.io/gitea/modules/avatar"
"code.gitea.io/gitea/modules/eventsource" "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) path := user_model.UserPath(u.Name)
if err := util.RemoveAll(path); err != nil { if err := util.RemoveAll(path); err != nil {
err = fmt.Errorf("Failed to RemoveAll %s: %v", path, err) 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 return err
} }
@ -194,7 +194,7 @@ func DeleteUser(ctx context.Context, u *user_model.User, purge bool) error {
avatarPath := u.CustomAvatarRelativePath() avatarPath := u.CustomAvatarRelativePath()
if err := storage.Avatars.Delete(avatarPath); err != nil { if err := storage.Avatars.Delete(avatarPath); err != nil {
err = fmt.Errorf("Failed to remove %s: %v", avatarPath, err) 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 return err
} }
} }

View File

@ -12,8 +12,8 @@ import (
"os" "os"
"strings" "strings"
admin_model "code.gitea.io/gitea/models/admin"
repo_model "code.gitea.io/gitea/models/repo" repo_model "code.gitea.io/gitea/models/repo"
system_model "code.gitea.io/gitea/models/system"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
@ -384,6 +384,6 @@ func DeleteWiki(ctx context.Context, repo *repo_model.Repository) error {
return err return err
} }
admin_model.RemoveAllWithNotice(ctx, "Delete repository wiki", repo.WikiPath()) system_model.RemoveAllWithNotice(ctx, "Delete repository wiki", repo.WikiPath())
return nil return nil
} }

View File

@ -301,10 +301,18 @@
<div class="ui attached table segment"> <div class="ui attached table segment">
<dl class="dl-horizontal admin-dl-horizontal"> <dl class="dl-horizontal admin-dl-horizontal">
<dt>{{.locale.Tr "admin.config.disable_gravatar"}}</dt> <dt>{{.locale.Tr "admin.config.disable_gravatar"}}</dt>
<dd>{{if .DisableGravatar}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>
<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> <div class="ui divider"></div>
<dt>{{.locale.Tr "admin.config.enable_federated_avatar"}}</dt> <dt>{{.locale.Tr "admin.config.enable_federated_avatar"}}</dt>
<dd>{{if .EnableFederatedAvatar}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</dd> <dd>
<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> </dl>
</div> </div>

View File

@ -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;
});
}

View File

@ -16,7 +16,8 @@ import initRepoMigration from './features/repo-migration.js';
import initRepoProject from './features/repo-projects.js'; import initRepoProject from './features/repo-projects.js';
import initServiceWorker from './features/serviceworker.js'; import initServiceWorker from './features/serviceworker.js';
import initTableSort from './features/tablesort.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 {initMarkupAnchors} from './markup/anchors.js';
import {initNotificationCount, initNotificationsTable} from './features/notification.js'; import {initNotificationCount, initNotificationsTable} from './features/notification.js';
import {initRepoIssueContentHistory} from './features/repo-issue-content.js'; import {initRepoIssueContentHistory} from './features/repo-issue-content.js';
@ -60,8 +61,8 @@ import {
initGlobalTooltips, initGlobalTooltips,
} from './features/common-global.js'; } from './features/common-global.js';
import {initRepoTopicBar} from './features/repo-home.js'; import {initRepoTopicBar} from './features/repo-home.js';
import {initAdminEmails} from './features/admin-emails.js'; import {initAdminEmails} from './features/admin/emails.js';
import {initAdminCommon} from './features/admin-common.js'; import {initAdminCommon} from './features/admin/common.js';
import {initRepoTemplateSearch} from './features/repo-template.js'; import {initRepoTemplateSearch} from './features/repo-template.js';
import {initRepoCodeView} from './features/repo-code.js'; import {initRepoCodeView} from './features/repo-code.js';
import {initSshKeyFormParser} from './features/sshkey-helper.js'; import {initSshKeyFormParser} from './features/sshkey-helper.js';
@ -139,6 +140,7 @@ $(document).ready(() => {
initAdminCommon(); initAdminCommon();
initAdminEmails(); initAdminEmails();
initAdminUserListSearchForm(); initAdminUserListSearchForm();
initAdminConfigs();
initDashboardRepoList(); initDashboardRepoList();

View File

@ -131,6 +131,7 @@
--color-timeline: #ececec; --color-timeline: #ececec;
--color-input-text: #212121; --color-input-text: #212121;
--color-input-background: #ffffff; --color-input-background: #ffffff;
--color-input-toggle-background: #dedede;
--color-input-border: #dedede; --color-input-border: #dedede;
--color-input-border-hover: #cecece; --color-input-border-hover: #cecece;
--color-navbar: #f8f8f8; --color-navbar: #f8f8f8;

View File

@ -113,7 +113,7 @@ textarea:focus,
} }
.ui.toggle.checkbox label::before { .ui.toggle.checkbox label::before {
background: var(--color-input-background); background: var(--color-input-toggle-background);
} }
.ui.toggle.checkbox label, .ui.toggle.checkbox label,

View File

@ -106,6 +106,7 @@
--color-timeline: #4c525e; --color-timeline: #4c525e;
--color-input-text: #d5dbe6; --color-input-text: #d5dbe6;
--color-input-background: #232933; --color-input-background: #232933;
--color-input-toggle-background: #454a57;
--color-input-border: #454a57; --color-input-border: #454a57;
--color-input-border-hover: #505667; --color-input-border-hover: #505667;
--color-navbar: #2a2e3a; --color-navbar: #2a2e3a;