mirror of
				https://codeberg.org/yeentown/barkey.git
				synced 2025-11-04 15:34:13 +00: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…
	
	Add table
		
		Reference in a new issue