diff --git a/modules/setting/config.go b/modules/setting/config.go index 59f046e88d..1391c37799 100644 --- a/modules/setting/config.go +++ b/modules/setting/config.go @@ -62,10 +62,9 @@ const ( ) type InstanceNotice struct { - Enabled bool - Message string - Level string - ShowIcon bool + Enabled bool + Message string + Level string StartTime int64 EndTime int64 @@ -73,8 +72,7 @@ type InstanceNotice struct { func DefaultInstanceNotice() InstanceNotice { return InstanceNotice{ - Level: InstanceNoticeLevelInfo, - ShowIcon: true, + Level: InstanceNoticeLevelInfo, } } diff --git a/options/locale/locale_en-US.json b/options/locale/locale_en-US.json index 01be25b2fa..987794373a 100644 --- a/options/locale/locale_en-US.json +++ b/options/locale/locale_en-US.json @@ -3288,12 +3288,11 @@ "admin.config.instance_notice.level.success": "Success", "admin.config.instance_notice.level.warning": "Warning", "admin.config.instance_notice.level.danger": "Danger", - "admin.config.instance_notice.show_icon": "Show icon", + "admin.config.instance_notice.message_too_long": "Message must be at most %d characters.", "admin.config.instance_notice.start_time": "Start time", "admin.config.instance_notice.end_time": "End time", "admin.config.instance_notice.invalid_time": "Invalid date-time value. Use the picker format.", "admin.config.instance_notice.invalid_time_range": "End time must be after start time.", - "admin.config.instance_notice.markdown_hint": "This banner is informational only and does not block access.", "admin.config.instance_notice.preview": "Preview", "admin.config.instance_notice.save": "Save banner", "admin.config.instance_notice.save_success": "Instance banner settings updated.", diff --git a/routers/common/pagetmpl.go b/routers/common/pagetmpl.go index b5a12a5334..c14d4f851e 100644 --- a/routers/common/pagetmpl.go +++ b/routers/common/pagetmpl.go @@ -80,18 +80,17 @@ type pageGlobalDataType struct { type InstanceNoticeBannerTmplInfo struct { Message string Level string - ShowIcon bool IconName string } func instanceNoticeIconName(level string) string { switch level { case setting.InstanceNoticeLevelSuccess: - return "octicon-check-circle" + return "octicon-check" case setting.InstanceNoticeLevelWarning: return "octicon-alert" case setting.InstanceNoticeLevelDanger: - return "octicon-stop" + return "octicon-alert" default: return "octicon-info" } @@ -105,7 +104,6 @@ func getInstanceNoticeBanner(ctx *context.Context) *InstanceNoticeBannerTmplInfo return &InstanceNoticeBannerTmplInfo{ Message: notice.Message, Level: notice.Level, - ShowIcon: notice.ShowIcon, IconName: instanceNoticeIconName(notice.Level), } } diff --git a/routers/web/admin/config.go b/routers/web/admin/config.go index cc9e04a670..b81af78ae6 100644 --- a/routers/web/admin/config.go +++ b/routers/web/admin/config.go @@ -10,6 +10,7 @@ import ( "strconv" "strings" "time" + "unicode/utf8" system_model "code.gitea.io/gitea/models/system" "code.gitea.io/gitea/modules/cache" @@ -29,6 +30,8 @@ import ( const ( tplConfig templates.TplName = "admin/config" tplConfigSettings templates.TplName = "admin/config_settings/config_settings" + + instanceNoticeMessageMaxLength = 2000 ) // SendTestMail send test mail to confirm mail service is OK @@ -148,6 +151,7 @@ func Config(ctx *context.Context) { ctx.Data["Webhook"] = setting.Webhook instanceNotice := setting.GetInstanceNotice(ctx) ctx.Data["InstanceNotice"] = instanceNotice + ctx.Data["InstanceNoticeMessageMaxLength"] = instanceNoticeMessageMaxLength if instanceNotice.StartTime > 0 { ctx.Data["InstanceNoticeStartTime"] = time.Unix(instanceNotice.StartTime, 0).In(setting.DefaultUILocation).Format("2006-01-02T15:04") } @@ -237,7 +241,6 @@ func SetInstanceNotice(ctx *context.Context) { enabled := ctx.FormBool("enabled") message := strings.TrimSpace(ctx.FormString("message")) level := strings.TrimSpace(ctx.FormString("level")) - showIcon := ctx.FormBool("show_icon") startTime, err := parseDatetimeLocalValue(strings.TrimSpace(ctx.FormString("start_time"))) if err != nil { ctx.Flash.Error(ctx.Tr("admin.config.instance_notice.invalid_time")) @@ -258,6 +261,11 @@ func SetInstanceNotice(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/-/admin/config#instance-notice") return } + if utf8.RuneCountInString(message) > instanceNoticeMessageMaxLength { + ctx.Flash.Error(ctx.Tr("admin.config.instance_notice.message_too_long", instanceNoticeMessageMaxLength)) + ctx.Redirect(setting.AppSubURL + "/-/admin/config#instance-notice") + return + } if startTime > 0 && endTime > 0 && endTime < startTime { ctx.Flash.Error(ctx.Tr("admin.config.instance_notice.invalid_time_range")) ctx.Redirect(setting.AppSubURL + "/-/admin/config#instance-notice") @@ -268,7 +276,6 @@ func SetInstanceNotice(ctx *context.Context) { Enabled: enabled, Message: message, Level: level, - ShowIcon: showIcon, StartTime: startTime, EndTime: endTime, } diff --git a/templates/admin/config.tmpl b/templates/admin/config.tmpl index 940b7ec65f..8e78bcc887 100644 --- a/templates/admin/config.tmpl +++ b/templates/admin/config.tmpl @@ -279,10 +279,10 @@ -
- - -
+
+ + +
-
-
- - -
-
-
+
@@ -308,25 +302,23 @@
-

{{ctx.Locale.Tr "admin.config.instance_notice.markdown_hint"}}

-
- -
-
-
- {{if eq .InstanceNotice.Level "success"}}{{svg "octicon-check-circle"}}{{else if eq .InstanceNotice.Level "warning"}}{{svg "octicon-alert"}}{{else if eq .InstanceNotice.Level "danger"}}{{svg "octicon-stop"}}{{else}}{{svg "octicon-info"}}{{end}} + +
+
+
+ {{if eq .InstanceNotice.Level "success"}}{{svg "octicon-check"}}{{else if eq .InstanceNotice.Level "warning"}}{{svg "octicon-alert"}}{{else if eq .InstanceNotice.Level "danger"}}{{svg "octicon-alert"}}{{else}}{{svg "octicon-info"}}{{end}} +
+
{{ctx.RenderUtils.MarkdownToHtml .InstanceNotice.Message}}
-
{{ctx.RenderUtils.MarkdownToHtml .InstanceNotice.Message}}
+
+
+ {{svg "octicon-info"}} + {{svg "octicon-check"}} + {{svg "octicon-alert"}} + {{svg "octicon-alert"}}
-
- {{svg "octicon-info"}} - {{svg "octicon-check-circle"}} - {{svg "octicon-alert"}} - {{svg "octicon-stop"}} -
-
diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index 2144851e66..5a8c6d251f 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -177,14 +177,12 @@ {{end}} {{if and .PageGlobalData .PageGlobalData.InstanceNoticeBanner}} - {{$banner := .PageGlobalData.InstanceNoticeBanner}} -
-
- {{if $banner.ShowIcon}} + {{$banner := .PageGlobalData.InstanceNoticeBanner}} +
+
{{svg $banner.IconName}} - {{end}} -
{{ctx.RenderUtils.MarkdownToHtml $banner.Message}}
-
+
{{ctx.RenderUtils.MarkdownToHtml $banner.Message}}
+
{{if .PageGlobalData.IsSiteAdmin}}
{{ctx.Locale.Tr "admin.config.instance_notice.edit_hint"}} diff --git a/tests/integration/instance_notice_test.go b/tests/integration/instance_notice_test.go index 2e5235d9d6..1d8f4941fc 100644 --- a/tests/integration/instance_notice_test.go +++ b/tests/integration/instance_notice_test.go @@ -5,6 +5,7 @@ package integration import ( "net/http" + "strings" "testing" "time" @@ -23,10 +24,9 @@ func TestInstanceNoticeVisibility(t *testing.T) { setInstanceNoticeForTest(t, setting.DefaultInstanceNotice()) setInstanceNoticeForTest(t, setting.InstanceNotice{ - Enabled: true, - Message: "Planned **upgrade** in progress.", - Level: setting.InstanceNoticeLevelWarning, - ShowIcon: true, + Enabled: true, + Message: "Planned **upgrade** in progress.", + Level: setting.InstanceNoticeLevelWarning, }) t.Run("AnonymousUserSeesBanner", func(t *testing.T) { @@ -86,16 +86,25 @@ func TestInstanceNoticeAdminCRUD(t *testing.T) { adminSession := loginUser(t, "user1") req := NewRequestWithValues(t, "POST", "/-/admin/config/instance_notice", map[string]string{ - "enabled": "true", - "message": "Admin set banner", - "level": "danger", - "show_icon": "true", + "enabled": "true", + "message": "Admin set banner", + "level": "danger", }) adminSession.MakeRequest(t, req, http.StatusSeeOther) notice := setting.GetInstanceNotice(t.Context()) assert.True(t, notice.Enabled) - assert.True(t, notice.ShowIcon) + assert.Equal(t, "Admin set banner", notice.Message) + assert.Equal(t, setting.InstanceNoticeLevelDanger, notice.Level) + + req = NewRequestWithValues(t, "POST", "/-/admin/config/instance_notice", map[string]string{ + "enabled": "true", + "message": strings.Repeat("a", 2001), + "level": "warning", + }) + adminSession.MakeRequest(t, req, http.StatusSeeOther) + + notice = setting.GetInstanceNotice(t.Context()) assert.Equal(t, "Admin set banner", notice.Message) assert.Equal(t, setting.InstanceNoticeLevelDanger, notice.Level) diff --git a/web_src/js/features/admin/config.test.ts b/web_src/js/features/admin/config.test.ts index 5e303df5b7..d5a86e0704 100644 --- a/web_src/js/features/admin/config.test.ts +++ b/web_src/js/features/admin/config.test.ts @@ -21,7 +21,6 @@ function createPreviewDOM() { -
@@ -31,7 +30,7 @@ function createPreviewDOM() { - +
`; @@ -69,22 +68,17 @@ describe('Admin Instance Notice Preview', () => { expect(previewContent.innerHTML).toContain('Rendered message'); }); - test('updates preview class and icon when level and icon toggle change', () => { + test('updates preview class and icon when level changes', () => { initAdminConfigs(); const levelSelect = document.querySelector('select[name="level"]')!; - const showIcon = document.querySelector('input[name="show_icon"]')!; const preview = document.querySelector('#instance-notice-preview')!; const previewIcon = document.querySelector('#instance-notice-preview-icon')!; levelSelect.value = 'danger'; levelSelect.dispatchEvent(new Event('change')); expect(preview.classList.contains('negative')).toBe(true); - expect(previewIcon.innerHTML).toContain('data-icon="danger"'); - - showIcon.checked = false; - showIcon.dispatchEvent(new Event('change')); - expect(previewIcon.classList.contains('tw-hidden')).toBe(true); + expect(previewIcon.innerHTML).toContain('data-icon="warning"'); }); test('queues a second render while first request is in flight and re-renders with latest text', async () => { diff --git a/web_src/js/features/admin/config.ts b/web_src/js/features/admin/config.ts index 7f00cd611c..6c02c5a585 100644 --- a/web_src/js/features/admin/config.ts +++ b/web_src/js/features/admin/config.ts @@ -10,12 +10,11 @@ function initInstanceNoticePreview(elAdminConfig: HTMLDivElement): void { const inputMessage = form.querySelector('textarea[name="message"]'); const selectLevel = form.querySelector('select[name="level"]'); - const inputShowIcon = form.querySelector('input[name="show_icon"]'); const preview = elAdminConfig.querySelector('#instance-notice-preview'); const previewIcon = elAdminConfig.querySelector('#instance-notice-preview-icon'); const previewContent = elAdminConfig.querySelector('#instance-notice-preview-content'); const iconContainer = elAdminConfig.querySelector('#instance-notice-preview-icons'); - if (!inputMessage || !selectLevel || !inputShowIcon || !preview || !previewIcon || !previewContent || !iconContainer) return; + if (!inputMessage || !selectLevel || !preview || !previewIcon || !previewContent || !iconContainer) return; const iconHTMLByLevel = new Map(); for (const el of iconContainer.querySelectorAll('[data-level]')) { @@ -33,7 +32,6 @@ function initInstanceNoticePreview(elAdminConfig: HTMLDivElement): void { preview.classList.remove('info', 'positive', 'warning', 'negative'); preview.classList.add(classByLevel[selectLevel.value] || 'info'); previewIcon.innerHTML = iconHTMLByLevel.get(selectLevel.value) || iconHTMLByLevel.get('info') || ''; - previewIcon.classList.toggle('tw-hidden', !inputShowIcon.checked); }; let renderRequesting = false; @@ -68,7 +66,6 @@ function initInstanceNoticePreview(elAdminConfig: HTMLDivElement): void { renderPreviewMarkdown(); }); selectLevel.addEventListener('change', updateStyle); - inputShowIcon.addEventListener('change', updateStyle); updateStyle(); }