Block deliver by software (#15727)
* feat(backend): suspend instance by software * feat(frontend): suspend instance by software * docs(chaangelog): 連合先のソフトウェア及びバージョン名により配信停止を行えるようになりました * chore: 例で使うバージョン名を変える * fix: broken lockfile * fix: broken lock file * fix broken lock file * update changelog * fix dependencies * Update CHANGELOG.md --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
This commit is contained in:
parent
2fcb50273d
commit
795b8366b5
|
@ -8,6 +8,7 @@
|
||||||
|
|
||||||
### Server
|
### Server
|
||||||
- Enhance: 凍結されたユーザのノートが各種タイムラインで表示されないように `#15775`
|
- Enhance: 凍結されたユーザのノートが各種タイムラインで表示されないように `#15775`
|
||||||
|
- Enhance: 連合先のソフトウェア及びバージョン名により配信停止を行えるように `#15727`
|
||||||
|
|
||||||
## 2025.4.1
|
## 2025.4.1
|
||||||
|
|
||||||
|
|
|
@ -898,6 +898,10 @@ export interface Locale extends ILocale {
|
||||||
* ソフトウェア
|
* ソフトウェア
|
||||||
*/
|
*/
|
||||||
"software": string;
|
"software": string;
|
||||||
|
/**
|
||||||
|
* ソフトウェア名
|
||||||
|
*/
|
||||||
|
"softwareName": string;
|
||||||
/**
|
/**
|
||||||
* バージョン
|
* バージョン
|
||||||
*/
|
*/
|
||||||
|
@ -5871,6 +5875,10 @@ export interface Locale extends ILocale {
|
||||||
* サーバー応答なしのため停止中
|
* サーバー応答なしのため停止中
|
||||||
*/
|
*/
|
||||||
"autoSuspendedForNotResponding": string;
|
"autoSuspendedForNotResponding": string;
|
||||||
|
/**
|
||||||
|
* 配信停止中のソフトウェアであるため停止中
|
||||||
|
*/
|
||||||
|
"softwareSuspended": string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
"_bubbleGame": {
|
"_bubbleGame": {
|
||||||
|
@ -6356,6 +6364,14 @@ export interface Locale extends ILocale {
|
||||||
* 一定期間モデレーターのアクティビティが検出されなかった場合、スパム防止のためこの設定は自動でオフになります。
|
* 一定期間モデレーターのアクティビティが検出されなかった場合、スパム防止のためこの設定は自動でオフになります。
|
||||||
*/
|
*/
|
||||||
"thisSettingWillAutomaticallyOffWhenModeratorsInactive": string;
|
"thisSettingWillAutomaticallyOffWhenModeratorsInactive": string;
|
||||||
|
/**
|
||||||
|
* 配信停止中のソフトウェア
|
||||||
|
*/
|
||||||
|
"deliverSuspendedSoftware": string;
|
||||||
|
/**
|
||||||
|
* 脆弱性などの理由で、サーバーのソフトウェアの名前及びバージョンの範囲を指定して配信を停止できます。このバージョン情報はサーバーが提供したものであり、信頼性は保証されません。バージョン指定には semver の範囲指定が使用できますが、>= 2024.3.1 と指定すると 2024.3.1-custom.0 のようなカスタムバージョンが含まれないため、>= 2024.3.1-0 のように prerelease の指定を行うことを推奨します。
|
||||||
|
*/
|
||||||
|
"deliverSuspendedSoftwareDescription": string;
|
||||||
};
|
};
|
||||||
"_accountMigration": {
|
"_accountMigration": {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -220,6 +220,7 @@ silenceThisInstance: "サーバーをサイレンス"
|
||||||
mediaSilenceThisInstance: "サーバーをメディアサイレンス"
|
mediaSilenceThisInstance: "サーバーをメディアサイレンス"
|
||||||
operations: "操作"
|
operations: "操作"
|
||||||
software: "ソフトウェア"
|
software: "ソフトウェア"
|
||||||
|
softwareName: "ソフトウェア名"
|
||||||
version: "バージョン"
|
version: "バージョン"
|
||||||
metadata: "メタデータ"
|
metadata: "メタデータ"
|
||||||
withNFiles: "{n}つのファイル"
|
withNFiles: "{n}つのファイル"
|
||||||
|
@ -1477,6 +1478,7 @@ _delivery:
|
||||||
manuallySuspended: "手動停止中"
|
manuallySuspended: "手動停止中"
|
||||||
goneSuspended: "サーバー削除のため停止中"
|
goneSuspended: "サーバー削除のため停止中"
|
||||||
autoSuspendedForNotResponding: "サーバー応答なしのため停止中"
|
autoSuspendedForNotResponding: "サーバー応答なしのため停止中"
|
||||||
|
softwareSuspended: "配信停止中のソフトウェアであるため停止中"
|
||||||
|
|
||||||
_bubbleGame:
|
_bubbleGame:
|
||||||
howToPlay: "遊び方"
|
howToPlay: "遊び方"
|
||||||
|
@ -1615,6 +1617,8 @@ _serverSettings:
|
||||||
openRegistration: "アカウントの作成をオープンにする"
|
openRegistration: "アカウントの作成をオープンにする"
|
||||||
openRegistrationWarning: "登録を開放することはリスクが伴います。サーバーを常に監視し、トラブルが発生した際にすぐに対応できる体制がある場合のみオンにすることを推奨します。"
|
openRegistrationWarning: "登録を開放することはリスクが伴います。サーバーを常に監視し、トラブルが発生した際にすぐに対応できる体制がある場合のみオンにすることを推奨します。"
|
||||||
thisSettingWillAutomaticallyOffWhenModeratorsInactive: "一定期間モデレーターのアクティビティが検出されなかった場合、スパム防止のためこの設定は自動でオフになります。"
|
thisSettingWillAutomaticallyOffWhenModeratorsInactive: "一定期間モデレーターのアクティビティが検出されなかった場合、スパム防止のためこの設定は自動でオフになります。"
|
||||||
|
deliverSuspendedSoftware: "配信停止中のソフトウェア"
|
||||||
|
deliverSuspendedSoftwareDescription: "脆弱性などの理由で、サーバーのソフトウェアの名前及びバージョンの範囲を指定して配信を停止できます。このバージョン情報はサーバーが提供したものであり、信頼性は保証されません。バージョン指定には semver の範囲指定が使用できますが、>= 2024.3.1 と指定すると 2024.3.1-custom.0 のようなカスタムバージョンが含まれないため、>= 2024.3.1-0 のように prerelease の指定を行うことを推奨します。"
|
||||||
|
|
||||||
_accountMigration:
|
_accountMigration:
|
||||||
moveFrom: "別のアカウントからこのアカウントに移行"
|
moveFrom: "別のアカウントからこのアカウントに移行"
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class DeliverSuspendedSoftware1743403874305 {
|
||||||
|
name = 'DeliverSuspendedSoftware1743403874305'
|
||||||
|
|
||||||
|
async up(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" ADD "deliverSuspendedSoftware" jsonb NOT NULL DEFAULT '[]'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async down(queryRunner) {
|
||||||
|
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "deliverSuspendedSoftware"`);
|
||||||
|
}
|
||||||
|
}
|
|
@ -169,6 +169,7 @@
|
||||||
"sanitize-html": "2.16.0",
|
"sanitize-html": "2.16.0",
|
||||||
"secure-json-parse": "3.0.2",
|
"secure-json-parse": "3.0.2",
|
||||||
"sharp": "0.34.1",
|
"sharp": "0.34.1",
|
||||||
|
"semver": "7.7.1",
|
||||||
"slacc": "0.0.10",
|
"slacc": "0.0.10",
|
||||||
"strict-event-emitter-types": "2.0.0",
|
"strict-event-emitter-types": "2.0.0",
|
||||||
"stringz": "2.1.0",
|
"stringz": "2.1.0",
|
||||||
|
|
|
@ -6,10 +6,12 @@
|
||||||
import { URL, domainToASCII } from 'node:url';
|
import { URL, domainToASCII } from 'node:url';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import RE2 from 're2';
|
import RE2 from 're2';
|
||||||
|
import semver from 'semver';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { Config } from '@/config.js';
|
import type { Config } from '@/config.js';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { MiMeta } from '@/models/Meta.js';
|
import { MiMeta, SoftwareSuspension } from '@/models/Meta.js';
|
||||||
|
import { MiInstance } from '@/models/Instance.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UtilityService {
|
export class UtilityService {
|
||||||
|
@ -143,4 +145,20 @@ export class UtilityService {
|
||||||
const host = this.extractDbHost(uri);
|
const host = this.extractDbHost(uri);
|
||||||
return this.isFederationAllowedHost(host);
|
return this.isFederationAllowedHost(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@bindThis
|
||||||
|
public isDeliverSuspendedSoftware(software: Pick<MiInstance, 'softwareName' | 'softwareVersion'>): SoftwareSuspension | undefined {
|
||||||
|
if (software.softwareName == null) return undefined;
|
||||||
|
if (software.softwareVersion == null) {
|
||||||
|
// software version is null; suspend iff versionRange is *
|
||||||
|
return this.meta.deliverSuspendedSoftware.find(x =>
|
||||||
|
x.software === software.softwareName
|
||||||
|
&& x.versionRange.trim() === '*');
|
||||||
|
} else {
|
||||||
|
const softwareVersion = software.softwareVersion;
|
||||||
|
return this.meta.deliverSuspendedSoftware.find(x =>
|
||||||
|
x.software === software.softwareName
|
||||||
|
&& semver.satisfies(softwareVersion, x.versionRange, { includePrerelease: true }));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ export class InstanceEntityService {
|
||||||
me?: { id: MiUser['id']; } | null | undefined,
|
me?: { id: MiUser['id']; } | null | undefined,
|
||||||
): Promise<Packed<'FederationInstance'>> {
|
): Promise<Packed<'FederationInstance'>> {
|
||||||
const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;
|
const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;
|
||||||
|
const softwareSuspended = this.utilityService.isDeliverSuspendedSoftware(instance);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: instance.id,
|
id: instance.id,
|
||||||
|
@ -41,8 +42,8 @@ export class InstanceEntityService {
|
||||||
followingCount: instance.followingCount,
|
followingCount: instance.followingCount,
|
||||||
followersCount: instance.followersCount,
|
followersCount: instance.followersCount,
|
||||||
isNotResponding: instance.isNotResponding,
|
isNotResponding: instance.isNotResponding,
|
||||||
isSuspended: instance.suspensionState !== 'none',
|
isSuspended: instance.suspensionState !== 'none' || Boolean(softwareSuspended),
|
||||||
suspensionState: instance.suspensionState,
|
suspensionState: instance.suspensionState === 'none' && softwareSuspended ? 'softwareSuspended' : instance.suspensionState,
|
||||||
isBlocked: this.utilityService.isBlockedHost(this.meta.blockedHosts, instance.host),
|
isBlocked: this.utilityService.isBlockedHost(this.meta.blockedHosts, instance.host),
|
||||||
softwareName: instance.softwareName,
|
softwareName: instance.softwareName,
|
||||||
softwareVersion: instance.softwareVersion,
|
softwareVersion: instance.softwareVersion,
|
||||||
|
|
|
@ -664,4 +664,14 @@ export class MiMeta {
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
public googleAnalyticsMeasurementId: string | null;
|
public googleAnalyticsMeasurementId: string | null;
|
||||||
|
|
||||||
|
@Column('jsonb', {
|
||||||
|
default: [],
|
||||||
|
})
|
||||||
|
public deliverSuspendedSoftware: SoftwareSuspension[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type SoftwareSuspension = {
|
||||||
|
software: string,
|
||||||
|
versionRange: string,
|
||||||
|
};
|
||||||
|
|
|
@ -48,7 +48,7 @@ export const packedFederationInstanceSchema = {
|
||||||
suspensionState: {
|
suspensionState: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
nullable: false, optional: false,
|
nullable: false, optional: false,
|
||||||
enum: ['none', 'manuallySuspended', 'goneSuspended', 'autoSuspendedForNotResponding'],
|
enum: ['none', 'manuallySuspended', 'goneSuspended', 'autoSuspendedForNotResponding', 'softwareSuspended'],
|
||||||
},
|
},
|
||||||
isBlocked: {
|
isBlocked: {
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
|
|
@ -71,6 +71,15 @@ export class DeliverProcessorService {
|
||||||
return 'skip (suspended)';
|
return 'skip (suspended)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const i = await (this.meta.enableStatsForFederatedInstances
|
||||||
|
? this.federatedInstanceService.fetchOrRegister(host)
|
||||||
|
: this.federatedInstanceService.fetch(host));
|
||||||
|
|
||||||
|
// suspend server by software
|
||||||
|
if (i != null && this.utilityService.isDeliverSuspendedSoftware(i)) {
|
||||||
|
return 'skip (software suspended)';
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content, job.data.digest);
|
await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content, job.data.digest);
|
||||||
|
|
||||||
|
@ -79,10 +88,6 @@ export class DeliverProcessorService {
|
||||||
|
|
||||||
// Update instance stats
|
// Update instance stats
|
||||||
process.nextTick(async () => {
|
process.nextTick(async () => {
|
||||||
const i = await (this.meta.enableStatsForFederatedInstances
|
|
||||||
? this.federatedInstanceService.fetchOrRegister(host)
|
|
||||||
: this.federatedInstanceService.fetch(host));
|
|
||||||
|
|
||||||
if (i == null) return;
|
if (i == null) return;
|
||||||
|
|
||||||
if (i.isNotResponding) {
|
if (i.isNotResponding) {
|
||||||
|
|
|
@ -528,6 +528,24 @@ export const meta = {
|
||||||
optional: false, nullable: false,
|
optional: false, nullable: false,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
deliverSuspendedSoftware: {
|
||||||
|
type: 'array',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
properties: {
|
||||||
|
software: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
versionRange: {
|
||||||
|
type: 'string',
|
||||||
|
optional: false, nullable: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -672,6 +690,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl,
|
urlPreviewSummaryProxyUrl: instance.urlPreviewSummaryProxyUrl,
|
||||||
federation: instance.federation,
|
federation: instance.federation,
|
||||||
federationHosts: instance.federationHosts,
|
federationHosts: instance.federationHosts,
|
||||||
|
deliverSuspendedSoftware: instance.deliverSuspendedSoftware,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,6 +185,17 @@ export const paramDef = {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
deliverSuspendedSoftware: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
software: { type: 'string' },
|
||||||
|
versionRange: { type: 'string' },
|
||||||
|
},
|
||||||
|
required: ['software', 'versionRange'],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
required: [],
|
required: [],
|
||||||
} as const;
|
} as const;
|
||||||
|
@ -671,6 +682,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||||
set.federation = ps.federation;
|
set.federation = ps.federation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.deliverSuspendedSoftware !== undefined) {
|
||||||
|
set.deliverSuspendedSoftware = ps.deliverSuspendedSoftware;
|
||||||
|
}
|
||||||
|
|
||||||
if (Array.isArray(ps.federationHosts)) {
|
if (Array.isArray(ps.federationHosts)) {
|
||||||
set.federationHosts = ps.federationHosts.filter(Boolean).map(x => x.toLowerCase());
|
set.federationHosts = ps.federationHosts.filter(Boolean).map(x => x.toLowerCase());
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,6 +230,31 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
<template #label>{{ i18n.ts.federationAllowedHosts }}<span v-if="federationForm.modifiedStates.federationHosts" class="_modified">{{ i18n.ts.modified }}</span></template>
|
<template #label>{{ i18n.ts.federationAllowedHosts }}<span v-if="federationForm.modifiedStates.federationHosts" class="_modified">{{ i18n.ts.modified }}</span></template>
|
||||||
<template #caption>{{ i18n.ts.federationAllowedHostsDescription }}</template>
|
<template #caption>{{ i18n.ts.federationAllowedHostsDescription }}</template>
|
||||||
</MkTextarea>
|
</MkTextarea>
|
||||||
|
|
||||||
|
<MkFolder>
|
||||||
|
<template #icon><i class="ti ti-list"></i></template>
|
||||||
|
<template #label><SearchLabel>{{ i18n.ts._serverSettings.deliverSuspendedSoftware }}</SearchLabel></template>
|
||||||
|
<template #footer>
|
||||||
|
<div class="_buttons">
|
||||||
|
<MkButton @click="federationForm.state.deliverSuspendedSoftware.push({software: '', versionRange: ''})"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div :class="$style.metadataRoot" class="_gaps_s">
|
||||||
|
<MkInfo>{{ i18n.ts._serverSettings.deliverSuspendedSoftwareDescription }}</MkInfo>
|
||||||
|
<div v-for="(element, index) in federationForm.state.deliverSuspendedSoftware" :key="index" v-panel :class="$style.fieldDragItem">
|
||||||
|
<button class="_button" :class="$style.dragItemRemove" @click="federationForm.state.deliverSuspendedSoftware.splice(index, 1)"><i class="ti ti-x"></i></button>
|
||||||
|
<div :class="$style.dragItemForm">
|
||||||
|
<FormSplit :minWidth="200">
|
||||||
|
<MkInput v-model="element.software" small :placeholder="i18n.ts.softwareName">
|
||||||
|
</MkInput>
|
||||||
|
<MkInput v-model="element.versionRange" small :placeholder="i18n.ts.version">
|
||||||
|
</MkInput>
|
||||||
|
</FormSplit>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</MkFolder>
|
||||||
</div>
|
</div>
|
||||||
</MkFolder>
|
</MkFolder>
|
||||||
|
|
||||||
|
@ -368,10 +393,12 @@ const urlPreviewForm = useForm({
|
||||||
const federationForm = useForm({
|
const federationForm = useForm({
|
||||||
federation: meta.federation,
|
federation: meta.federation,
|
||||||
federationHosts: meta.federationHosts.join('\n'),
|
federationHosts: meta.federationHosts.join('\n'),
|
||||||
|
deliverSuspendedSoftware: meta.deliverSuspendedSoftware,
|
||||||
}, async (state) => {
|
}, async (state) => {
|
||||||
await os.apiWithDialog('admin/update-meta', {
|
await os.apiWithDialog('admin/update-meta', {
|
||||||
federation: state.federation,
|
federation: state.federation,
|
||||||
federationHosts: state.federationHosts.split('\n'),
|
federationHosts: state.federationHosts.split('\n'),
|
||||||
|
deliverSuspendedSoftware: state.deliverSuspendedSoftware,
|
||||||
});
|
});
|
||||||
fetchInstance(true);
|
fetchInstance(true);
|
||||||
});
|
});
|
||||||
|
@ -398,4 +425,53 @@ definePage(() => ({
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
color: color(from var(--MI_THEME-fg) srgb r g b / 0.75);
|
color: color(from var(--MI_THEME-fg) srgb r g b / 0.75);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.metadataRoot {
|
||||||
|
container-type: inline-size;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fieldDragItem {
|
||||||
|
display: flex;
|
||||||
|
padding: 10px;
|
||||||
|
align-items: flex-end;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
/* (drag button) 32px + (drag button margin) 8px + (input width) 200px * 2 + (input gap) 12px = 452px */
|
||||||
|
@container (max-width: 452px) {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dragItemHandle {
|
||||||
|
cursor: grab;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
margin: 0 8px 0 0;
|
||||||
|
opacity: 0.5;
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dragItemRemove {
|
||||||
|
@extend .dragItemHandle;
|
||||||
|
|
||||||
|
color: #ff2a2a;
|
||||||
|
opacity: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover, &:focus {
|
||||||
|
opacity: .7;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dragItemForm {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
</MkKeyValue>
|
</MkKeyValue>
|
||||||
<MkButton v-if="suspensionState === 'none'" :disabled="!instance" danger @click="stopDelivery">{{ i18n.ts._delivery.stop }}</MkButton>
|
<MkButton v-if="suspensionState === 'none'" :disabled="!instance" danger @click="stopDelivery">{{ i18n.ts._delivery.stop }}</MkButton>
|
||||||
<MkButton v-if="suspensionState !== 'none'" :disabled="!instance" @click="resumeDelivery">{{ i18n.ts._delivery.resume }}</MkButton>
|
<MkButton v-if="suspensionState !== 'none'" :disabled="!instance || suspensionState == 'softwareSuspended'" @click="resumeDelivery">{{ i18n.ts._delivery.resume }}</MkButton>
|
||||||
<MkSwitch v-model="isBlocked" :disabled="!meta || !instance" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch>
|
<MkSwitch v-model="isBlocked" :disabled="!meta || !instance" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch>
|
||||||
<MkSwitch v-model="isSilenced" :disabled="!meta || !instance" @update:modelValue="toggleSilenced">{{ i18n.ts.silenceThisInstance }}</MkSwitch>
|
<MkSwitch v-model="isSilenced" :disabled="!meta || !instance" @update:modelValue="toggleSilenced">{{ i18n.ts.silenceThisInstance }}</MkSwitch>
|
||||||
<MkSwitch v-model="isMediaSilenced" :disabled="!meta || !instance" @update:modelValue="toggleMediaSilenced">{{ i18n.ts.mediaSilenceThisInstance }}</MkSwitch>
|
<MkSwitch v-model="isMediaSilenced" :disabled="!meta || !instance" @update:modelValue="toggleMediaSilenced">{{ i18n.ts.mediaSilenceThisInstance }}</MkSwitch>
|
||||||
|
@ -164,7 +164,7 @@ const tab = ref('overview');
|
||||||
const chartSrc = ref<ChartSrc>('instance-requests');
|
const chartSrc = ref<ChartSrc>('instance-requests');
|
||||||
const meta = ref<Misskey.entities.AdminMetaResponse | null>(null);
|
const meta = ref<Misskey.entities.AdminMetaResponse | null>(null);
|
||||||
const instance = ref<Misskey.entities.FederationInstance | null>(null);
|
const instance = ref<Misskey.entities.FederationInstance | null>(null);
|
||||||
const suspensionState = ref<'none' | 'manuallySuspended' | 'goneSuspended' | 'autoSuspendedForNotResponding'>('none');
|
const suspensionState = ref<'none' | 'manuallySuspended' | 'goneSuspended' | 'autoSuspendedForNotResponding' | 'softwareSuspended'>('none');
|
||||||
const isBlocked = ref(false);
|
const isBlocked = ref(false);
|
||||||
const isSilenced = ref(false);
|
const isSilenced = ref(false);
|
||||||
const isMediaSilenced = ref(false);
|
const isMediaSilenced = ref(false);
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
import { computed, reactive, watch } from 'vue';
|
import { computed, reactive, watch } from 'vue';
|
||||||
import type { Reactive } from 'vue';
|
import type { Reactive } from 'vue';
|
||||||
|
import { deepEqual } from '@/utility/deep-equal';
|
||||||
|
|
||||||
function copy<T>(v: T): T {
|
function copy<T>(v: T): T {
|
||||||
return JSON.parse(JSON.stringify(v));
|
return JSON.parse(JSON.stringify(v));
|
||||||
|
@ -27,7 +28,7 @@ export function useForm<T extends Record<string, any>>(initialState: T, save: (n
|
||||||
|
|
||||||
watch([currentState, previousState], () => {
|
watch([currentState, previousState], () => {
|
||||||
for (const key in modifiedStates) {
|
for (const key in modifiedStates) {
|
||||||
modifiedStates[key] = currentState[key] !== previousState[key];
|
modifiedStates[key] = !deepEqual(currentState[key], previousState[key]);
|
||||||
}
|
}
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@
|
||||||
},
|
},
|
||||||
"compileOnSave": false,
|
"compileOnSave": false,
|
||||||
"include": [
|
"include": [
|
||||||
|
"./lib/**/*.ts",
|
||||||
"./src/**/*.ts",
|
"./src/**/*.ts",
|
||||||
"./src/**/*.vue",
|
"./src/**/*.vue",
|
||||||
"./test/**/*.ts",
|
"./test/**/*.ts",
|
||||||
|
|
|
@ -4989,7 +4989,7 @@ export type components = {
|
||||||
isNotResponding: boolean;
|
isNotResponding: boolean;
|
||||||
isSuspended: boolean;
|
isSuspended: boolean;
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
suspensionState: 'none' | 'manuallySuspended' | 'goneSuspended' | 'autoSuspendedForNotResponding';
|
suspensionState: 'none' | 'manuallySuspended' | 'goneSuspended' | 'autoSuspendedForNotResponding' | 'softwareSuspended';
|
||||||
isBlocked: boolean;
|
isBlocked: boolean;
|
||||||
/** @example misskey */
|
/** @example misskey */
|
||||||
softwareName: string | null;
|
softwareName: string | null;
|
||||||
|
@ -8765,6 +8765,10 @@ export type operations = {
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
federation: 'all' | 'specified' | 'none';
|
federation: 'all' | 'specified' | 'none';
|
||||||
federationHosts: string[];
|
federationHosts: string[];
|
||||||
|
deliverSuspendedSoftware: {
|
||||||
|
software: string;
|
||||||
|
versionRange: string;
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -11431,6 +11435,10 @@ export type operations = {
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
federation?: 'all' | 'none' | 'specified';
|
federation?: 'all' | 'none' | 'specified';
|
||||||
federationHosts?: string[];
|
federationHosts?: string[];
|
||||||
|
deliverSuspendedSoftware?: {
|
||||||
|
software: string;
|
||||||
|
versionRange: string;
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -390,6 +390,9 @@ importers:
|
||||||
secure-json-parse:
|
secure-json-parse:
|
||||||
specifier: 3.0.2
|
specifier: 3.0.2
|
||||||
version: 3.0.2
|
version: 3.0.2
|
||||||
|
semver:
|
||||||
|
specifier: 7.7.1
|
||||||
|
version: 7.7.1
|
||||||
sharp:
|
sharp:
|
||||||
specifier: 0.34.1
|
specifier: 0.34.1
|
||||||
version: 0.34.1
|
version: 0.34.1
|
||||||
|
@ -9564,11 +9567,6 @@ packages:
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
semver@7.6.3:
|
|
||||||
resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
semver@7.7.1:
|
semver@7.7.1:
|
||||||
resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
|
resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
@ -17051,7 +17049,7 @@ snapshots:
|
||||||
natural-compare: 1.4.0
|
natural-compare: 1.4.0
|
||||||
nth-check: 2.1.1
|
nth-check: 2.1.1
|
||||||
postcss-selector-parser: 6.1.2
|
postcss-selector-parser: 6.1.2
|
||||||
semver: 7.6.3
|
semver: 7.7.1
|
||||||
vue-eslint-parser: 10.1.3(eslint@9.25.1)
|
vue-eslint-parser: 10.1.3(eslint@9.25.1)
|
||||||
xml-name-validator: 4.0.0
|
xml-name-validator: 4.0.0
|
||||||
|
|
||||||
|
@ -20987,8 +20985,6 @@ snapshots:
|
||||||
dependencies:
|
dependencies:
|
||||||
lru-cache: 6.0.0
|
lru-cache: 6.0.0
|
||||||
|
|
||||||
semver@7.6.3: {}
|
|
||||||
|
|
||||||
semver@7.7.1: {}
|
semver@7.7.1: {}
|
||||||
|
|
||||||
send@0.19.0:
|
send@0.19.0:
|
||||||
|
|
Loading…
Reference in New Issue