From 611e303bab1ace64c7ab1611e35d850a96f0bace Mon Sep 17 00:00:00 2001 From: Acid Chicken Date: Thu, 23 May 2024 15:19:52 +0900 Subject: [PATCH 1/9] feat(backend): add /healthz endpoint (#13834) * feat(backend): add /healthz endpoint * feat(backend): also check meilisearch status if available * style: header * chore: no-store * chore: healthcheck.sh * style: format --- healthcheck.sh | 2 +- packages/backend/src/boot/entry.ts | 3 ++ packages/backend/src/boot/ready.ts | 6 +++ .../backend/src/server/HealthServerService.ts | 54 +++++++++++++++++++ packages/backend/src/server/ServerModule.ts | 2 + packages/backend/src/server/ServerService.ts | 3 ++ 6 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 packages/backend/src/boot/ready.ts create mode 100644 packages/backend/src/server/HealthServerService.ts diff --git a/healthcheck.sh b/healthcheck.sh index d6d416c7a1..dcfcf76786 100644 --- a/healthcheck.sh +++ b/healthcheck.sh @@ -4,4 +4,4 @@ # SPDX-License-Identifier: AGPL-3.0-only PORT=$(grep '^port:' /misskey/.config/default.yml | awk 'NR==1{print $2; exit}') -curl -s -S -o /dev/null "http://localhost:${PORT}" +curl -Sfso/dev/null "http://localhost:${PORT}/healthz" diff --git a/packages/backend/src/boot/entry.ts b/packages/backend/src/boot/entry.ts index 6b8e83d4f9..04c6ca9723 100644 --- a/packages/backend/src/boot/entry.ts +++ b/packages/backend/src/boot/entry.ts @@ -15,6 +15,7 @@ import Logger from '@/logger.js'; import { envOption } from '../env.js'; import { masterMain } from './master.js'; import { workerMain } from './worker.js'; +import { readyRef } from './ready.js'; import 'reflect-metadata'; @@ -79,6 +80,8 @@ if (cluster.isWorker || envOption.disableClustering) { await workerMain(); } +readyRef.value = true; + // ユニットテスト時にMisskeyが子プロセスで起動された時のため // それ以外のときは process.send は使えないので弾く if (process.send) { diff --git a/packages/backend/src/boot/ready.ts b/packages/backend/src/boot/ready.ts new file mode 100644 index 0000000000..591ae5cb58 --- /dev/null +++ b/packages/backend/src/boot/ready.ts @@ -0,0 +1,6 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export const readyRef = { value: false }; diff --git a/packages/backend/src/server/HealthServerService.ts b/packages/backend/src/server/HealthServerService.ts new file mode 100644 index 0000000000..2c3ed85925 --- /dev/null +++ b/packages/backend/src/server/HealthServerService.ts @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import * as Redis from 'ioredis'; +import { DataSource } from 'typeorm'; +import { bindThis } from '@/decorators.js'; +import { DI } from '@/di-symbols.js'; +import { readyRef } from '@/boot/ready.js'; +import type { FastifyInstance, FastifyPluginOptions } from 'fastify'; +import type { MeiliSearch } from 'meilisearch'; + +@Injectable() +export class HealthServerService { + constructor( + @Inject(DI.redis) + private redis: Redis.Redis, + + @Inject(DI.redisForPub) + private redisForPub: Redis.Redis, + + @Inject(DI.redisForSub) + private redisForSub: Redis.Redis, + + @Inject(DI.redisForTimelines) + private redisForTimelines: Redis.Redis, + + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.meilisearch) + private meilisearch: MeiliSearch | null, + ) {} + + @bindThis + public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { + fastify.get('/', async (request, reply) => { + reply.code(await Promise.all([ + new Promise((resolve, reject) => readyRef.value ? resolve() : reject()), + this.redis.ping(), + this.redisForPub.ping(), + this.redisForSub.ping(), + this.redisForTimelines.ping(), + this.db.query('SELECT 1'), + ...(this.meilisearch ? [this.meilisearch.health()] : []), + ]).then(() => 200, () => 503)); + reply.header('Cache-Control', 'no-store'); + }); + + done(); + } +} diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts index f43968d236..12d5061985 100644 --- a/packages/backend/src/server/ServerModule.ts +++ b/packages/backend/src/server/ServerModule.ts @@ -8,6 +8,7 @@ import { EndpointsModule } from '@/server/api/EndpointsModule.js'; import { CoreModule } from '@/core/CoreModule.js'; import { ApiCallService } from './api/ApiCallService.js'; import { FileServerService } from './FileServerService.js'; +import { HealthServerService } from './HealthServerService.js'; import { NodeinfoServerService } from './NodeinfoServerService.js'; import { ServerService } from './ServerService.js'; import { WellKnownServerService } from './WellKnownServerService.js'; @@ -55,6 +56,7 @@ import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js ClientServerService, ClientLoggerService, FeedService, + HealthServerService, UrlPreviewService, ActivityPubServerService, FileServerService, diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index da17a88e03..3572f16627 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -28,6 +28,7 @@ import { ApiServerService } from './api/ApiServerService.js'; import { StreamingApiServerService } from './api/StreamingApiServerService.js'; import { WellKnownServerService } from './WellKnownServerService.js'; import { FileServerService } from './FileServerService.js'; +import { HealthServerService } from './HealthServerService.js'; import { ClientServerService } from './web/ClientServerService.js'; import { OpenApiServerService } from './api/openapi/OpenApiServerService.js'; import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js'; @@ -61,6 +62,7 @@ export class ServerService implements OnApplicationShutdown { private wellKnownServerService: WellKnownServerService, private nodeinfoServerService: NodeinfoServerService, private fileServerService: FileServerService, + private healthServerService: HealthServerService, private clientServerService: ClientServerService, private globalEventService: GlobalEventService, private loggerService: LoggerService, @@ -108,6 +110,7 @@ export class ServerService implements OnApplicationShutdown { fastify.register(this.wellKnownServerService.createServer); fastify.register(this.oauth2ProviderService.createServer, { prefix: '/oauth' }); fastify.register(this.oauth2ProviderService.createTokenServer, { prefix: '/oauth/token' }); + fastify.register(this.healthServerService.createServer, { prefix: '/healthz' }); fastify.get<{ Params: { path: string }; Querystring: { static?: any; badge?: any; }; }>('/emoji/:path(.*)', async (request, reply) => { const path = request.params.path; From 83a9aa4533912c685a74a107be3894c4a85a338c Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 23 May 2024 15:55:47 +0900 Subject: [PATCH 2/9] feat: suspend instance improvements (#13861) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(backend): dead instance detection * feat(backend): suspend type detection * feat(frontend): show suspend reason on frontend * feat(backend): resume federation automatically if the server is automatically suspended * docs(changelog): 配信停止まわりの改善 * lint: fix lint errors * Update packages/frontend/src/pages/instance-info.vue * lint: fix lint error * chore: suspendedState => suspensionState --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 3 ++ locales/index.d.ts | 32 ++++++++++++ locales/ja-JP.yml | 10 ++++ .../1716345015347-NotRespondingSince.js | 16 ++++++ ...8870-SuspensionStateInsteadOfIsSspended.js | 50 +++++++++++++++++++ .../core/entities/InstanceEntityService.ts | 3 +- packages/backend/src/models/Instance.ts | 17 +++++-- .../models/json-schema/federation-instance.ts | 5 ++ .../processors/DeliverProcessorService.ts | 14 +++++- .../queue/processors/InboxProcessorService.ts | 2 + .../src/server/api/ApiServerService.ts | 2 +- .../admin/federation/update-instance.ts | 11 +++- .../frontend/src/pages/admin/federation.vue | 14 +++++- packages/frontend/src/pages/instance-info.vue | 29 +++++++++-- packages/misskey-js/src/autogen/types.ts | 2 + 15 files changed, 193 insertions(+), 17 deletions(-) create mode 100644 packages/backend/migration/1716345015347-NotRespondingSince.js create mode 100644 packages/backend/migration/1716447138870-SuspensionStateInsteadOfIsSspended.js diff --git a/CHANGELOG.md b/CHANGELOG.md index ce66d779a3..21bb3b2e8e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ - サスペンド済みユーザーか - 鍵アカウントユーザーか - 「アカウントを見つけやすくする」が有効なユーザーか +- Enhance: Goneを出さずに終了したサーバーへの配信停止を自動的に行うように + - もしそのようなサーバーからから配信が届いた場合には自動的に配信を再開します +- Enhance: 配信停止の理由を表示するように - Fix: Play作成時に設定した公開範囲が機能していない問題を修正 - Fix: 正規化されていない状態のhashtagが連合されてきたhtmlに含まれているとhashtagが正しくhashtagに復元されない問題を修正 - Fix: みつけるのアンケート欄にてチャンネルのアンケートが含まれてしまう問題を修正 diff --git a/locales/index.d.ts b/locales/index.d.ts index d5d6ef0f34..991ec1ac1d 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -4972,6 +4972,38 @@ export interface Locale extends ILocale { * お問い合わせ */ "inquiry": string; + "_delivery": { + /** + * 配信状態 + */ + "status": string; + /** + * 配信停止 + */ + "stop": string; + /** + * 配信再開 + */ + "resume": string; + "_type": { + /** + * 配信中 + */ + "none": string; + /** + * 手動停止中 + */ + "manuallySuspended": string; + /** + * サーバー削除のため停止中 + */ + "goneSuspended": string; + /** + * サーバー応答なしのため停止中 + */ + "autoSuspendedForNotResponding": string; + }; + }; "_bubbleGame": { /** * 遊び方 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 9aa1e6e6a0..d7635acc2e 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1240,6 +1240,16 @@ noDescription: "説明文はありません" alwaysConfirmFollow: "フォローの際常に確認する" inquiry: "お問い合わせ" +_delivery: + status: "配信状態" + stop: "配信停止" + resume: "配信再開" + _type: + none: "配信中" + manuallySuspended: "手動停止中" + goneSuspended: "サーバー削除のため停止中" + autoSuspendedForNotResponding: "サーバー応答なしのため停止中" + _bubbleGame: howToPlay: "遊び方" hold: "ホールド" diff --git a/packages/backend/migration/1716345015347-NotRespondingSince.js b/packages/backend/migration/1716345015347-NotRespondingSince.js new file mode 100644 index 0000000000..fc4ee6639a --- /dev/null +++ b/packages/backend/migration/1716345015347-NotRespondingSince.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class NotRespondingSince1716345015347 { + name = 'NotRespondingSince1716345015347' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "instance" ADD "notRespondingSince" TIMESTAMP WITH TIME ZONE`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "notRespondingSince"`); + } +} diff --git a/packages/backend/migration/1716447138870-SuspensionStateInsteadOfIsSspended.js b/packages/backend/migration/1716447138870-SuspensionStateInsteadOfIsSspended.js new file mode 100644 index 0000000000..4808a9a3db --- /dev/null +++ b/packages/backend/migration/1716447138870-SuspensionStateInsteadOfIsSspended.js @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class SuspensionStateInsteadOfIsSspended1716345771510 { + name = 'SuspensionStateInsteadOfIsSspended1716345771510' + + async up(queryRunner) { + await queryRunner.query(`CREATE TYPE "public"."instance_suspensionstate_enum" AS ENUM('none', 'manuallySuspended', 'goneSuspended', 'autoSuspendedForNotResponding')`); + + await queryRunner.query(`DROP INDEX "public"."IDX_34500da2e38ac393f7bb6b299c"`); + + await queryRunner.query(`ALTER TABLE "instance" RENAME COLUMN "isSuspended" TO "suspensionState"`); + + await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "suspensionState" DROP DEFAULT`); + + await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "suspensionState" TYPE "public"."instance_suspensionstate_enum" USING ( + CASE "suspensionState" + WHEN TRUE THEN 'manuallySuspended'::instance_suspensionstate_enum + ELSE 'none'::instance_suspensionstate_enum + END + )`); + + await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "suspensionState" SET DEFAULT 'none'`); + + await queryRunner.query(`CREATE INDEX "IDX_3ede46f507c87ad698051d56a8" ON "instance" ("suspensionState") `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_3ede46f507c87ad698051d56a8"`); + + await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "suspensionState" DROP DEFAULT`); + + await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "suspensionState" TYPE boolean USING ( + CASE "suspensionState" + WHEN 'none'::instance_suspensionstate_enum THEN FALSE + ELSE TRUE + END + )`); + + await queryRunner.query(`ALTER TABLE "instance" ALTER COLUMN "suspensionState" SET DEFAULT false`); + + await queryRunner.query(`ALTER TABLE "instance" RENAME COLUMN "suspensionState" TO "isSuspended"`); + + await queryRunner.query(`CREATE INDEX "IDX_34500da2e38ac393f7bb6b299c" ON "instance" ("isSuspended") `); + + await queryRunner.query(`DROP TYPE "public"."instance_suspensionstate_enum"`); + } +} diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts index e46bd8b963..9117b13914 100644 --- a/packages/backend/src/core/entities/InstanceEntityService.ts +++ b/packages/backend/src/core/entities/InstanceEntityService.ts @@ -39,7 +39,8 @@ export class InstanceEntityService { followingCount: instance.followingCount, followersCount: instance.followersCount, isNotResponding: instance.isNotResponding, - isSuspended: instance.isSuspended, + isSuspended: instance.suspensionState !== 'none', + suspensionState: instance.suspensionState, isBlocked: this.utilityService.isBlockedHost(meta.blockedHosts, instance.host), softwareName: instance.softwareName, softwareVersion: instance.softwareVersion, diff --git a/packages/backend/src/models/Instance.ts b/packages/backend/src/models/Instance.ts index 9863c9d75d..17cd5c6665 100644 --- a/packages/backend/src/models/Instance.ts +++ b/packages/backend/src/models/Instance.ts @@ -81,13 +81,22 @@ export class MiInstance { public isNotResponding: boolean; /** - * このインスタンスへの配信を停止するか + * このインスタンスと不通になった日時 + */ + @Column('timestamp with time zone', { + nullable: true, + }) + public notRespondingSince: Date | null; + + /** + * このインスタンスへの配信状態 */ @Index() - @Column('boolean', { - default: false, + @Column('enum', { + default: 'none', + enum: ['none', 'manuallySuspended', 'goneSuspended', 'autoSuspendedForNotResponding'], }) - public isSuspended: boolean; + public suspensionState: 'none' | 'manuallySuspended' | 'goneSuspended' | 'autoSuspendedForNotResponding'; @Column('varchar', { length: 64, nullable: true, diff --git a/packages/backend/src/models/json-schema/federation-instance.ts b/packages/backend/src/models/json-schema/federation-instance.ts index 42d98fe523..ed40d405c6 100644 --- a/packages/backend/src/models/json-schema/federation-instance.ts +++ b/packages/backend/src/models/json-schema/federation-instance.ts @@ -45,6 +45,11 @@ export const packedFederationInstanceSchema = { type: 'boolean', optional: false, nullable: false, }, + suspensionState: { + type: 'string', + nullable: false, optional: false, + enum: ['none', 'manuallySuspended', 'goneSuspended', 'autoSuspendedForNotResponding'], + }, isBlocked: { type: 'boolean', optional: false, nullable: false, diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 5fed070929..b73195afc3 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -5,6 +5,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Bull from 'bullmq'; +import { Not } from 'typeorm'; import { DI } from '@/di-symbols.js'; import type { InstancesRepository } from '@/models/_.js'; import type Logger from '@/logger.js'; @@ -62,7 +63,7 @@ export class DeliverProcessorService { if (suspendedHosts == null) { suspendedHosts = await this.instancesRepository.find({ where: { - isSuspended: true, + suspensionState: Not('none'), }, }); this.suspendedHostsCache.set(suspendedHosts); @@ -79,6 +80,7 @@ export class DeliverProcessorService { if (i.isNotResponding) { this.federatedInstanceService.update(i.id, { isNotResponding: false, + notRespondingSince: null, }); } @@ -98,7 +100,15 @@ export class DeliverProcessorService { if (!i.isNotResponding) { this.federatedInstanceService.update(i.id, { isNotResponding: true, + notRespondingSince: new Date(), }); + } else if (i.notRespondingSince) { + // 1週間以上不通ならサスペンド + if (i.suspensionState === 'none' && i.notRespondingSince.getTime() <= Date.now() - 1000 * 60 * 60 * 24 * 7) { + this.federatedInstanceService.update(i.id, { + suspensionState: 'autoSuspendedForNotResponding', + }); + } } this.apRequestChart.deliverFail(); @@ -116,7 +126,7 @@ export class DeliverProcessorService { if (job.data.isSharedInbox && res.statusCode === 410) { this.federatedInstanceService.fetch(host).then(i => { this.federatedInstanceService.update(i.id, { - isSuspended: true, + suspensionState: 'goneSuspended', }); }); throw new Bull.UnrecoverableError(`${host} is gone`); diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index 1d05f4ade1..f465339075 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -188,6 +188,8 @@ export class InboxProcessorService { this.federatedInstanceService.update(i.id, { latestRequestReceivedAt: new Date(), isNotResponding: false, + // もしサーバーが死んでるために配信が止まっていた場合には自動的に復活させてあげる + suspensionState: i.suspensionState === 'autoSuspendedForNotResponding' ? 'none' : undefined, }); this.fetchInstanceMetadataService.fetchInstanceMetadata(i); diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index e99244cdd0..4a5935f930 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -137,7 +137,7 @@ export class ApiServerService { const instances = await this.instancesRepository.find({ select: ['host'], where: { - isSuspended: false, + suspensionState: 'none', }, }); diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts index 0bcdc2a4b8..fed7bfbbde 100644 --- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts @@ -46,12 +46,19 @@ export default class extends Endpoint { // eslint- throw new Error('instance not found'); } + const isSuspendedBefore = instance.suspensionState !== 'none'; + let suspensionState: undefined | 'manuallySuspended' | 'none'; + + if (ps.isSuspended != null && isSuspendedBefore !== ps.isSuspended) { + suspensionState = ps.isSuspended ? 'manuallySuspended' : 'none'; + } + await this.federatedInstanceService.update(instance.id, { - isSuspended: ps.isSuspended, + suspensionState, moderationNote: ps.moderationNote, }); - if (ps.isSuspended != null && instance.isSuspended !== ps.isSuspended) { + if (ps.isSuspended != null && isSuspendedBefore !== ps.isSuspended) { if (ps.isSuspended) { this.moderationLogService.log(me, 'suspendRemoteInstance', { id: instance.id, diff --git a/packages/frontend/src/pages/admin/federation.vue b/packages/frontend/src/pages/admin/federation.vue index de27e1f67a..0aaa398584 100644 --- a/packages/frontend/src/pages/admin/federation.vue +++ b/packages/frontend/src/pages/admin/federation.vue @@ -58,6 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only + + diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue index bcd6eb7c0f..e50b208775 100644 --- a/packages/frontend/src/pages/announcements.vue +++ b/packages/frontend/src/pages/announcements.vue @@ -21,14 +21,19 @@ SPDX-License-Identifier: AGPL-3.0-only - {{ announcement.title }} + {{ announcement.title }}
-
- -
+ +
+ {{ i18n.ts.createdAt }}: +
+
+ {{ i18n.ts.updatedAt }}: +
+
{{ i18n.ts.gotIt }} @@ -73,24 +78,24 @@ const paginationEl = ref>(); const tab = ref('current'); -async function read(announcement) { - if (announcement.needConfirmationToRead) { +async function read(target) { + if (target.needConfirmationToRead) { const confirm = await os.confirm({ type: 'question', title: i18n.ts._announcement.readConfirmTitle, - text: i18n.tsx._announcement.readConfirmText({ title: announcement.title }), + text: i18n.tsx._announcement.readConfirmText({ title: target.title }), }); if (confirm.canceled) return; } if (!paginationEl.value) return; - paginationEl.value.updateItem(announcement.id, a => { + paginationEl.value.updateItem(target.id, a => { a.isRead = true; return a; }); - misskeyApi('i/read-announcement', { announcementId: announcement.id }); + misskeyApi('i/read-announcement', { announcementId: target.id }); updateAccount({ - unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== announcement.id), + unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== target.id), }); } diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts index c5b576f505..c12ae0fa57 100644 --- a/packages/frontend/src/router/definition.ts +++ b/packages/frontend/src/router/definition.ts @@ -193,6 +193,9 @@ const routes: RouteDef[] = [{ }, { path: '/announcements', component: page(() => import('@/pages/announcements.vue')), +}, { + path: '/announcements/:announcementId', + component: page(() => import('@/pages/announcement.vue')), }, { path: '/about', component: page(() => import('@/pages/about.vue')), diff --git a/packages/frontend/src/ui/_common_/announcements.vue b/packages/frontend/src/ui/_common_/announcements.vue index 362c29e6c2..374bc20b54 100644 --- a/packages/frontend/src/ui/_common_/announcements.vue +++ b/packages/frontend/src/ui/_common_/announcements.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only v-for="announcement in $i.unreadAnnouncements.filter(x => x.display === 'banner')" :key="announcement.id" :class="$style.item" - to="/announcements" + :to="`/announcements/${announcement.id}`" > diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 9cdb61da87..6ff711cabb 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -336,6 +336,12 @@ type AnnouncementsRequest = operations['announcements']['requestBody']['content' // @public (undocumented) type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json']; +// @public (undocumented) +type AnnouncementsShowRequest = operations['announcements___show']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AnnouncementsShowResponse = operations['announcements___show']['responses']['200']['content']['application/json']; + // @public (undocumented) type Antenna = components['schemas']['Antenna']; @@ -1224,6 +1230,8 @@ declare namespace entities { AdminRolesUsersResponse, AnnouncementsRequest, AnnouncementsResponse, + AnnouncementsShowRequest, + AnnouncementsShowResponse, AntennasCreateRequest, AntennasCreateResponse, AntennasDeleteRequest, diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 729107a78d..181f7274b7 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -851,6 +851,17 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *No* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index b0982e1e55..ab3baf1670 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -101,6 +101,8 @@ import type { AdminRolesUsersResponse, AnnouncementsRequest, AnnouncementsResponse, + AnnouncementsShowRequest, + AnnouncementsShowResponse, AntennasCreateRequest, AntennasCreateResponse, AntennasDeleteRequest, @@ -631,6 +633,7 @@ export type Endpoints = { 'admin/roles/update-default-policies': { req: AdminRolesUpdateDefaultPoliciesRequest; res: EmptyResponse }; 'admin/roles/users': { req: AdminRolesUsersRequest; res: AdminRolesUsersResponse }; 'announcements': { req: AnnouncementsRequest; res: AnnouncementsResponse }; + 'announcements/show': { req: AnnouncementsShowRequest; res: AnnouncementsShowResponse }; 'antennas/create': { req: AntennasCreateRequest; res: AntennasCreateResponse }; 'antennas/delete': { req: AntennasDeleteRequest; res: EmptyResponse }; 'antennas/list': { req: EmptyRequest; res: AntennasListResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 60bf6659c0..02ca932d8a 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -104,6 +104,8 @@ export type AdminRolesUsersRequest = operations['admin___roles___users']['reques export type AdminRolesUsersResponse = operations['admin___roles___users']['responses']['200']['content']['application/json']; export type AnnouncementsRequest = operations['announcements']['requestBody']['content']['application/json']; export type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json']; +export type AnnouncementsShowRequest = operations['announcements___show']['requestBody']['content']['application/json']; +export type AnnouncementsShowResponse = operations['announcements___show']['responses']['200']['content']['application/json']; export type AntennasCreateRequest = operations['antennas___create']['requestBody']['content']['application/json']; export type AntennasCreateResponse = operations['antennas___create']['responses']['200']['content']['application/json']; export type AntennasDeleteRequest = operations['antennas___delete']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 1e9c190ca5..208f03dc3e 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -706,6 +706,15 @@ export type paths = { */ post: operations['announcements']; }; + '/announcements/show': { + /** + * announcements/show + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['announcements___show']; + }; '/antennas/create': { /** * antennas/create @@ -9662,6 +9671,60 @@ export type operations = { }; }; }; + /** + * announcements/show + * @description No description provided. + * + * **Credential required**: *No* + */ + announcements___show: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + announcementId: string; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Announcement']; + }; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; /** * antennas/create * @description No description provided. From 1df8ea824e5dace883f0d6855d7342984c8032d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 27 May 2024 17:15:42 +0900 Subject: [PATCH 5/9] =?UTF-8?q?fix(backend):=20`/@`=20=E3=81=AB=E3=82=A2?= =?UTF-8?q?=E3=82=AF=E3=82=BB=E3=82=B9=E3=81=99=E3=82=8B=E3=81=A8=E3=82=B5?= =?UTF-8?q?=E3=83=BC=E3=83=90=E3=83=BC=E3=82=A8=E3=83=A9=E3=83=BC=E3=81=8C?= =?UTF-8?q?=E7=99=BA=E7=94=9F=E3=81=99=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20(#13884)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/src/server/web/ClientServerService.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index f35ec8ba31..ab03489c0d 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -466,7 +466,9 @@ export class ClientServerService { }; // Atom - fastify.get<{ Params: { user: string; } }>('/@:user.atom', async (request, reply) => { + fastify.get<{ Params: { user?: string; } }>('/@:user.atom', async (request, reply) => { + if (request.params.user == null) return await renderBase(reply); + const feed = await getFeed(request.params.user); if (feed) { @@ -479,7 +481,9 @@ export class ClientServerService { }); // RSS - fastify.get<{ Params: { user: string; } }>('/@:user.rss', async (request, reply) => { + fastify.get<{ Params: { user?: string; } }>('/@:user.rss', async (request, reply) => { + if (request.params.user == null) return await renderBase(reply); + const feed = await getFeed(request.params.user); if (feed) { @@ -492,7 +496,9 @@ export class ClientServerService { }); // JSON - fastify.get<{ Params: { user: string; } }>('/@:user.json', async (request, reply) => { + fastify.get<{ Params: { user?: string; } }>('/@:user.json', async (request, reply) => { + if (request.params.user == null) return await renderBase(reply); + const feed = await getFeed(request.params.user); if (feed) { From 1b81ca45636db21166753e0aa00d91ab23e46ac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 27 May 2024 17:16:47 +0900 Subject: [PATCH 6/9] =?UTF-8?q?enhance(frontend):=20=E3=80=8C=E8=A6=8B?= =?UTF-8?q?=E3=81=9F=E3=81=93=E3=81=A8=E3=81=AE=E3=81=82=E3=82=8B=E3=83=AA?= =?UTF-8?q?=E3=83=8E=E3=83=BC=E3=83=88=E3=82=92=E7=9C=81=E7=95=A5=E3=81=97?= =?UTF-8?q?=E3=81=A6=E8=A1=A8=E7=A4=BA=E3=80=8D=E3=81=AE=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E3=82=92=E5=A4=89=E6=9B=B4=20(#13883)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(frontend): 「見たことのあるリノートを省略して表示」の名称を変更 * ひとつだけcaptionが入ってるやつが真ん中にいると不格好だったので場所変更 --- locales/index.d.ts | 6 +++++- locales/ja-JP.yml | 3 ++- packages/frontend/src/pages/settings/general.vue | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index 18d8eee18f..eb7e297aa3 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -4113,9 +4113,13 @@ export interface Locale extends ILocale { */ "thisPostMayBeAnnoyingIgnore": string; /** - * 見たことのあるリノートを省略して表示 + * リノートのスマート省略 */ "collapseRenotes": string; + /** + * リアクションやリノートをしたことがあるノートをたたんで表示します。 + */ + "collapseRenotesDescription": string; /** * サーバー内部エラー */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 8b1738aebe..ebaf16745c 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1024,7 +1024,8 @@ thisPostMayBeAnnoying: "この投稿は迷惑になる可能性があります thisPostMayBeAnnoyingHome: "ホームに投稿" thisPostMayBeAnnoyingCancel: "やめる" thisPostMayBeAnnoyingIgnore: "このまま投稿" -collapseRenotes: "見たことのあるリノートを省略して表示" +collapseRenotes: "リノートのスマート省略" +collapseRenotesDescription: "リアクションやリノートをしたことがあるノートをたたんで表示します。" internalServerError: "サーバー内部エラー" internalServerErrorDescription: "サーバー内部で予期しないエラーが発生しました。" copyErrorInfo: "エラー情報をコピー" diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index 55d514ddf9..cfc63f2a08 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -50,9 +50,12 @@ SPDX-License-Identifier: AGPL-3.0-only
+ + + + {{ i18n.ts.showNoteActionsOnlyHover }} {{ i18n.ts.showClipButtonInNoteFooter }} - {{ i18n.ts.collapseRenotes }} {{ i18n.ts.enableAdvancedMfm }} {{ i18n.ts.enableAnimatedMfm }} {{ i18n.ts.enableQuickAddMfmFunction }} From 805a11aadbbc0f0a32531fd86443de514df74466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 27 May 2024 17:18:12 +0900 Subject: [PATCH 7/9] =?UTF-8?q?enhance(backend):=20=E3=83=97=E3=83=AD?= =?UTF-8?q?=E3=83=95=E3=82=A3=E3=83=BC=E3=83=AB=E3=81=AE=E3=83=AA=E3=83=B3?= =?UTF-8?q?=E3=82=AF=E6=A4=9C=E8=A8=BC=E3=81=ABtry-catch=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=20(#13882)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(backend): プロフィールのリンク検証にtry-catchを追加 * :v: --- .../src/server/api/endpoints/i/update.ts | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 84a1931a3d..a8e702f328 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -498,26 +498,32 @@ export default class extends Endpoint { // eslint- private async verifyLink(url: string, user: MiLocalUser) { if (!safeForSql(url)) return; - const html = await this.httpRequestService.getHtml(url); + try { + const html = await this.httpRequestService.getHtml(url); - const { window } = new JSDOM(html); - const doc = window.document; + const { window } = new JSDOM(html); + const doc = window.document; - const myLink = `${this.config.url}/@${user.username}`; + const myLink = `${this.config.url}/@${user.username}`; - const aEls = Array.from(doc.getElementsByTagName('a')); - const linkEls = Array.from(doc.getElementsByTagName('link')); + const aEls = Array.from(doc.getElementsByTagName('a')); + const linkEls = Array.from(doc.getElementsByTagName('link')); - const includesMyLink = aEls.some(a => a.href === myLink); - const includesRelMeLinks = [...aEls, ...linkEls].some(link => link.rel === 'me' && link.href === myLink); + const includesMyLink = aEls.some(a => a.href === myLink); + const includesRelMeLinks = [...aEls, ...linkEls].some(link => link.rel === 'me' && link.href === myLink); - if (includesMyLink || includesRelMeLinks) { - await this.userProfilesRepository.createQueryBuilder('profile').update() - .where('userId = :userId', { userId: user.id }) - .set({ - verifiedLinks: () => `array_append("verifiedLinks", '${url}')`, // ここでSQLインジェクションされそうなのでとりあえず safeForSql で弾いている - }) - .execute(); + if (includesMyLink || includesRelMeLinks) { + await this.userProfilesRepository.createQueryBuilder('profile').update() + .where('userId = :userId', { userId: user.id }) + .set({ + verifiedLinks: () => `array_append("verifiedLinks", '${url}')`, // ここでSQLインジェクションされそうなのでとりあえず safeForSql で弾いている + }) + .execute(); + } + + window.close(); + } catch (err) { + // なにもしない } } } From d013e4516d7afb6ed4362467f69df2d79b9f0f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 27 May 2024 17:19:09 +0900 Subject: [PATCH 8/9] =?UTF-8?q?enhance(frontend):=20=E3=81=8A=E6=B0=97?= =?UTF-8?q?=E3=81=AB=E5=85=A5=E3=82=8A=E3=83=81=E3=83=A3=E3=83=B3=E3=83=8D?= =?UTF-8?q?=E3=83=AB=E3=82=92=E3=82=AD=E3=83=A3=E3=83=83=E3=82=B7=E3=83=A5?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#13881)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/cache.ts | 1 + packages/frontend/src/pages/channel.vue | 3 +++ packages/frontend/src/pages/timeline.vue | 6 ++---- packages/frontend/src/scripts/get-note-menu.ts | 6 ++---- packages/frontend/src/ui/deck/channel-column.vue | 13 ++++++------- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/frontend/src/cache.ts b/packages/frontend/src/cache.ts index b286528de6..bfe8fbe0e4 100644 --- a/packages/frontend/src/cache.ts +++ b/packages/frontend/src/cache.ts @@ -11,3 +11,4 @@ export const clipsCache = new Cache(1000 * 60 * 30, () export const rolesCache = new Cache(1000 * 60 * 30, () => misskeyApi('admin/roles/list')); export const userListsCache = new Cache(1000 * 60 * 30, () => misskeyApi('users/lists/list')); export const antennasCache = new Cache(1000 * 60 * 30, () => misskeyApi('antennas/list')); +export const favoritedChannelsCache = new Cache(1000 * 60 * 30, () => misskeyApi('channels/my-favorites', { limit: 100 })); diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 611ae6feca..a895df76e8 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -83,6 +83,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js'; import { deviceKind } from '@/scripts/device-kind.js'; import MkNotes from '@/components/MkNotes.vue'; import { url } from '@/config.js'; +import { favoritedChannelsCache } from '@/cache.js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import { defaultStore } from '@/store.js'; @@ -153,6 +154,7 @@ function favorite() { channelId: channel.value.id, }).then(() => { favorited.value = true; + favoritedChannelsCache.delete(); }); } @@ -168,6 +170,7 @@ async function unfavorite() { channelId: channel.value.id, }).then(() => { favorited.value = false; + favoritedChannelsCache.delete(); }); } diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 48dfc1fd44..98744c6318 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -48,7 +48,7 @@ import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; import { $i } from '@/account.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; -import { antennasCache, userListsCache } from '@/cache.js'; +import { antennasCache, userListsCache, favoritedChannelsCache } from '@/cache.js'; import { deviceKind } from '@/scripts/device-kind.js'; import { deepMerge } from '@/scripts/merge.js'; import { MenuItem } from '@/types/menu.js'; @@ -173,9 +173,7 @@ async function chooseAntenna(ev: MouseEvent): Promise { } async function chooseChannel(ev: MouseEvent): Promise { - const channels = await misskeyApi('channels/my-favorites', { - limit: 100, - }); + const channels = await favoritedChannelsCache.fetch(); const items: MenuItem[] = [ ...channels.map(channel => { const lastReadedAt = miLocalStorage.getItemAsJson(`channelLastReadedAt:${channel.id}`) ?? null; diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index e7c9a848e0..71ad299f50 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -16,7 +16,7 @@ import { url } from '@/config.js'; import { defaultStore, noteActions } from '@/store.js'; import { miLocalStorage } from '@/local-storage.js'; import { getUserMenu } from '@/scripts/get-user-menu.js'; -import { clipsCache } from '@/cache.js'; +import { clipsCache, favoritedChannelsCache } from '@/cache.js'; import { MenuItem } from '@/types/menu.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { isSupportShare } from '@/scripts/navigator.js'; @@ -603,9 +603,7 @@ export function getRenoteMenu(props: { icon: 'ti ti-repeat', text: appearNote.channel ? i18n.ts.renoteToOtherChannel : i18n.ts.renoteToChannel, children: async () => { - const channels = await misskeyApi('channels/my-favorites', { - limit: 30, - }); + const channels = await favoritedChannelsCache.fetch(); return channels.filter((channel) => { if (!appearNote.channelId) return true; return channel.id !== appearNote.channelId; diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue index bd3b059497..28c741bba2 100644 --- a/packages/frontend/src/ui/deck/channel-column.vue +++ b/packages/frontend/src/ui/deck/channel-column.vue @@ -26,6 +26,7 @@ import { updateColumn, Column } from './deck-store.js'; import MkTimeline from '@/components/MkTimeline.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; +import { favoritedChannelsCache } from '@/cache.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; @@ -42,20 +43,18 @@ if (props.column.channelId == null) { } async function setChannel() { - const channels = await misskeyApi('channels/my-favorites', { - limit: 100, - }); - const { canceled, result: channel } = await os.select({ + const channels = await favoritedChannelsCache.fetch(); + const { canceled, result: chosenChannel } = await os.select({ title: i18n.ts.selectChannel, items: channels.map(x => ({ value: x, text: x.name, })), default: props.column.channelId, }); - if (canceled) return; + if (canceled || chosenChannel == null) return; updateColumn(props.column.id, { - channelId: channel.id, - name: channel.name, + channelId: chosenChannel.id, + name: chosenChannel.name, }); } From 6af9492ea5492c02a11302afe7c6a6e83c00de1b Mon Sep 17 00:00:00 2001 From: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Mon, 27 May 2024 17:21:05 +0900 Subject: [PATCH 9/9] Quick action implement (#13878) * enhance(frontend): quick action for file admin-lookup * docs(changelog): update changelog * enhance(frontend): quick action for general admin-lookup, remove unimplemented note, instance admin-lookup * docs(changelog): update changelog * chore: fix lint --- CHANGELOG.md | 2 ++ packages/frontend/src/pages/admin/files.vue | 27 ++----------------- packages/frontend/src/pages/admin/index.vue | 21 ++++++--------- packages/frontend/src/pages/admin/users.vue | 2 +- .../{lookup-user.ts => admin-lookup.ts} | 23 ++++++++++++++++ 5 files changed, 36 insertions(+), 39 deletions(-) rename packages/frontend/src/scripts/{lookup-user.ts => admin-lookup.ts} (72%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e23fa8f72..d23f512e3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ - Enhance: AiScriptを0.18.0にバージョンアップ - Enhance: 通常のノートでも、お気に入りに登録したチャンネルにリノートできるように - Enhance: 長いテキストをペーストした際にテキストファイルとして添付するかどうかを選択できるように +- Enhance: コントロールパネルのクイックアクションからファイルを照会できるように +- Enhance: コントロールパネルのクイックアクションから通常の照会を行えるように - Fix: 一部のページ内リンクが正しく動作しない問題を修正 - Fix: 周年の実績が閏年を考慮しない問題を修正 - Fix: ローカルURLのプレビューポップアップが左上に表示される diff --git a/packages/frontend/src/pages/admin/files.vue b/packages/frontend/src/pages/admin/files.vue index 3fe021e771..5132b85c64 100644 --- a/packages/frontend/src/pages/admin/files.vue +++ b/packages/frontend/src/pages/admin/files.vue @@ -42,7 +42,7 @@ import MkInput from '@/components/MkInput.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue'; import * as os from '@/os.js'; -import { misskeyApi } from '@/scripts/misskey-api.js'; +import { lookupFile } from '@/scripts/admin-lookup.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; @@ -73,33 +73,10 @@ function clear() { }); } -function show(file) { - os.pageWindow(`/admin/file/${file.id}`); -} - -async function find() { - const { canceled, result: q } = await os.inputText({ - title: i18n.ts.fileIdOrUrl, - minLength: 1, - }); - if (canceled) return; - - misskeyApi('admin/drive/show-file', q.startsWith('http://') || q.startsWith('https://') ? { url: q.trim() } : { fileId: q.trim() }).then(file => { - show(file); - }).catch(err => { - if (err.code === 'NO_SUCH_FILE') { - os.alert({ - type: 'error', - text: i18n.ts.notFound, - }); - } - }); -} - const headerActions = computed(() => [{ text: i18n.ts.lookup, icon: 'ti ti-search', - handler: find, + handler: lookupFile, }, { text: i18n.ts.clearCachedFiles, icon: 'ti ti-trash', diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index d4a41c66cc..eef1c8afa9 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -33,9 +33,10 @@ import { i18n } from '@/i18n.js'; import MkSuperMenu from '@/components/MkSuperMenu.vue'; import MkInfo from '@/components/MkInfo.vue'; import { instance } from '@/instance.js'; +import { lookup } from '@/scripts/lookup.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { lookupUser, lookupUserByEmail } from '@/scripts/lookup-user.js'; +import { lookupUser, lookupUserByEmail, lookupFile } from '@/scripts/admin-lookup.js'; import { PageMetadata, definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js'; import { useRouter } from '@/router/supplier.js'; @@ -82,7 +83,7 @@ const menuDef = computed(() => [{ type: 'button', icon: 'ti ti-search', text: i18n.ts.lookup, - action: lookup, + action: adminLookup, }, ...(instance.disableRegistration ? [{ type: 'button', icon: 'ti ti-user-plus', @@ -282,7 +283,7 @@ function invite() { }); } -function lookup(ev: MouseEvent) { +function adminLookup(ev: MouseEvent) { os.popupMenu([{ text: i18n.ts.user, icon: 'ti ti-user', @@ -295,23 +296,17 @@ function lookup(ev: MouseEvent) { action: () => { lookupUserByEmail(); }, - }, { - text: i18n.ts.note, - icon: 'ti ti-pencil', - action: () => { - alert('TODO'); - }, }, { text: i18n.ts.file, icon: 'ti ti-cloud', action: () => { - alert('TODO'); + lookupFile(); }, }, { - text: i18n.ts.instance, - icon: 'ti ti-planet', + text: i18n.ts.lookup, + icon: 'ti ti-world-search', action: () => { - alert('TODO'); + lookup(); }, }], ev.currentTarget ?? ev.target); } diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue index 06317760d2..7d87b97a36 100644 --- a/packages/frontend/src/pages/admin/users.vue +++ b/packages/frontend/src/pages/admin/users.vue @@ -63,7 +63,7 @@ import MkInput from '@/components/MkInput.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkPagination from '@/components/MkPagination.vue'; import * as os from '@/os.js'; -import { lookupUser } from '@/scripts/lookup-user.js'; +import { lookupUser } from '@/scripts/admin-lookup.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; diff --git a/packages/frontend/src/scripts/lookup-user.ts b/packages/frontend/src/scripts/admin-lookup.ts similarity index 72% rename from packages/frontend/src/scripts/lookup-user.ts rename to packages/frontend/src/scripts/admin-lookup.ts index efc9132e75..1b57b853c9 100644 --- a/packages/frontend/src/scripts/lookup-user.ts +++ b/packages/frontend/src/scripts/admin-lookup.ts @@ -63,3 +63,26 @@ export async function lookupUserByEmail() { } } } + +export async function lookupFile() { + const { canceled, result: q } = await os.inputText({ + title: i18n.ts.fileIdOrUrl, + minLength: 1, + }); + if (canceled) return; + + const show = (file) => { + os.pageWindow(`/admin/file/${file.id}`); + }; + + misskeyApi('admin/drive/show-file', q.startsWith('http://') || q.startsWith('https://') ? { url: q.trim() } : { fileId: q.trim() }).then(file => { + show(file); + }).catch(err => { + if (err.code === 'NO_SUCH_FILE') { + os.alert({ + type: 'error', + text: i18n.ts.notFound, + }); + } + }); +}