Merge branch 'develop' into mkjs-n

This commit is contained in:
tamaina 2023-07-03 04:35:13 +00:00
commit cef5feaee2
17 changed files with 137 additions and 33 deletions

View file

@ -14,8 +14,13 @@
## 13.x.x (unreleased) ## 13.x.x (unreleased)
### General
- identicon生成を無効にしてパフォーマンスを向上させることができるようになりました
- サーバーのマシン情報の公開を無効にしてパフォーマンスを向上させることができるようになりました
### Client ### Client
- Fix: サーバーメトリクスが90度傾いている - Fix: サーバーメトリクスが90度傾いている
- Fix: sparkle内にリンクを入れるとクリック不能になる問題の修正
## 13.13.2 ## 13.13.2

2
locales/index.d.ts vendored
View file

@ -1066,6 +1066,8 @@ export interface Locale {
"additionalEmojiDictionary": string; "additionalEmojiDictionary": string;
"installed": string; "installed": string;
"branding": string; "branding": string;
"enableServerMachineStats": string;
"enableIdenticonGeneration": string;
"_initialAccountSetting": { "_initialAccountSetting": {
"accountCreated": string; "accountCreated": string;
"letsStartAccountSetup": string; "letsStartAccountSetup": string;

View file

@ -1063,6 +1063,8 @@ goToMisskey: "Misskeyへ"
additionalEmojiDictionary: "絵文字の追加辞書" additionalEmojiDictionary: "絵文字の追加辞書"
installed: "インストール済み" installed: "インストール済み"
branding: "ブランディング" branding: "ブランディング"
enableServerMachineStats: "サーバーのマシン情報を公開する"
enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にする"
_initialAccountSetting: _initialAccountSetting:
accountCreated: "アカウントの作成が完了しました!" accountCreated: "アカウントの作成が完了しました!"

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View file

@ -0,0 +1,13 @@
export class AddMetaOptions1688280713783 {
name = 'AddMetaOptions1688280713783'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "enableServerMachineStats" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`ALTER TABLE "meta" ADD "enableIdenticonGeneration" boolean NOT NULL DEFAULT true`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableIdenticonGeneration"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableServerMachineStats"`);
}
}

View file

@ -3,6 +3,7 @@ import si from 'systeminformation';
import Xev from 'xev'; import Xev from 'xev';
import * as osUtils from 'os-utils'; import * as osUtils from 'os-utils';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
import type { OnApplicationShutdown } from '@nestjs/common'; import type { OnApplicationShutdown } from '@nestjs/common';
const ev = new Xev(); const ev = new Xev();
@ -14,9 +15,10 @@ const round = (num: number) => Math.round(num * 10) / 10;
@Injectable() @Injectable()
export class ServerStatsService implements OnApplicationShutdown { export class ServerStatsService implements OnApplicationShutdown {
private intervalId: NodeJS.Timer; private intervalId: NodeJS.Timer | null = null;
constructor( constructor(
private metaService: MetaService,
) { ) {
} }
@ -24,7 +26,9 @@ export class ServerStatsService implements OnApplicationShutdown {
* Report server stats regularly * Report server stats regularly
*/ */
@bindThis @bindThis
public start(): void { public async start(): Promise<void> {
if (!(await this.metaService.fetch(true)).enableServerMachineStats) return;
const log = [] as any[]; const log = [] as any[];
ev.on('requestServerStatsLog', x => { ev.on('requestServerStatsLog', x => {
@ -64,8 +68,10 @@ export class ServerStatsService implements OnApplicationShutdown {
@bindThis @bindThis
public dispose(): void { public dispose(): void {
if (this.intervalId) {
clearInterval(this.intervalId); clearInterval(this.intervalId);
} }
}
@bindThis @bindThis
public onApplicationShutdown(signal?: string | undefined): void { public onApplicationShutdown(signal?: string | undefined): void {

View file

@ -413,6 +413,16 @@ export class Meta {
}) })
public enableChartsForFederatedInstances: boolean; public enableChartsForFederatedInstances: boolean;
@Column('boolean', {
default: false,
})
public enableServerMachineStats: boolean;
@Column('boolean', {
default: true,
})
public enableIdenticonGeneration: boolean;
@Column('jsonb', { @Column('jsonb', {
default: { }, default: { },
}) })

View file

@ -16,6 +16,7 @@ import { createTemp } from '@/misc/create-temp.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { LoggerService } from '@/core/LoggerService.js'; import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
import { ActivityPubServerService } from './ActivityPubServerService.js'; import { ActivityPubServerService } from './ActivityPubServerService.js';
import { NodeinfoServerService } from './NodeinfoServerService.js'; import { NodeinfoServerService } from './NodeinfoServerService.js';
import { ApiServerService } from './api/ApiServerService.js'; import { ApiServerService } from './api/ApiServerService.js';
@ -45,6 +46,7 @@ export class ServerService implements OnApplicationShutdown {
@Inject(DI.emojisRepository) @Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository, private emojisRepository: EmojisRepository,
private metaService: MetaService,
private userEntityService: UserEntityService, private userEntityService: UserEntityService,
private apiServerService: ApiServerService, private apiServerService: ApiServerService,
private openApiServerService: OpenApiServerService, private openApiServerService: OpenApiServerService,
@ -161,11 +163,16 @@ export class ServerService implements OnApplicationShutdown {
}); });
fastify.get<{ Params: { x: string } }>('/identicon/:x', async (request, reply) => { fastify.get<{ Params: { x: string } }>('/identicon/:x', async (request, reply) => {
const [temp, cleanup] = await createTemp();
await genIdenticon(request.params.x, fs.createWriteStream(temp));
reply.header('Content-Type', 'image/png'); reply.header('Content-Type', 'image/png');
reply.header('Cache-Control', 'public, max-age=86400'); reply.header('Cache-Control', 'public, max-age=86400');
if ((await this.metaService.fetch()).enableIdenticonGeneration) {
const [temp, cleanup] = await createTemp();
await genIdenticon(request.params.x, fs.createWriteStream(temp));
return fs.createReadStream(temp).on('close', () => cleanup()); return fs.createReadStream(temp).on('close', () => cleanup());
} else {
return reply.redirect('/static-assets/avatar.png');
}
}); });
fastify.get<{ Params: { code: string } }>('/verify-email/:code', async (request, reply) => { fastify.get<{ Params: { code: string } }>('/verify-email/:code', async (request, reply) => {

View file

@ -96,6 +96,8 @@ export default class extends Endpoint<'admin/meta'> {
enableActiveEmailValidation: instance.enableActiveEmailValidation, enableActiveEmailValidation: instance.enableActiveEmailValidation,
enableChartsForRemoteUser: instance.enableChartsForRemoteUser, enableChartsForRemoteUser: instance.enableChartsForRemoteUser,
enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances, enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances,
enableServerMachineStats: instance.enableServerMachineStats,
enableIdenticonGeneration: instance.enableIdenticonGeneration,
}; };
}); });
} }

View file

@ -304,6 +304,14 @@ export default class extends Endpoint<'admin/update-meta'> {
set.enableChartsForFederatedInstances = ps.enableChartsForFederatedInstances; set.enableChartsForFederatedInstances = ps.enableChartsForFederatedInstances;
} }
if (ps.enableServerMachineStats !== undefined) {
set.enableServerMachineStats = ps.enableServerMachineStats;
}
if (ps.enableIdenticonGeneration !== undefined) {
set.enableIdenticonGeneration = ps.enableIdenticonGeneration;
}
if (ps.serverRules !== undefined) { if (ps.serverRules !== undefined) {
set.serverRules = ps.serverRules; set.serverRules = ps.serverRules;
} }

View file

@ -2,9 +2,12 @@ import * as os from 'node:os';
import si from 'systeminformation'; import si from 'systeminformation';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js'; import { Endpoint } from '@/server/api/endpoint-base.js';
import { MetaService } from '@/core/MetaService.js';
export const meta = { export const meta = {
requireCredential: false, requireCredential: false,
allowGet: true,
cacheSec: 60 * 1,
tags: ['meta'], tags: ['meta'],
} as const; } as const;
@ -19,8 +22,24 @@ export const paramDef = {
@Injectable() @Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor( constructor(
private metaService: MetaService,
) { ) {
super(meta, paramDef, async () => { super(meta, paramDef, async () => {
if (!(await this.metaService.fetch()).enableServerMachineStats) return {
machine: '?',
cpu: {
model: '?',
cores: 0,
},
mem: {
total: 0,
},
fs: {
total: 0,
used: 0,
},
};
const memStats = await si.mem(); const memStats = await si.mem();
const fsStats = await si.fsSize(); const fsStats = await si.fsSize();

View file

@ -22,10 +22,13 @@ import TestWebGL2 from '@/workers/test-webgl2?worker';
import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch'; import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch';
import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash'; import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash';
const workerPromise = new Promise<WorkerMultiDispatch | null>(resolve => { const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resolve => {
// Web Worker // Web Worker
if (import.meta.env.MODE === 'test') { if (import.meta.env.MODE === 'test') {
resolve(null); const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
resolve(canvas);
return; return;
} }
const testWorker = new TestWebGL2(); const testWorker = new TestWebGL2();
@ -38,7 +41,10 @@ const workerPromise = new Promise<WorkerMultiDispatch | null>(resolve => {
resolve(workers); resolve(workers);
if (_DEV_) console.log('WebGL2 in worker is supported!'); if (_DEV_) console.log('WebGL2 in worker is supported!');
} else { } else {
resolve(null); const canvas = document.createElement('canvas');
canvas.width = 64;
canvas.height = 64;
resolve(canvas);
if (_DEV_) console.log('WebGL2 in worker is not supported...'); if (_DEV_) console.log('WebGL2 in worker is not supported...');
} }
testWorker.terminate(); testWorker.terminate();
@ -70,6 +76,7 @@ const props = withDefaults(defineProps<{
width?: number; width?: number;
cover?: boolean; cover?: boolean;
forceBlurhash?: boolean; forceBlurhash?: boolean;
onlyAvgColor?: boolean; // Blurhash使
}>(), { }>(), {
transition: null, transition: null,
src: null, src: null,
@ -79,6 +86,7 @@ const props = withDefaults(defineProps<{
width: 64, width: 64,
cover: true, cover: true,
forceBlurhash: false, forceBlurhash: false,
onlyAvgColor: false,
}); });
const viewId = uuid(); const viewId = uuid();
@ -139,8 +147,8 @@ function drawImage(bitmap: CanvasImageSource) {
ctx.drawImage(bitmap, 0, 0, canvasWidth, canvasHeight); ctx.drawImage(bitmap, 0, 0, canvasWidth, canvasHeight);
} }
async function draw() { function drawAvg() {
if (!canvas.value || props.hash == null) return; if (!canvas.value || !props.hash) return;
const ctx = canvas.value.getContext('2d'); const ctx = canvas.value.getContext('2d');
if (!ctx) return; if (!ctx) return;
@ -149,25 +157,28 @@ async function draw() {
ctx.beginPath(); ctx.beginPath();
ctx.fillStyle = extractAvgColorFromBlurhash(props.hash) ?? '#888'; ctx.fillStyle = extractAvgColorFromBlurhash(props.hash) ?? '#888';
ctx.fillRect(0, 0, canvasWidth, canvasHeight); ctx.fillRect(0, 0, canvasWidth, canvasHeight);
}
const workers = await workerPromise; async function draw() {
if (workers) { if (props.hash == null) return;
workers.postMessage(
drawAvg();
if (props.onlyAvgColor) return;
const work = await canvasPromise;
if (work instanceof WorkerMultiDispatch) {
work.postMessage(
{ {
id: viewId, id: viewId,
hash: props.hash, hash: props.hash,
width: canvasWidth,
height: canvasHeight,
}, },
undefined, undefined,
); );
} else { } else {
try { try {
const work = document.createElement('canvas');
work.width = canvasWidth;
work.height = canvasHeight;
render(props.hash, work); render(props.hash, work);
ctx.drawImage(work, 0, 0, canvasWidth, canvasHeight); drawImage(work);
} catch (error) { } catch (error) {
console.error('Error occured during drawing blurhash', error); console.error('Error occured during drawing blurhash', error);
} }
@ -179,9 +190,9 @@ function workerOnMessage(event: MessageEvent) {
drawImage(event.data.bitmap as ImageBitmap); drawImage(event.data.bitmap as ImageBitmap);
} }
workerPromise.then(worker => { canvasPromise.then(work => {
if (worker) { if (work instanceof WorkerMultiDispatch) {
worker.addListener(workerOnMessage); work.addListener(workerOnMessage);
} }
draw(); draw();
@ -204,8 +215,10 @@ onMounted(() => {
}); });
onUnmounted(() => { onUnmounted(() => {
workerPromise.then(worker => { canvasPromise.then(work => {
worker?.removeListener(workerOnMessage); if (work instanceof WorkerMultiDispatch) {
work.removeListener(workerOnMessage);
}
}); });
}); });
</script> </script>

View file

@ -32,7 +32,8 @@
</path> </path>
</svg> </svg>
--> -->
<svg v-for="particle in particles" :key="particle.id" :width="width" :height="height" :viewBox="`0 0 ${width} ${height}`" xmlns="http://www.w3.org/2000/svg" style="position: absolute; top: -32px; left: -32px;"> <!-- MFMで上位レイヤーに表示されるためリンクをクリックできるようにstyleにpointer-events: none;を付与 -->
<svg v-for="particle in particles" :key="particle.id" :width="width" :height="height" :viewBox="`0 0 ${width} ${height}`" xmlns="http://www.w3.org/2000/svg" style="position: absolute; top: -32px; left: -32px; pointer-events: none;">
<path <path
style="transform-origin: center; transform-box: fill-box;" style="transform-origin: center; transform-box: fill-box;"
:transform="`translate(${particle.x} ${particle.y})`" :transform="`translate(${particle.x} ${particle.y})`"
@ -115,6 +116,5 @@ onUnmounted(() => {
.root { .root {
position: relative; position: relative;
display: inline-block; display: inline-block;
pointer-events: none;
} }
</style> </style>

View file

@ -1,6 +1,6 @@
<template> <template>
<component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.animation]: animation, [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick"> <component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.animation]: animation, [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick">
<img :class="$style.inner" :src="url" :hash="user?.avatarBlurhash" :cover="true"/> <MkImgWithBlurhash :class="$style.inner" :src="url" :hash="user?.avatarBlurhash" :cover="true" :onlyAvgColor="true"/>
<MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/> <MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/>
<div v-if="user.isCat" :class="[$style.ears]"> <div v-if="user.isCat" :class="[$style.ears]">
<div :class="$style.earLeft"> <div :class="$style.earLeft">
@ -24,6 +24,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { watch } from 'vue'; import { watch } from 'vue';
import * as misskey from 'misskey-js'; import * as misskey from 'misskey-js';
import MkImgWithBlurhash from '../MkImgWithBlurhash.vue';
import MkA from './MkA.vue'; import MkA from './MkA.vue';
import { getStaticImageUrl } from '@/scripts/media-proxy'; import { getStaticImageUrl } from '@/scripts/media-proxy';
import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash'; import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash';

View file

@ -4,6 +4,14 @@
<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
<FormSuspense :p="init"> <FormSuspense :p="init">
<div class="_gaps_s"> <div class="_gaps_s">
<MkSwitch v-model="enableServerMachineStats">
<template #label>{{ i18n.ts.enableServerMachineStats }}</template>
</MkSwitch>
<MkSwitch v-model="enableIdenticonGeneration">
<template #label>{{ i18n.ts.enableIdenticonGeneration }}</template>
</MkSwitch>
<MkSwitch v-model="enableChartsForRemoteUser"> <MkSwitch v-model="enableChartsForRemoteUser">
<template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template> <template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template>
</MkSwitch> </MkSwitch>
@ -27,17 +35,23 @@ import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata'; import { definePageMetadata } from '@/scripts/page-metadata';
import MkSwitch from '@/components/MkSwitch.vue'; import MkSwitch from '@/components/MkSwitch.vue';
let enableServerMachineStats: boolean = $ref(false);
let enableIdenticonGeneration: boolean = $ref(false);
let enableChartsForRemoteUser: boolean = $ref(false); let enableChartsForRemoteUser: boolean = $ref(false);
let enableChartsForFederatedInstances: boolean = $ref(false); let enableChartsForFederatedInstances: boolean = $ref(false);
async function init() { async function init() {
const meta = await os.api('admin/meta'); const meta = await os.api('admin/meta');
enableServerMachineStats = meta.enableServerMachineStats;
enableIdenticonGeneration = meta.enableIdenticonGeneration;
enableChartsForRemoteUser = meta.enableChartsForRemoteUser; enableChartsForRemoteUser = meta.enableChartsForRemoteUser;
enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances; enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances;
} }
function save() { function save() {
os.apiWithDialog('admin/update-meta', { os.apiWithDialog('admin/update-meta', {
enableServerMachineStats,
enableIdenticonGeneration,
enableChartsForRemoteUser, enableChartsForRemoteUser,
enableChartsForFederatedInstances, enableChartsForFederatedInstances,
}).then(() => { }).then(() => {

View file

@ -62,7 +62,7 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name,
const meta = ref(null); const meta = ref(null);
os.api('server-info', {}).then(res => { os.apiGet('server-info', {}).then(res => {
meta.value = res; meta.value = res;
}); });

View file

@ -1,5 +1,7 @@
import { render } from 'buraha'; import { render } from 'buraha';
const canvas = new OffscreenCanvas(64, 64);
onmessage = (event) => { onmessage = (event) => {
// console.log(event.data); // console.log(event.data);
if (!('id' in event.data && typeof event.data.id === 'string')) { if (!('id' in event.data && typeof event.data.id === 'string')) {
@ -8,8 +10,8 @@ onmessage = (event) => {
if (!('hash' in event.data && typeof event.data.hash === 'string')) { if (!('hash' in event.data && typeof event.data.hash === 'string')) {
return; return;
} }
const work = new OffscreenCanvas(event.data.width ?? 64, event.data.height ?? 64);
render(event.data.hash, work); render(event.data.hash, canvas);
const bitmap = work.transferToImageBitmap(); const bitmap = canvas.transferToImageBitmap();
postMessage({ id: event.data.id, bitmap }); postMessage({ id: event.data.id, bitmap });
}; };