mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-04-25 04:57:31 +00:00
ui: improve error pages (#7274)
* add testing * make each page accessible via `/devtest/error` * allow translating the `Page not found` part of the title * code: improve consistency, remove unused * devtest: put index page in a container to fix alignment * 500: make navbar more like the real one, remove fake menu button * deadcode: remove unused `func NotFound`: it was added inbdd32f152d
and the only usage was removed in1bfb0a24d8
Preview: https://codeberg.org/attachments/1b75afb3-e898-410f-be02-f036a5400143 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7274 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Reviewed-by: Beowulf <beowulf@beocode.eu>
This commit is contained in:
parent
683eb5bf78
commit
51ff4970ec
11 changed files with 106 additions and 32 deletions
|
@ -210,9 +210,6 @@ forgejo.org/modules/zstd
|
|||
Writer.Write
|
||||
Writer.Close
|
||||
|
||||
forgejo.org/routers/web
|
||||
NotFound
|
||||
|
||||
forgejo.org/routers/web/org
|
||||
MustEnableProjects
|
||||
|
||||
|
|
|
@ -363,6 +363,9 @@ var ignoredErrorMessage = []string{
|
|||
|
||||
// TestDatabaseCollation
|
||||
`[E] [Error SQL Query] INSERT INTO test_collation_tbl (txt) VALUES ('main') []`,
|
||||
|
||||
// TestDevtestErrorpages
|
||||
`ErrorPage() [E] Example error: Example error`,
|
||||
}
|
||||
|
||||
func (w *testLoggerWriterCloser) recordError(msg string) {
|
||||
|
|
|
@ -16,5 +16,6 @@
|
|||
"incorrect_root_url": "This Forgejo instance is configured to be served on \"%s\". You are currently viewing Forgejo through a different URL, which may cause parts of the application to break. The canonical URL is controlled by Forgejo admins via the ROOT_URL setting in the app.ini.",
|
||||
"themes.names.forgejo-auto": "Forgejo (follow system theme)",
|
||||
"themes.names.forgejo-light": "Forgejo light",
|
||||
"themes.names.forgejo-dark": "Forgejo dark"
|
||||
"themes.names.forgejo-dark": "Forgejo dark",
|
||||
"error.not_found.title": "Page not found"
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package devtest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
@ -42,6 +44,17 @@ func FetchActionTest(ctx *context.Context) {
|
|||
ctx.JSONRedirect("")
|
||||
}
|
||||
|
||||
func ErrorPage(ctx *context.Context) {
|
||||
if ctx.Params("errcode") == "404" {
|
||||
ctx.NotFound("Example error", errors.New("Example error"))
|
||||
return
|
||||
} else if ctx.Params("errcode") == "413" {
|
||||
ctx.HTML(http.StatusRequestEntityTooLarge, base.TplName("status/413"))
|
||||
return
|
||||
}
|
||||
ctx.ServerError("Example error", errors.New("Example error"))
|
||||
}
|
||||
|
||||
func Tmpl(ctx *context.Context) {
|
||||
now := time.Now()
|
||||
ctx.Data["TimeNow"] = now
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2019 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package web
|
||||
|
@ -112,9 +113,3 @@ func HomeSitemap(ctx *context.Context) {
|
|||
log.Error("Failed writing sitemap: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// NotFound render 404 page
|
||||
func NotFound(ctx *context.Context) {
|
||||
ctx.Data["Title"] = "Page Not Found"
|
||||
ctx.NotFound("home.NotFound", nil)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2023 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package web
|
||||
|
@ -1661,6 +1662,7 @@ func registerRoutes(m *web.Route) {
|
|||
m.Any("/devtest", devtest.List)
|
||||
m.Any("/devtest/fetch-action-test", devtest.FetchActionTest)
|
||||
m.Any("/devtest/{sub}", devtest.Tmpl)
|
||||
m.Get("/devtest/error/{errcode}", devtest.ErrorPage)
|
||||
}
|
||||
|
||||
m.NotFound(func(w http.ResponseWriter, req *http.Request) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2023 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package context
|
||||
|
@ -66,7 +67,10 @@ func (ctx *Context) RedirectToFirst(location ...string) string {
|
|||
return setting.AppSubURL + "/"
|
||||
}
|
||||
|
||||
const tplStatus500 base.TplName = "status/500"
|
||||
const (
|
||||
tplStatus404 base.TplName = "status/404"
|
||||
tplStatus500 base.TplName = "status/500"
|
||||
)
|
||||
|
||||
// HTML calls Context.HTML and renders the template to HTTP response
|
||||
func (ctx *Context) HTML(status int, name base.TplName) {
|
||||
|
@ -153,8 +157,8 @@ func (ctx *Context) notFoundInternal(logMsg string, logErr error) {
|
|||
}
|
||||
|
||||
ctx.Data["IsRepo"] = ctx.Repo.Repository != nil
|
||||
ctx.Data["Title"] = "Page Not Found"
|
||||
ctx.HTML(http.StatusNotFound, base.TplName("status/404"))
|
||||
ctx.Data["Title"] = ctx.Locale.TrString("error.not_found.title")
|
||||
ctx.HTML(http.StatusNotFound, tplStatus404)
|
||||
}
|
||||
|
||||
// ServerError displays a 500 (Internal Server Error) page and prints the given error, if any.
|
||||
|
@ -177,7 +181,6 @@ func (ctx *Context) serverErrorInternal(logMsg string, logErr error) {
|
|||
}
|
||||
}
|
||||
|
||||
ctx.Data["Title"] = "Internal Server Error"
|
||||
ctx.HTML(http.StatusInternalServerError, tplStatus500)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
{{template "base/head" .}}
|
||||
|
||||
<ul>
|
||||
<div role="main" class="page-content ui container">
|
||||
<ul>
|
||||
{{range .SubNames}}
|
||||
<li><a href="{{AppSubUrl}}/devtest/{{.}}">{{.}}</a></li>
|
||||
{{end}}
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
<article>
|
||||
<h2>Error pages</h2>
|
||||
<ul>
|
||||
<li><a href="./error/404">Not found</a></li>
|
||||
<li><a href="./error/413">Quota exhaustion</a></li>
|
||||
<li><a href="./error/500">Server error</a></li>
|
||||
</ul>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
ul {
|
||||
|
|
|
@ -16,19 +16,14 @@
|
|||
</head>
|
||||
<body>
|
||||
<div class="full height">
|
||||
<nav class="ui secondary menu">
|
||||
<div class="ui container tw-flex">
|
||||
<div class="item tw-flex-1">
|
||||
<a href="{{AppSubUrl}}/" aria-label="{{ctx.Locale.Tr "home"}}">
|
||||
<nav id="navbar" aria-label="{{ctx.Locale.Tr "aria.navbar"}}">
|
||||
<div class="navbar-left ui secondary menu">
|
||||
<a class="item" id="navbar-logo" href="{{AppSubUrl}}/" aria-label="{{ctx.Locale.Tr "home"}}">
|
||||
<img width="30" height="30" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{ctx.Locale.Tr "logo"}}" aria-hidden="true">
|
||||
</a>
|
||||
</div>
|
||||
<div class="item">
|
||||
<button class="ui icon button disabled">{{svg "octicon-three-bars"}}</button>{{/* a fake button to make the UI looks better*/}}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="divider tw-my-0"></div>
|
||||
|
||||
<div role="main" class="page-content status-page-500">
|
||||
<div class="ui container" >
|
||||
<style> .ui.message.flash-message { text-align: left; } </style>
|
||||
|
|
53
tests/integration/devtest_error_test.go
Normal file
53
tests/integration/devtest_error_test.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2025 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"forgejo.org/modules/setting"
|
||||
"forgejo.org/modules/test"
|
||||
"forgejo.org/routers"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// `/devtest/error/{errcode}` provides a convenient way of testing various
|
||||
// error pages sometimes which can be hard to reach otherwise.
|
||||
// This file is a test of various attributes on those pages.
|
||||
|
||||
func TestDevtestErrorpages(t *testing.T) {
|
||||
defer test.MockVariableValue(&setting.IsProd, false)()
|
||||
defer test.MockVariableValue(&testWebRoutes, routers.NormalRoutes())()
|
||||
|
||||
t.Run("Server error", func(t *testing.T) {
|
||||
// `/devtest/error/x` returns 500 for any x by default.
|
||||
// `/500` is simply for good look here
|
||||
req := NewRequest(t, "GET", "/devtest/error/500")
|
||||
resp := MakeRequest(t, req, http.StatusInternalServerError)
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
assert.EqualValues(t, "500", doc.Find(".error-code").Text())
|
||||
assert.Contains(t, doc.Find("head title").Text(), "Internal server error")
|
||||
})
|
||||
|
||||
t.Run("Page not found",
|
||||
func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/devtest/error/404").
|
||||
// Without this header `notFoundInternal` returns plaintext error message
|
||||
SetHeader("Accept", "text/html")
|
||||
resp := MakeRequest(t, req, http.StatusNotFound)
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
assert.EqualValues(t, "404", doc.Find(".error-code").Text())
|
||||
assert.Contains(t, doc.Find("head title").Text(), "Page not found")
|
||||
})
|
||||
|
||||
t.Run("Quota exhaustion",
|
||||
func(t *testing.T) {
|
||||
req := NewRequest(t, "GET", "/devtest/error/413")
|
||||
resp := MakeRequest(t, req, http.StatusRequestEntityTooLarge)
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
assert.EqualValues(t, "413", doc.Find(".error-code").Text())
|
||||
})
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// Copyright 2023 The Forgejo Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package integration
|
||||
|
@ -691,7 +692,7 @@ func TestCommitView(t *testing.T) {
|
|||
|
||||
// Really ensure that 404 is being sent back.
|
||||
doc := NewHTMLParser(t, resp.Body)
|
||||
doc.AssertElement(t, `[aria-label="Page Not Found"]`, true)
|
||||
doc.AssertElement(t, `[aria-label="Page not found"]`, true)
|
||||
})
|
||||
|
||||
t.Run("Too short commit ID", func(t *testing.T) {
|
||||
|
|
Loading…
Add table
Reference in a new issue