mirror of
https://github.com/go-gitea/gitea
synced 2024-12-01 01:18:16 +01:00
Backport #30890 by wxiaoguang Follow #27011 Follow #30885 Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
parent
2200c41ffd
commit
b99473f4ec
11 changed files with 93 additions and 19 deletions
|
@ -3320,6 +3320,7 @@ self_check.database_collation_case_insensitive = Database is using a collation %
|
||||||
self_check.database_inconsistent_collation_columns = Database is using collation %s, but these columns are using mismatched collations. It might cause some unexpected problems.
|
self_check.database_inconsistent_collation_columns = Database is using collation %s, but these columns are using mismatched collations. It might cause some unexpected problems.
|
||||||
self_check.database_fix_mysql = For MySQL/MariaDB users, you could use the "gitea doctor convert" command to fix the collation problems, or you could also fix the problem by "ALTER ... COLLATE ..." SQLs manually.
|
self_check.database_fix_mysql = For MySQL/MariaDB users, you could use the "gitea doctor convert" command to fix the collation problems, or you could also fix the problem by "ALTER ... COLLATE ..." SQLs manually.
|
||||||
self_check.database_fix_mssql = For MSSQL users, you could only fix the problem by "ALTER ... COLLATE ..." SQLs manually at the moment.
|
self_check.database_fix_mssql = For MSSQL users, you could only fix the problem by "ALTER ... COLLATE ..." SQLs manually at the moment.
|
||||||
|
self_check.location_origin_mismatch = Current URL (%[1]s) doesn't match the URL seen by Gitea (%[2]s). If you are using a reverse proxy, please make sure the "Host" and "X-Forwarded-Proto" headers are set correctly.
|
||||||
|
|
||||||
[action]
|
[action]
|
||||||
create_repo = created repository <a href="%s">%s</a>
|
create_repo = created repository <a href="%s">%s</a>
|
||||||
|
|
|
@ -9,12 +9,14 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
activities_model "code.gitea.io/gitea/models/activities"
|
activities_model "code.gitea.io/gitea/models/activities"
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
"code.gitea.io/gitea/modules/base"
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/graceful"
|
"code.gitea.io/gitea/modules/graceful"
|
||||||
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
@ -223,6 +225,16 @@ func SelfCheck(ctx *context.Context) {
|
||||||
ctx.HTML(http.StatusOK, tplSelfCheck)
|
ctx.HTML(http.StatusOK, tplSelfCheck)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SelfCheckPost(ctx *context.Context) {
|
||||||
|
var problems []string
|
||||||
|
frontendAppURL := ctx.FormString("location_origin") + setting.AppSubURL + "/"
|
||||||
|
ctxAppURL := httplib.GuessCurrentAppURL(ctx)
|
||||||
|
if !strings.HasPrefix(ctxAppURL, frontendAppURL) {
|
||||||
|
problems = append(problems, ctx.Locale.TrString("admin.self_check.location_origin_mismatch", frontendAppURL, ctxAppURL))
|
||||||
|
}
|
||||||
|
ctx.JSON(http.StatusOK, map[string]any{"problems": problems})
|
||||||
|
}
|
||||||
|
|
||||||
func CronTasks(ctx *context.Context) {
|
func CronTasks(ctx *context.Context) {
|
||||||
ctx.Data["Title"] = ctx.Tr("admin.monitor.cron")
|
ctx.Data["Title"] = ctx.Tr("admin.monitor.cron")
|
||||||
ctx.Data["PageIsAdminMonitorCron"] = true
|
ctx.Data["PageIsAdminMonitorCron"] = true
|
||||||
|
|
|
@ -4,8 +4,14 @@
|
||||||
package admin
|
package admin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/modules/json"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/test"
|
||||||
|
"code.gitea.io/gitea/services/contexttest"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -66,3 +72,21 @@ func TestShadowPassword(t *testing.T) {
|
||||||
assert.EqualValues(t, k.Result, shadowPassword(k.Provider, k.CfgItem))
|
assert.EqualValues(t, k.Result, shadowPassword(k.Provider, k.CfgItem))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSelfCheckPost(t *testing.T) {
|
||||||
|
defer test.MockVariableValue(&setting.AppURL, "http://config/sub/")()
|
||||||
|
defer test.MockVariableValue(&setting.AppSubURL, "/sub")()
|
||||||
|
|
||||||
|
ctx, resp := contexttest.MockContext(t, "GET http://host/sub/admin/self_check?location_origin=http://frontend")
|
||||||
|
SelfCheckPost(ctx)
|
||||||
|
assert.EqualValues(t, http.StatusOK, resp.Code)
|
||||||
|
|
||||||
|
data := struct {
|
||||||
|
Problems []string `json:"problems"`
|
||||||
|
}{}
|
||||||
|
err := json.Unmarshal(resp.Body.Bytes(), &data)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
ctx.Locale.TrString("admin.self_check.location_origin_mismatch", "http://frontend/sub/", "http://host/sub/"),
|
||||||
|
}, data.Problems)
|
||||||
|
}
|
||||||
|
|
|
@ -686,6 +686,7 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost)
|
m.Post("", web.Bind(forms.AdminDashboardForm{}), admin.DashboardPost)
|
||||||
|
|
||||||
m.Get("/self_check", admin.SelfCheck)
|
m.Get("/self_check", admin.SelfCheck)
|
||||||
|
m.Post("/self_check", admin.SelfCheckPost)
|
||||||
|
|
||||||
m.Group("/config", func() {
|
m.Group("/config", func() {
|
||||||
m.Get("", admin.Config)
|
m.Get("", admin.Config)
|
||||||
|
|
|
@ -309,7 +309,8 @@ func NewBaseContext(resp http.ResponseWriter, req *http.Request) (b *Base, close
|
||||||
Locale: middleware.Locale(resp, req),
|
Locale: middleware.Locale(resp, req),
|
||||||
Data: middleware.GetContextData(req.Context()),
|
Data: middleware.GetContextData(req.Context()),
|
||||||
}
|
}
|
||||||
b.AppendContextValue(translation.ContextKey, b.Locale)
|
|
||||||
b.Req = b.Req.WithContext(b)
|
b.Req = b.Req.WithContext(b)
|
||||||
|
b.AppendContextValue(translation.ContextKey, b.Locale)
|
||||||
|
b.AppendContextValue(httplib.RequestContextKey, b.Req)
|
||||||
return b, b.cleanUp
|
return b, b.cleanUp
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ func mockRequest(t *testing.T, reqPath string) *http.Request {
|
||||||
}
|
}
|
||||||
requestURL, err := url.Parse(path)
|
requestURL, err := url.Parse(path)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
req := &http.Request{Method: method, URL: requestURL, Form: maps.Clone(requestURL.Query()), Header: http.Header{}}
|
req := &http.Request{Method: method, Host: requestURL.Host, URL: requestURL, Form: maps.Clone(requestURL.Query()), Header: http.Header{}}
|
||||||
req = req.WithContext(middleware.WithContextData(req.Context()))
|
req = req.WithContext(middleware.WithContextData(req.Context()))
|
||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin config")}}
|
{{template "admin/layout_head" (dict "ctxData" . "pageClass" "admin")}}
|
||||||
|
|
||||||
<div class="admin-setting-content">
|
<div class="admin-setting-content">
|
||||||
<h4 class="ui top attached header">
|
<h4 class="ui top attached header">
|
||||||
|
@ -6,7 +6,7 @@
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
{{if .StartupProblems}}
|
{{if .StartupProblems}}
|
||||||
<div class="ui attached segment">
|
<div class="ui attached segment self-check-problem">
|
||||||
<div class="ui warning message">
|
<div class="ui warning message">
|
||||||
<div>{{ctx.Locale.Tr "admin.self_check.startup_warnings"}}</div>
|
<div>{{ctx.Locale.Tr "admin.self_check.startup_warnings"}}</div>
|
||||||
<ul class="tw-w-full">{{range .StartupProblems}}<li>{{.}}</li>{{end}}</ul>
|
<ul class="tw-w-full">{{range .StartupProblems}}<li>{{.}}</li>{{end}}</ul>
|
||||||
|
@ -14,8 +14,10 @@
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
|
<div class="ui attached segment tw-hidden self-check-problem" id="self-check-by-frontend"></div>
|
||||||
|
|
||||||
{{if .DatabaseCheckHasProblems}}
|
{{if .DatabaseCheckHasProblems}}
|
||||||
<div class="ui attached segment">
|
<div class="ui attached segment self-check-problem">
|
||||||
{{if .DatabaseType.IsMySQL}}
|
{{if .DatabaseType.IsMySQL}}
|
||||||
<div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.database_fix_mysql"}}</div>
|
<div class="tw-p-2">{{ctx.Locale.Tr "admin.self_check.database_fix_mysql"}}</div>
|
||||||
{{else if .DatabaseType.IsMSSQL}}
|
{{else if .DatabaseType.IsMSSQL}}
|
||||||
|
@ -29,22 +31,22 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if .DatabaseCheckInconsistentCollationColumns}}
|
{{if .DatabaseCheckInconsistentCollationColumns}}
|
||||||
<div class="ui red message">
|
<div class="ui red message">
|
||||||
{{ctx.Locale.Tr "admin.self_check.database_inconsistent_collation_columns" .DatabaseCheckResult.DatabaseCollation}}
|
<details>
|
||||||
<ul class="tw-w-full">
|
<summary>{{ctx.Locale.Tr "admin.self_check.database_inconsistent_collation_columns" .DatabaseCheckResult.DatabaseCollation}}</summary>
|
||||||
{{range .DatabaseCheckInconsistentCollationColumns}}
|
<ul class="tw-w-full">
|
||||||
<li>{{.}}</li>
|
{{range .DatabaseCheckInconsistentCollationColumns}}
|
||||||
{{end}}
|
<li>{{.}}</li>
|
||||||
</ul>
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{/* only shown when there is no visible "self-check-problem" */}}
|
||||||
{{if and (not .StartupProblems) (not .DatabaseCheckHasProblems)}}
|
<div class="ui attached segment tw-hidden self-check-no-problem">
|
||||||
<div class="ui attached segment">
|
|
||||||
{{ctx.Locale.Tr "admin.self_check.no_problem_found"}}
|
{{ctx.Locale.Tr "admin.self_check.no_problem_found"}}
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{template "admin/layout_footer" .}}
|
{{template "admin/layout_footer" .}}
|
||||||
|
|
6
web_src/js/bootstrap.js
vendored
6
web_src/js/bootstrap.js
vendored
|
@ -16,20 +16,20 @@ function shouldIgnoreError(err) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showGlobalErrorMessage(msg) {
|
export function showGlobalErrorMessage(msg, msgType = 'error') {
|
||||||
const msgContainer = document.querySelector('.page-content') ?? document.body;
|
const msgContainer = document.querySelector('.page-content') ?? document.body;
|
||||||
const msgCompact = msg.replace(/\W/g, '').trim(); // compact the message to a data attribute to avoid too many duplicated messages
|
const msgCompact = msg.replace(/\W/g, '').trim(); // compact the message to a data attribute to avoid too many duplicated messages
|
||||||
let msgDiv = msgContainer.querySelector(`.js-global-error[data-global-error-msg-compact="${msgCompact}"]`);
|
let msgDiv = msgContainer.querySelector(`.js-global-error[data-global-error-msg-compact="${msgCompact}"]`);
|
||||||
if (!msgDiv) {
|
if (!msgDiv) {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
el.innerHTML = `<div class="ui container negative message center aligned js-global-error tw-mt-[15px] tw-whitespace-pre-line"></div>`;
|
el.innerHTML = `<div class="ui container js-global-error tw-my-[--page-spacing]"><div class="ui ${msgType} message tw-text-center tw-whitespace-pre-line"></div></div>`;
|
||||||
msgDiv = el.childNodes[0];
|
msgDiv = el.childNodes[0];
|
||||||
}
|
}
|
||||||
// merge duplicated messages into "the message (count)" format
|
// merge duplicated messages into "the message (count)" format
|
||||||
const msgCount = Number(msgDiv.getAttribute(`data-global-error-msg-count`)) + 1;
|
const msgCount = Number(msgDiv.getAttribute(`data-global-error-msg-count`)) + 1;
|
||||||
msgDiv.setAttribute(`data-global-error-msg-compact`, msgCompact);
|
msgDiv.setAttribute(`data-global-error-msg-compact`, msgCompact);
|
||||||
msgDiv.setAttribute(`data-global-error-msg-count`, msgCount.toString());
|
msgDiv.setAttribute(`data-global-error-msg-count`, msgCount.toString());
|
||||||
msgDiv.textContent = msg + (msgCount > 1 ? ` (${msgCount})` : '');
|
msgDiv.querySelector('.ui.message').textContent = msg + (msgCount > 1 ? ` (${msgCount})` : '');
|
||||||
msgContainer.prepend(msgDiv);
|
msgContainer.prepend(msgDiv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
31
web_src/js/features/admin/selfcheck.js
Normal file
31
web_src/js/features/admin/selfcheck.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import {toggleElem} from '../../utils/dom.js';
|
||||||
|
import {POST} from '../../modules/fetch.js';
|
||||||
|
|
||||||
|
const {appSubUrl} = window.config;
|
||||||
|
|
||||||
|
export async function initAdminSelfCheck() {
|
||||||
|
const elCheckByFrontend = document.querySelector('#self-check-by-frontend');
|
||||||
|
if (!elCheckByFrontend) return;
|
||||||
|
|
||||||
|
const elContent = document.querySelector('.page-content.admin .admin-setting-content');
|
||||||
|
|
||||||
|
// send frontend self-check request
|
||||||
|
const resp = await POST(`${appSubUrl}/admin/self_check`, {
|
||||||
|
data: new URLSearchParams({
|
||||||
|
location_origin: window.location.origin,
|
||||||
|
now: Date.now(), // TODO: check time difference between server and client
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const json = await resp.json();
|
||||||
|
toggleElem(elCheckByFrontend, Boolean(json.problems?.length));
|
||||||
|
for (const problem of json.problems ?? []) {
|
||||||
|
const elProblem = document.createElement('div');
|
||||||
|
elProblem.classList.add('ui', 'warning', 'message');
|
||||||
|
elProblem.textContent = problem;
|
||||||
|
elCheckByFrontend.append(elProblem);
|
||||||
|
}
|
||||||
|
|
||||||
|
// only show the "no problem" if there is no visible "self-check-problem"
|
||||||
|
const hasProblem = Boolean(elContent.querySelectorAll('.self-check-problem:not(.tw-hidden)').length);
|
||||||
|
toggleElem(elContent.querySelector('.self-check-no-problem'), !hasProblem);
|
||||||
|
}
|
|
@ -451,5 +451,5 @@ export function checkAppUrl() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
showGlobalErrorMessage(`Your ROOT_URL in app.ini is "${appUrl}", it's unlikely matching the site you are visiting.
|
showGlobalErrorMessage(`Your ROOT_URL in app.ini is "${appUrl}", it's unlikely matching the site you are visiting.
|
||||||
Mismatched ROOT_URL config causes wrong URL links for web UI/mail content/webhook notification/OAuth2 sign-in.`);
|
Mismatched ROOT_URL config causes wrong URL links for web UI/mail content/webhook notification/OAuth2 sign-in.`, 'warning');
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,7 @@ import {initRepoDiffCommitBranchesAndTags} from './features/repo-diff-commit.js'
|
||||||
import {initDirAuto} from './modules/dirauto.js';
|
import {initDirAuto} from './modules/dirauto.js';
|
||||||
import {initRepositorySearch} from './features/repo-search.js';
|
import {initRepositorySearch} from './features/repo-search.js';
|
||||||
import {initColorPickers} from './features/colorpicker.js';
|
import {initColorPickers} from './features/colorpicker.js';
|
||||||
|
import {initAdminSelfCheck} from './features/admin/selfcheck.js';
|
||||||
|
|
||||||
// Init Gitea's Fomantic settings
|
// Init Gitea's Fomantic settings
|
||||||
initGiteaFomantic();
|
initGiteaFomantic();
|
||||||
|
@ -132,6 +133,7 @@ onDomReady(() => {
|
||||||
initAdminEmails();
|
initAdminEmails();
|
||||||
initAdminUserListSearchForm();
|
initAdminUserListSearchForm();
|
||||||
initAdminConfigs();
|
initAdminConfigs();
|
||||||
|
initAdminSelfCheck();
|
||||||
|
|
||||||
initDashboardRepoList();
|
initDashboardRepoList();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue