mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-09 03:33:09 +02:00
feat(server): Misskey Webでユーザーフレンドリーなエラーページを出す (#10590)
* (add) user-friendly error page * Update CHANGELOG.md * (add) cache-control header * Add ClientLoggerService * Log params and query * remove error stack on client * fix pug * 文面を調整 * :art] --------- Co-authored-by: tamaina <tamaina@hotmail.co.jp>
This commit is contained in:
parent
5d56799070
commit
49749b46c4
7 changed files with 218 additions and 1 deletions
|
@ -25,6 +25,8 @@
|
||||||
-
|
-
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
|
- Misskey Webでのサーバーサイドエラー画面を改善
|
||||||
|
- Misskey Webでのサーバーサイドエラーのログが残るように
|
||||||
- ノート作成時のアンテナ追加パフォーマンスを改善
|
- ノート作成時のアンテナ追加パフォーマンスを改善
|
||||||
- フォローインポートなどでの大量のフォロー等操作をキューイングするように #10544 @nmkj-io
|
- フォローインポートなどでの大量のフォロー等操作をキューイングするように #10544 @nmkj-io
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ gulp.task('build:backend:script', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.task('build:backend:style', () => {
|
gulp.task('build:backend:style', () => {
|
||||||
return gulp.src(['./packages/backend/src/server/web/style.css', './packages/backend/src/server/web/bios.css', './packages/backend/src/server/web/cli.css'])
|
return gulp.src(['./packages/backend/src/server/web/style.css', './packages/backend/src/server/web/bios.css', './packages/backend/src/server/web/cli.css', './packages/backend/src/server/web/error.css'])
|
||||||
.pipe(cssnano({
|
.pipe(cssnano({
|
||||||
zindex: false
|
zindex: false
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -34,6 +34,7 @@ import { QueueStatsChannelService } from './api/stream/channels/queue-stats.js';
|
||||||
import { ServerStatsChannelService } from './api/stream/channels/server-stats.js';
|
import { ServerStatsChannelService } from './api/stream/channels/server-stats.js';
|
||||||
import { UserListChannelService } from './api/stream/channels/user-list.js';
|
import { UserListChannelService } from './api/stream/channels/user-list.js';
|
||||||
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
|
import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
|
||||||
|
import { ClientLoggerService } from './web/ClientLoggerService.js';
|
||||||
import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
|
import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
@ -43,6 +44,7 @@ import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ClientServerService,
|
ClientServerService,
|
||||||
|
ClientLoggerService,
|
||||||
FeedService,
|
FeedService,
|
||||||
UrlPreviewService,
|
UrlPreviewService,
|
||||||
ActivityPubServerService,
|
ActivityPubServerService,
|
||||||
|
|
14
packages/backend/src/server/web/ClientLoggerService.ts
Normal file
14
packages/backend/src/server/web/ClientLoggerService.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import type Logger from '@/logger.js';
|
||||||
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ClientLoggerService {
|
||||||
|
public logger: Logger;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private loggerService: LoggerService,
|
||||||
|
) {
|
||||||
|
this.logger = this.loggerService.getLogger('client');
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import { dirname } from 'node:path';
|
import { dirname } from 'node:path';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import { v4 as uuid } from 'uuid';
|
||||||
import { createBullBoard } from '@bull-board/api';
|
import { createBullBoard } from '@bull-board/api';
|
||||||
import { BullAdapter } from '@bull-board/api/bullAdapter.js';
|
import { BullAdapter } from '@bull-board/api/bullAdapter.js';
|
||||||
import { FastifyAdapter } from '@bull-board/fastify';
|
import { FastifyAdapter } from '@bull-board/fastify';
|
||||||
|
@ -26,6 +27,7 @@ import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityServi
|
||||||
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
|
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
|
||||||
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
|
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
|
||||||
import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
|
import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
|
||||||
|
import type Logger from '@/logger.js';
|
||||||
import { deepClone } from '@/misc/clone.js';
|
import { deepClone } from '@/misc/clone.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
|
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
|
||||||
|
@ -34,6 +36,7 @@ import manifest from './manifest.json' assert { type: 'json' };
|
||||||
import { FeedService } from './FeedService.js';
|
import { FeedService } from './FeedService.js';
|
||||||
import { UrlPreviewService } from './UrlPreviewService.js';
|
import { UrlPreviewService } from './UrlPreviewService.js';
|
||||||
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
||||||
|
import { ClientLoggerService } from './ClientLoggerService.js';
|
||||||
|
|
||||||
const _filename = fileURLToPath(import.meta.url);
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
const _dirname = dirname(_filename);
|
const _dirname = dirname(_filename);
|
||||||
|
@ -46,6 +49,8 @@ const viteOut = `${_dirname}/../../../../../built/_vite_/`;
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ClientServerService {
|
export class ClientServerService {
|
||||||
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.config)
|
@Inject(DI.config)
|
||||||
private config: Config,
|
private config: Config,
|
||||||
|
@ -85,6 +90,7 @@ export class ClientServerService {
|
||||||
private urlPreviewService: UrlPreviewService,
|
private urlPreviewService: UrlPreviewService,
|
||||||
private feedService: FeedService,
|
private feedService: FeedService,
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
|
private clientLoggerService: ClientLoggerService,
|
||||||
|
|
||||||
@Inject('queue:system') public systemQueue: SystemQueue,
|
@Inject('queue:system') public systemQueue: SystemQueue,
|
||||||
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
|
@Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
|
||||||
|
@ -649,6 +655,24 @@ export class ClientServerService {
|
||||||
return await renderBase(reply);
|
return await renderBase(reply);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fastify.setErrorHandler(async (error, request, reply) => {
|
||||||
|
const errId = uuid();
|
||||||
|
this.clientLoggerService.logger.error(`Internal error occured in ${request.routerPath}: ${error.message}`, {
|
||||||
|
path: request.routerPath,
|
||||||
|
params: request.params,
|
||||||
|
query: request.query,
|
||||||
|
code: error.name,
|
||||||
|
stack: error.stack,
|
||||||
|
id: errId,
|
||||||
|
});
|
||||||
|
reply.code(500);
|
||||||
|
reply.header('Cache-Control', 'max-age=10, must-revalidate');
|
||||||
|
return await reply.view('error', {
|
||||||
|
code: error.code,
|
||||||
|
id: errId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
done();
|
done();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
110
packages/backend/src/server/web/error.css
Normal file
110
packages/backend/src/server/web/error.css
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
* {
|
||||||
|
font-family: BIZ UDGothic, Roboto, HelveticaNeue, Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
#misskey_app,
|
||||||
|
#splash {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body,
|
||||||
|
html {
|
||||||
|
background-color: #222;
|
||||||
|
color: #dfddcc;
|
||||||
|
justify-content: center;
|
||||||
|
margin: auto;
|
||||||
|
padding: 10px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0px 12px 0px 12px;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-big {
|
||||||
|
background: linear-gradient(90deg, rgb(134, 179, 0), rgb(74, 179, 0));
|
||||||
|
line-height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-big:hover {
|
||||||
|
background: rgb(153, 204, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-small {
|
||||||
|
background: #444;
|
||||||
|
line-height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-small:hover {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-label-big {
|
||||||
|
color: #222;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 20px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-label-small {
|
||||||
|
color: rgb(153, 204, 0);
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgb(134, 179, 0);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
li {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dont-worry,
|
||||||
|
#msg {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-warning {
|
||||||
|
color: #dec340;
|
||||||
|
height: 4rem;
|
||||||
|
padding-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
display: block;
|
||||||
|
font-family: Fira, FiraCode, monospace;
|
||||||
|
background: #333;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
max-width: 40rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
justify-content: center;
|
||||||
|
margin: auto;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary > * {
|
||||||
|
display: inline;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 500px) {
|
||||||
|
details {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
}
|
65
packages/backend/src/server/web/views/error.pug
Normal file
65
packages/backend/src/server/web/views/error.pug
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
doctype html
|
||||||
|
|
||||||
|
//
|
||||||
|
-
|
||||||
|
_____ _ _
|
||||||
|
| |_|___ ___| |_ ___ _ _
|
||||||
|
| | | | |_ -|_ -| '_| -_| | |
|
||||||
|
|_|_|_|_|___|___|_,_|___|_ |
|
||||||
|
|___|
|
||||||
|
Thank you for using Misskey!
|
||||||
|
If you are reading this message... how about joining the development?
|
||||||
|
https://github.com/misskey-dev/misskey
|
||||||
|
|
||||||
|
|
||||||
|
html
|
||||||
|
|
||||||
|
head
|
||||||
|
meta(charset='utf-8')
|
||||||
|
meta(name='viewport' content='width=device-width, initial-scale=1')
|
||||||
|
meta(name='application-name' content='Misskey')
|
||||||
|
meta(name='referrer' content='origin')
|
||||||
|
|
||||||
|
title
|
||||||
|
block title
|
||||||
|
= 'An error has occurred... | Misskey'
|
||||||
|
|
||||||
|
style
|
||||||
|
include ../error.css
|
||||||
|
|
||||||
|
body
|
||||||
|
svg.icon-warning(xmlns="http://www.w3.org/2000/svg", viewBox="0 0 24 24", stroke-width="2", stroke="currentColor", fill="none", stroke-linecap="round", stroke-linejoin="round")
|
||||||
|
path(stroke="none", d="M0 0h24v24H0z", fill="none")
|
||||||
|
path(d="M12 9v2m0 4v.01")
|
||||||
|
path(d="M5 19h14a2 2 0 0 0 1.84 -2.75l-7.1 -12.25a2 2 0 0 0 -3.5 0l-7.1 12.25a2 2 0 0 0 1.75 2.75")
|
||||||
|
|
||||||
|
h1 An error has occurred!
|
||||||
|
|
||||||
|
button.button-big(onclick="location.reload();")
|
||||||
|
span.button-label-big Refresh
|
||||||
|
|
||||||
|
p.dont-worry Don't worry, it's (probably) not your fault.
|
||||||
|
|
||||||
|
p If reloading after a period of time does not resolve the problem, contact the server administrator with the following ERROR ID.
|
||||||
|
|
||||||
|
div#errors
|
||||||
|
code.
|
||||||
|
ERROR CODE: #{code}
|
||||||
|
ERROR ID: #{id}
|
||||||
|
|
||||||
|
p You may also try the following options:
|
||||||
|
|
||||||
|
p Update your os and browser.
|
||||||
|
p Disable an adblocker.
|
||||||
|
|
||||||
|
a(href="/flush")
|
||||||
|
button.button-small
|
||||||
|
span.button-label-small Clear preferences and cache
|
||||||
|
br
|
||||||
|
a(href="/cli")
|
||||||
|
button.button-small
|
||||||
|
span.button-label-small Start the simple client
|
||||||
|
br
|
||||||
|
a(href="/bios")
|
||||||
|
button.button-small
|
||||||
|
span.button-label-small Start the repair tool
|
Loading…
Reference in a new issue