Instance-wide (global) info banner and maintenance mode (#36571)
The banner allows site operators to communicate important announcements (e.g., maintenance windows, policy updates, service notices) directly within the UI. The maintenance mode only allows admin to access the web UI. * Fix #2345 * Fix #9618 --------- Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
+25
-53
@@ -5,9 +5,9 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
system_model "code.gitea.io/gitea/models/system"
|
||||
@@ -145,7 +145,6 @@ func Config(ctx *context.Context) {
|
||||
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
|
||||
@@ -191,52 +190,27 @@ func ConfigSettings(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("admin.config_settings")
|
||||
ctx.Data["PageIsAdminConfig"] = true
|
||||
ctx.Data["PageIsAdminConfigSettings"] = true
|
||||
ctx.Data["DefaultOpenWithEditorAppsString"] = setting.DefaultOpenWithEditorApps().ToTextareaString()
|
||||
ctx.HTML(http.StatusOK, tplConfigSettings)
|
||||
}
|
||||
|
||||
func validateConfigKeyValue(dynKey, input string) error {
|
||||
opt := config.GetConfigOption(dynKey)
|
||||
if opt == nil {
|
||||
return util.NewInvalidArgumentErrorf("unknown config key: %s", dynKey)
|
||||
}
|
||||
|
||||
const limit = 64 * 1024
|
||||
if len(input) > limit {
|
||||
return util.NewInvalidArgumentErrorf("value length exceeds limit of %d", limit)
|
||||
}
|
||||
|
||||
if !json.Valid([]byte(input)) {
|
||||
return util.NewInvalidArgumentErrorf("invalid json value for key: %s", dynKey)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ChangeConfig(ctx *context.Context) {
|
||||
cfg := setting.Config()
|
||||
|
||||
marshalBool := func(v string) ([]byte, error) {
|
||||
b, _ := strconv.ParseBool(v)
|
||||
return json.Marshal(b)
|
||||
}
|
||||
|
||||
marshalString := func(emptyDefault string) func(v string) ([]byte, error) {
|
||||
return func(v string) ([]byte, error) {
|
||||
return json.Marshal(util.IfZero(v, emptyDefault))
|
||||
}
|
||||
}
|
||||
|
||||
marshalOpenWithApps := func(value string) ([]byte, error) {
|
||||
// TODO: move the block alongside OpenWithEditorAppsType.ToTextareaString
|
||||
lines := strings.Split(value, "\n")
|
||||
var openWithEditorApps setting.OpenWithEditorAppsType
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
displayName, openURL, ok := strings.Cut(line, "=")
|
||||
displayName, openURL = strings.TrimSpace(displayName), strings.TrimSpace(openURL)
|
||||
if !ok || displayName == "" || openURL == "" {
|
||||
continue
|
||||
}
|
||||
openWithEditorApps = append(openWithEditorApps, setting.OpenWithEditorApp{
|
||||
DisplayName: strings.TrimSpace(displayName),
|
||||
OpenURL: strings.TrimSpace(openURL),
|
||||
})
|
||||
}
|
||||
return json.Marshal(openWithEditorApps)
|
||||
}
|
||||
marshallers := map[string]func(string) ([]byte, error){
|
||||
cfg.Picture.DisableGravatar.DynKey(): marshalBool,
|
||||
cfg.Picture.EnableFederatedAvatar.DynKey(): marshalBool,
|
||||
cfg.Repository.OpenWithEditorApps.DynKey(): marshalOpenWithApps,
|
||||
cfg.Repository.GitGuideRemoteName.DynKey(): marshalString(cfg.Repository.GitGuideRemoteName.DefaultValue()),
|
||||
}
|
||||
|
||||
_ = ctx.Req.ParseForm()
|
||||
configKeys := ctx.Req.Form["key"]
|
||||
configValues := ctx.Req.Form["value"]
|
||||
@@ -249,18 +223,16 @@ loop:
|
||||
}
|
||||
value := configValues[i]
|
||||
|
||||
marshaller, hasMarshaller := marshallers[key]
|
||||
if !hasMarshaller {
|
||||
ctx.JSONError(ctx.Tr("admin.config.set_setting_failed", key))
|
||||
break loop
|
||||
}
|
||||
|
||||
marshaledValue, err := marshaller(value)
|
||||
err := validateConfigKeyValue(key, value)
|
||||
if err != nil {
|
||||
ctx.JSONError(ctx.Tr("admin.config.set_setting_failed", key))
|
||||
if errors.Is(err, util.ErrInvalidArgument) {
|
||||
ctx.JSONError(err.Error())
|
||||
} else {
|
||||
ctx.JSONError(ctx.Tr("admin.config.set_setting_failed", key))
|
||||
}
|
||||
break loop
|
||||
}
|
||||
configSettings[key] = string(marshaledValue)
|
||||
configSettings[key] = value
|
||||
}
|
||||
if ctx.Written() {
|
||||
return
|
||||
|
||||
@@ -162,6 +162,11 @@ func consumeAuthRedirectLink(ctx *context.Context) string {
|
||||
}
|
||||
|
||||
func redirectAfterAuth(ctx *context.Context) {
|
||||
if setting.Config().Instance.MaintenanceMode.Value(ctx).IsActive() {
|
||||
// in maintenance mode, redirect to admin dashboard, it is the only accessible page
|
||||
ctx.Redirect(setting.AppSubURL + "/-/admin")
|
||||
return
|
||||
}
|
||||
ctx.RedirectToCurrentSite(consumeAuthRedirectLink(ctx))
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,15 @@ package misc
|
||||
import (
|
||||
"net/http"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/httpcache"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
"code.gitea.io/gitea/modules/web/middleware"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
func SSHInfo(rw http.ResponseWriter, req *http.Request) {
|
||||
@@ -47,3 +50,9 @@ func StaticRedirect(target string) func(w http.ResponseWriter, req *http.Request
|
||||
http.Redirect(w, req, path.Join(setting.StaticURLPrefix, target), http.StatusMovedPermanently)
|
||||
}
|
||||
}
|
||||
|
||||
func WebBannerDismiss(ctx *context.Context) {
|
||||
_, rev, _ := setting.Config().Instance.WebBanner.ValueRevision(ctx)
|
||||
middleware.SetSiteCookie(ctx.Resp, middleware.CookieWebBannerDismissed, strconv.Itoa(rev), 48*3600)
|
||||
ctx.JSONOK()
|
||||
}
|
||||
|
||||
@@ -37,6 +37,6 @@ func WebThemeApply(ctx *context.Context) {
|
||||
opts := &user_service.UpdateOptions{Theme: optional.Some(themeName)}
|
||||
_ = user_service.UpdateUser(ctx, ctx.Doer, opts)
|
||||
} else {
|
||||
middleware.SetSiteCookie(ctx.Resp, "gitea_theme", themeName, 0)
|
||||
middleware.SetSiteCookie(ctx.Resp, middleware.CookieTheme, themeName, 0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,9 +69,6 @@ func prepareHomeSidebarRepoTopics(ctx *context.Context) {
|
||||
func prepareOpenWithEditorApps(ctx *context.Context) {
|
||||
var tmplApps []map[string]any
|
||||
apps := setting.Config().Repository.OpenWithEditorApps.Value(ctx)
|
||||
if len(apps) == 0 {
|
||||
apps = setting.DefaultOpenWithEditorApps()
|
||||
}
|
||||
for _, app := range apps {
|
||||
schema, _, _ := strings.Cut(app.OpenURL, ":")
|
||||
|
||||
|
||||
+1
-1
@@ -480,7 +480,7 @@ func registerWebRoutes(m *web.Router) {
|
||||
}, optionsCorsHandler())
|
||||
|
||||
m.Post("/-/markup", reqSignIn, web.Bind(structs.MarkupOption{}), misc.Markup)
|
||||
|
||||
m.Post("/-/web-banner/dismiss", misc.WebBannerDismiss)
|
||||
m.Get("/-/web-theme/list", misc.WebThemeList)
|
||||
m.Post("/-/web-theme/apply", optSignIn, misc.WebThemeApply)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user