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:
Nicolas
2026-02-26 16:16:11 +01:00
committed by GitHub
parent d0f92cb0a1
commit 26d83c932a
34 changed files with 870 additions and 158 deletions

View File

@@ -100,12 +100,12 @@ func GetValidateContext(req *http.Request) (ctx *ValidateContext) {
return ctx
}
func NewTemplateContextForWeb(ctx *Context) TemplateContext {
tmplCtx := NewTemplateContext(ctx, ctx.Req)
tmplCtx["Locale"] = ctx.Base.Locale
func NewTemplateContextForWeb(ctx reqctx.RequestContext, req *http.Request, locale translation.Locale) TemplateContext {
tmplCtx := NewTemplateContext(ctx, req)
tmplCtx["Locale"] = locale
tmplCtx["AvatarUtils"] = templates.NewAvatarUtils(ctx)
tmplCtx["RenderUtils"] = templates.NewRenderUtils(ctx)
tmplCtx["RootData"] = ctx.Data
tmplCtx["RootData"] = ctx.GetData()
tmplCtx["Consts"] = map[string]any{
"RepoUnitTypeCode": unit.TypeCode,
"RepoUnitTypeIssues": unit.TypeIssues,
@@ -132,7 +132,7 @@ func NewWebContext(base *Base, render Render, session session.Store) *Context {
Repo: &Repository{},
Org: &Organization{},
}
ctx.TemplateContext = NewTemplateContextForWeb(ctx)
ctx.TemplateContext = NewTemplateContextForWeb(ctx, ctx.Base.Req, ctx.Base.Locale)
ctx.Flash = &middleware.Flash{DataStore: ctx, Values: url.Values{}}
ctx.SetContextValue(WebContextKey, ctx)
return ctx

View File

@@ -6,8 +6,11 @@ package context
import (
"context"
"net/http"
"strconv"
"time"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/web/middleware"
"code.gitea.io/gitea/services/webtheme"
)
@@ -17,6 +20,10 @@ func NewTemplateContext(ctx context.Context, req *http.Request) TemplateContext
return TemplateContext{"_ctx": ctx, "_req": req}
}
func (c TemplateContext) req() *http.Request {
return c["_req"].(*http.Request)
}
func (c TemplateContext) parentContext() context.Context {
return c["_ctx"].(context.Context)
}
@@ -38,7 +45,6 @@ func (c TemplateContext) Value(key any) any {
}
func (c TemplateContext) CurrentWebTheme() *webtheme.ThemeMetaInfo {
req := c["_req"].(*http.Request)
var themeName string
if webCtx := GetWebContext(c); webCtx != nil {
if webCtx.Doer != nil {
@@ -46,9 +52,20 @@ func (c TemplateContext) CurrentWebTheme() *webtheme.ThemeMetaInfo {
}
}
if themeName == "" {
if cookieTheme, _ := req.Cookie("gitea_theme"); cookieTheme != nil {
themeName = cookieTheme.Value
}
themeName = middleware.GetSiteCookie(c.req(), middleware.CookieTheme)
}
return webtheme.GuaranteeGetThemeMetaInfo(themeName)
}
func (c TemplateContext) CurrentWebBanner() *setting.WebBannerType {
// Using revision as a simple approach to determine if the banner has been changed after the user dismissed it.
// There could be some false-positives because revision can be changed even if the banner isn't.
// While it should be still good enough (no admin would keep changing the settings) and doesn't really harm end users (just a few more times to see the banner)
// So it doesn't need to make it more complicated by allocating unique IDs or using hashes.
dismissedBannerRevision, _ := strconv.Atoi(middleware.GetSiteCookie(c.req(), middleware.CookieWebBannerDismissed))
banner, revision, _ := setting.Config().Instance.WebBanner.ValueRevision(c)
if banner.ShouldDisplay() && dismissedBannerRevision != revision {
return &banner
}
return nil
}