Skip to content

Commit

Permalink
管理画面の連合の調整 (#4833)
Browse files Browse the repository at this point in the history
* fed

* インスタンスのユーザーを削除
  • Loading branch information
mei23 authored Feb 24, 2024
1 parent 3aeea6f commit e7cc35e
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 156 deletions.
17 changes: 6 additions & 11 deletions locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1678,21 +1678,16 @@ admin/views/federation.vue:
system: "System"
softwareName: "Software"
softwareVersion: "Version"
following: "Following"
followers: "Followers"
caught-at: "Created at"
infoUpdatedAt: "Info updated at"
status: "Statuses"
latest-request-sent-at: "Time of last request sent"
latest-request-received-at: "Last request received at"
remove-all-following: "Withold all followers"
remove-all-following-info: "Unfollow all accounts from {host}. Please run this if the instance no longer exists."
ignore: "Ignore instance"
ignore: "Ignore instance (block)"
marked-as-closed: "Marked as closed"
flag-info: "Ignore stops sending, receiving, and fetching. Close only stops sending. The flag is automatically updated according to the communication status."
flag-info: "Ignore stops sending, receiving, and fetching. Close only stops sending. The close flag is automatically updated according to the communication status."
lookup: "Look up"
instances: "Federated"
instance-not-registered: "The instance has not been discovered"
matchBlocked: "Matching ignore in instance moderation"
matchSelfSilenced: "Matching self-silencing in instance moderation"
deleteInstanceUsers: "Delete an instance user"
deleteInstanceUsers-confirm: "Do you want to delete users in the instance (up to 1000 will be deleted)"
sort: "Sort by"
sorts:
caughtAtAsc: "Date of discovery (Ascending)"
Expand Down
19 changes: 6 additions & 13 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1839,23 +1839,16 @@ admin/views/federation.vue:
system: "システム"
softwareName: "ソフトウェア"
softwareVersion: "バージョン"
following: "フォロー中"
followers: "フォロワー"
caught-at: "登録日時"
infoUpdatedAt: "情報更新日時"
status: "ステータス"
activeHalfyear: "Users 6m"
activeMonth: "Users 1m"
latest-request-sent-at: "直近のリクエスト送信"
latest-request-received-at: "直近のリクエスト受信"
remove-all-following: "フォローを全解除"
remove-all-following-info: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。"
ignore: "インスタンスを無視"
ignore: "インスタンスを無視 (ブロック)"
marked-as-closed: "閉鎖されているとマーク"
flag-info: "無視はそのインスタンスとの送受信と取得を停止します。閉鎖は送信のみを停止します。フラグは疎通状況によって自動更新されます"
flag-info: "無視はそのインスタンスとの送受信と取得を停止します。閉鎖は送信のみを停止します。閉鎖フラグは疎通状況によって自動更新されます"
lookup: "照会"
instances: "連合"
instance-not-registered: "そのインスタンスは登録されていません"
matchBlocked: "インスタンスモデレーションで無視に一致しています"
matchSelfSilenced: "インスタンスモデレーションでセルフサイレンスに一致しています"
deleteInstanceUsers: "インスタンスのユーザーを削除"
deleteInstanceUsers-confirm: "インスタンス内のユーザーを削除しますか (最大1000件削除されます)"
sort: "ソート"
sorts:
caughtAtAsc: "登録日時が古い順"
Expand Down
200 changes: 71 additions & 129 deletions src/client/app/admin/views/federation.vue
Original file line number Diff line number Diff line change
@@ -1,132 +1,64 @@
<template>
<div>
<!-- 照会 -->
<ui-card>
<template #title><fa :icon="faTerminal"/> {{ $t('instance') }}</template>
<section class="fit-top">
<ui-input class="target" v-model="target" type="text" @enter="showInstance()">
<span>{{ $t('host') }}</span>
</ui-input>
<ui-button @click="showInstance()"><fa :icon="faSearch"/> {{ $t('lookup') }}</ui-button>
</section>
</ui-card>

<div class="instance" v-if="instance">
<ui-horizon-group inputs>
<ui-input :value="instance.host" type="text" readonly>
<span>{{ $t('host') }}</span>
</ui-input>
<ui-input :value="instance.caughtAt | date" type="text" readonly>
<span>{{ $t('caught-at') }}</span>
</ui-input>
<ui-input :value="instance.infoUpdatedAt | date" type="text" readonly>
<span>{{ $t('infoUpdatedAt') }}</span>
</ui-input>
</ui-horizon-group>
<ui-horizon-group inputs>
<ui-input :value="instance.notesCount" type="text" readonly>
<span>{{ $t('notes') }}</span>
</ui-input>
<ui-input :value="instance.usersCount" type="text" readonly>
<span>{{ $t('users') }}</span>
</ui-input>
<ui-input :value="instance.activeHalfyear" type="text" readonly>
<span>{{ $t('activeHalfyear') }}</span>
</ui-input>
<ui-input :value="instance.activeMonth" type="text" readonly>
<span>{{ $t('activeMonth') }}</span>
</ui-input>
<ui-input :value="instance.followingCount" type="text" readonly>
<span>{{ $t('following') }}</span>
</ui-input>
<ui-input :value="instance.followersCount" type="text" readonly>
<span>{{ $t('followers') }}</span>
</ui-input>
</ui-horizon-group>
<ui-horizon-group inputs>
<ui-input :value="instance.latestRequestSentAt | date" type="text" readonly>
<span>{{ $t('latest-request-sent-at') }}</span>
</ui-input>
<ui-input :value="instance.latestStatus" type="text" readonly>
<span>{{ $t('status') }}</span>
</ui-input>
<ui-input :value="instance.latestRequestReceivedAt | date" type="text" readonly>
<span>{{ $t('latest-request-received-at') }}</span>
</ui-input>
</ui-horizon-group>
<ui-horizon-group inputs>
<ui-input :value="instance.cc" type="text" readonly>
<template #prefix><mfm :text="ccToEmoji(instance.cc)" :plain="true" :nowrap="true" :key="instance.cc"/></template>
<span>CC</span>
</ui-input>
<ui-input :value="instance.isp" type="text" readonly>
<span>ISP</span>
</ui-input>
<ui-input :value="instance.org" type="text" readonly>
<span>ORG</span>
</ui-input>
<ui-input :value="instance.as" type="text" readonly>
<span>AS</span>
</ui-input>
</ui-horizon-group>
<ui-horizon-group inputs>
<ui-input :value="instance.softwareName" type="text" readonly>
<span>{{ $t('softwareName') }}</span>
</ui-input>
<ui-input :value="instance.softwareVersion" type="text" readonly>
<span>{{ $t('softwareVersion') }}</span>
</ui-input>
</ui-horizon-group>
<ui-horizon-group inputs>
<ui-input :value="instance.name" type="text" readonly>
<span>{{ $t('name') }}</span>
</ui-input>
<ui-input :value="instance.description" type="text" readonly>
<span>{{ $t('description') }}</span>
</ui-input>
</ui-horizon-group>
<!-- インスタンス -->
<ui-card v-if="instance">
<template #title>{{ instance.host }}</template>
<section>
<ui-info :warn="true" v-if="instance.matchBlocked">{{ $t('matchBlocked') }}</ui-info>
<ui-info :warn="true" v-if="instance.matchSelfSilenced">{{ $t('matchSelfSilenced') }}</ui-info>
<ui-switch v-model="instance.isBlocked" @change="updateInstance()" :disabled="!$store.getters.isAdminOrModerator">{{ $t('ignore') }}</ui-switch>
<ui-switch v-model="instance.isMarkedAsClosed" @change="updateInstance()" :disabled="!$store.getters.isAdminOrModerator">{{ $t('marked-as-closed') }}</ui-switch>
<ui-info>{{ $t('flag-info') }}</ui-info>
<ui-button v-if="instance.isBlocked || instance.isMarkedAsClosed || instance.matchBlocked" @click="deleteInstanceUsers()">{{ $t('deleteInstanceUsers') }}</ui-button>
</section>

<!-- meta -->
<section>
<details :open="false">
<ui-textarea :value="instance | json5" readonly tall style="margin-top:16px;"></ui-textarea>
</details>
</section>

<!-- チャート -->
<section>
<details :open="false">
<summary>{{ $t('charts') }}</summary>
<ui-horizon-group inputs>
<ui-input :value="instance.maintainerName" type="text" readonly>
<span>{{ $t('maintainerName') }}</span>
</ui-input>
<ui-input :value="instance.maintainerEmail" type="text" readonly>
<span>{{ $t('maintainerEmail') }}</span>
</ui-input>
<ui-select v-model="chartSrc">
<option value="requests">{{ $t('chart-srcs.requests') }}</option>
<option value="users">{{ $t('chart-srcs.users') }}</option>
<option value="users-total">{{ $t('chart-srcs.users-total') }}</option>
<option value="notes">{{ $t('chart-srcs.notes') }}</option>
<option value="notes-total">{{ $t('chart-srcs.notes-total') }}</option>
<option value="ff">{{ $t('chart-srcs.ff') }}</option>
<option value="ff-total">{{ $t('chart-srcs.ff-total') }}</option>
<option value="drive-usage">{{ $t('chart-srcs.drive-usage') }}</option>
<option value="drive-usage-total">{{ $t('chart-srcs.drive-usage-total') }}</option>
<option value="drive-files">{{ $t('chart-srcs.drive-files') }}</option>
<option value="drive-files-total">{{ $t('chart-srcs.drive-files-total') }}</option>
</ui-select>
<ui-select v-model="chartSpan">
<option value="hour">{{ $t('chart-spans.hour') }}</option>
<option value="day">{{ $t('chart-spans.day') }}</option>
</ui-select>
</ui-horizon-group>
<ui-switch v-model="instance.isBlocked" @change="updateInstance()" :disabled="!$store.getters.isAdminOrModerator">{{ $t('ignore') }}</ui-switch>
<ui-switch v-model="instance.isMarkedAsClosed" @change="updateInstance()" :disabled="!$store.getters.isAdminOrModerator">{{ $t('marked-as-closed') }}</ui-switch>
<ui-info>{{ $t('flag-info') }}</ui-info>
<details :open="true">
<summary>{{ $t('charts') }}</summary>
<ui-horizon-group inputs>
<ui-select v-model="chartSrc">
<option value="requests">{{ $t('chart-srcs.requests') }}</option>
<option value="users">{{ $t('chart-srcs.users') }}</option>
<option value="users-total">{{ $t('chart-srcs.users-total') }}</option>
<option value="notes">{{ $t('chart-srcs.notes') }}</option>
<option value="notes-total">{{ $t('chart-srcs.notes-total') }}</option>
<option value="ff">{{ $t('chart-srcs.ff') }}</option>
<option value="ff-total">{{ $t('chart-srcs.ff-total') }}</option>
<option value="drive-usage">{{ $t('chart-srcs.drive-usage') }}</option>
<option value="drive-usage-total">{{ $t('chart-srcs.drive-usage-total') }}</option>
<option value="drive-files">{{ $t('chart-srcs.drive-files') }}</option>
<option value="drive-files-total">{{ $t('chart-srcs.drive-files-total') }}</option>
</ui-select>
<ui-select v-model="chartSpan">
<option value="hour">{{ $t('chart-spans.hour') }}</option>
<option value="day">{{ $t('chart-spans.day') }}</option>
</ui-select>
</ui-horizon-group>
<div ref="chart"></div>
</details>
<!--
<details v-if="$store.getters.isAdminOrModerator">
<summary>{{ $t('remove-all-following') }}</summary>
<ui-button @click="removeAllFollowing()" style="margin-top: 16px;"><fa :icon="faMinusCircle"/> {{ $t('remove-all-following') }}</ui-button>
<ui-info warn>{{ $t('remove-all-following-info', { host: instance.host }) }}</ui-info>
</details>
-->
</div>
<div ref="chart"></div>
</details>
</section>
</ui-card>

<!-- 一覧 -->
<ui-card>
<template #title><fa :icon="faServer"/> {{ $t('instances') }}</template>
<section class="fit-top">
Expand Down Expand Up @@ -182,21 +114,15 @@
<span>{{ $t('system') }}</span>
<span>{{ $t('notes') }}</span>
<span>{{ $t('users') }}</span>
<span>{{ $t('activeHalfyear') }}</span>
<span>{{ $t('activeMonth') }}</span>
<span>{{ $t('status') }}</span>
</header>
<div v-for="instance in instances" :key="instance.host" :style="{ opacity: instance.isNotResponding ? 0.5 : 1 }">
<a @click.prevent="showInstance(instance.host)" rel="nofollow noopener" target="_blank" :href="`https://${instance.host}`" :title="instance.name" :style="{ textDecoration: instance.isMarkedAsClosed ? 'line-through' : 'none', display: 'inline-flex', overflow: 'hidden', 'word-break': 'break-all' }">
<img v-if="instance.iconUrl != null" :src="`/proxy/icon.ico?${urlQuery({ url: instance.iconUrl })}`" :style="{ width: '1em', height: '1em' }"/>
{{ `${instance.host }` }}
</a>
<span>{{ `${instance.softwareName || 'unknown'}` }} <small :style="{ opacity: 0.7 }">{{ `${instance.softwareVersion || ''}` }}</small></span>
<span>{{ `${instance.softwareName || 'unknown'}` }}<br><small :style="{ opacity: 0.7 }">{{ `${instance.softwareVersion || ''}` }}</small></span>
<span>{{ instance.notesCount | kmg }}</span>
<span>{{ instance.usersCount | kmg }}</span>
<span>{{ instance.activeHalfyear | kmg }}</span>
<span>{{ instance.activeMonth | kmg }}</span>
<span>{{ instance.latestStatus }}</span>
</div>
</div>

Expand Down Expand Up @@ -231,7 +157,7 @@ export default defineComponent({
$root: getCurrentInstance() as any,
instance: null as Record<string, any> | null,
target: null as any,
sort: '+infoUpdatedAt',
sort: '+caughtAt',
state: 'all',
softwareName: '',
softwareVersion: '',
Expand Down Expand Up @@ -366,6 +292,29 @@ export default defineComponent({
});
},
async deleteInstanceUsers() {
if (!this.instance) return;
const confirm = await this.$root.dialog({
type: 'warning',
showCancelButton: true,
title: 'confirm',
text: this.$t('deleteInstanceUsers-confirm'),
});
if (confirm.canceled) return;
this.$root.api('admin/delete-instance-users', {
host: this.instance.host,
limit: 1000,
}).catch((e: any) => {
this.$root.dialog({
type: 'error',
text: e.message || e
});
});
},
updateInstance() {
if (!this.instance) return;
this.$root.api('admin/federation/update-instance', {
Expand Down Expand Up @@ -467,13 +416,6 @@ export default defineComponent({
format(arr) {
return arr.map((v, i) => ({ x: this.getDate(i).getTime(), y: v }));
},
ccToEmoji(cc: string | null | undefined) {
if (cc == null) return '';
if (cc === '??') return '';
return cc.toUpperCase().replace(/./g, c => String.fromCodePoint(c.charCodeAt(0) + 127397));
},
requestsChart(): any {
return {
series: [{
Expand Down
13 changes: 11 additions & 2 deletions src/server/api/endpoints/admin/delete-instance-users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { doPostSuspend } from '../../../../services/suspend-user';
import { createDeleteNotesJob, createDeleteDriveFilesJob } from '../../../../queue';
import { toDbHost } from '../../../../misc/convert-host';
import { isBlockedHost, isClosedHost } from '../../../../services/instance-moderation';
import { ApiError } from '../../error';

export const meta = {
desc: {
Expand All @@ -31,13 +32,21 @@ export const meta = {
validator: $.optional.num.range(1, 1000),
default: 50,
},
}
},

errors: {
hostIsAvailable: {
message: 'Host is available.',
code: 'HOST_IS_AVAILABLE',
id: '66dcfd00-1905-4e89-b2ac-b588fb1348fd'
},
},
};

export default define(meta, async (ps) => {
const host = toDbHost(ps.host);

if (!await isBlockedHost(host) && !await isClosedHost(host)) throw new Error('instance はブロックでもクローズでもない');
if (!await isBlockedHost(host) && !await isClosedHost(host)) throw new ApiError(meta.errors.hostIsAvailable);

const users = await User.find({
host,
Expand Down
6 changes: 5 additions & 1 deletion src/server/api/endpoints/admin/federation/show-instance.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import $ from 'cafy';
import define from '../../../define';
import Instance from '../../../../../models/instance';
import { isBlockedHost, isSelfSilencedHost } from '../../../../../services/instance-moderation';

export const meta = {
tags: ['federation'],
Expand All @@ -17,7 +18,10 @@ export const meta = {

export default define(meta, async (ps, me) => {
const instance = await Instance
.findOne({ host: ps.host });
.findOne({ host: ps.host }) as Record<string, unknown>;

instance.matchBlocked = await isBlockedHost(ps.host);
instance.matchSelfSilenced = await isSelfSilencedHost(ps.host);

return instance;
});

0 comments on commit e7cc35e

Please sign in to comment.