egirlskey/packages/client/src/pages/user-info.vue
PikaDude 6061937996
User moderation details (#8762)
* add more user details for admins to see

* fix some issues

* small style fix

as suggested by Johann150

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

* fix

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>

Co-authored-by: Johann150 <johann@qwertqwefsday.eu>
2022-06-03 23:14:50 +09:00

258 lines
7.1 KiB
Vue

<template>
<MkSpacer :content-max="500" :margin-min="16" :margin-max="32">
<FormSuspense :p="init">
<div class="_formRoot">
<div class="_formBlock aeakzknw">
<MkAvatar class="avatar" :user="user" :show-indicator="true"/>
</div>
<FormLink :to="userPage(user)">Profile</FormLink>
<div class="_formBlock">
<MkKeyValue :copy="acct(user)" oneline style="margin: 1em 0;">
<template #key>Acct</template>
<template #value><span class="_monospace">{{ acct(user) }}</span></template>
</MkKeyValue>
<MkKeyValue :copy="user.id" oneline style="margin: 1em 0;">
<template #key>ID</template>
<template #value><span class="_monospace">{{ user.id }}</span></template>
</MkKeyValue>
</div>
<FormSection v-if="iAmModerator">
<template #label>Moderation</template>
<FormSwitch v-if="user.host == null && $i.isAdmin && (moderator || !user.isAdmin)" v-model="moderator" class="_formBlock" @update:modelValue="toggleModerator">{{ $ts.moderator }}</FormSwitch>
<FormSwitch v-model="silenced" class="_formBlock" @update:modelValue="toggleSilence">{{ $ts.silence }}</FormSwitch>
<FormSwitch v-model="suspended" class="_formBlock" @update:modelValue="toggleSuspend">{{ $ts.suspend }}</FormSwitch>
{{ $ts.reflectMayTakeTime }}
<FormButton v-if="user.host == null && iAmModerator" class="_formBlock" @click="resetPassword"><i class="fas fa-key"></i> {{ $ts.resetPassword }}</FormButton>
</FormSection>
<FormSection>
<template #label>ActivityPub</template>
<div class="_formBlock">
<MkKeyValue v-if="user.host" oneline style="margin: 1em 0;">
<template #key>{{ $ts.instanceInfo }}</template>
<template #value><MkA :to="`/instance-info/${user.host}`" class="_link">{{ user.host }} <i class="fas fa-angle-right"></i></MkA></template>
</MkKeyValue>
<MkKeyValue v-else oneline style="margin: 1em 0;">
<template #key>{{ $ts.instanceInfo }}</template>
<template #value>(Local user)</template>
</MkKeyValue>
<MkKeyValue oneline style="margin: 1em 0;">
<template #key>{{ $ts.updatedAt }}</template>
<template #value><MkTime v-if="user.lastFetchedAt" mode="detail" :time="user.lastFetchedAt"/><span v-else>N/A</span></template>
</MkKeyValue>
<MkKeyValue v-if="ap" oneline style="margin: 1em 0;">
<template #key>Type</template>
<template #value><span class="_monospace">{{ ap.type }}</span></template>
</MkKeyValue>
</div>
<FormButton v-if="user.host != null" class="_formBlock" @click="updateRemoteUser"><i class="fas fa-sync"></i> {{ $ts.updateRemoteUser }}</FormButton>
</FormSection>
<MkObjectView v-if="info && $i.isAdmin" tall :value="info">
</MkObjectView>
<MkObjectView tall :value="user">
</MkObjectView>
</div>
</FormSuspense>
</MkSpacer>
</template>
<script lang="ts">
import { computed, defineAsyncComponent, defineComponent } from 'vue';
import MkObjectView from '@/components/object-view.vue';
import FormTextarea from '@/components/form/textarea.vue';
import FormSwitch from '@/components/form/switch.vue';
import FormLink from '@/components/form/link.vue';
import FormSection from '@/components/form/section.vue';
import FormButton from '@/components/ui/button.vue';
import MkKeyValue from '@/components/key-value.vue';
import FormSuspense from '@/components/form/suspense.vue';
import * as os from '@/os';
import number from '@/filters/number';
import bytes from '@/filters/bytes';
import * as symbols from '@/symbols';
import { url } from '@/config';
import { userPage, acct } from '@/filters/user';
export default defineComponent({
components: {
FormSection,
FormTextarea,
FormSwitch,
MkObjectView,
FormButton,
FormLink,
MkKeyValue,
FormSuspense,
},
props: {
userId: {
type: String,
required: true
}
},
data() {
return {
[symbols.PAGE_INFO]: computed(() => ({
title: this.user ? acct(this.user) : this.$ts.userInfo,
icon: 'fas fa-info-circle',
bg: 'var(--bg)',
actions: this.user ? [this.user.url ? {
text: this.user.url,
icon: 'fas fa-external-link-alt',
handler: () => {
window.open(this.user.url, '_blank');
}
} : undefined].filter(x => x !== undefined) : [],
})),
init: null,
user: null,
info: null,
ap: null,
moderator: false,
silenced: false,
suspended: false,
};
},
computed: {
iAmModerator(): boolean {
return this.$i && (this.$i.isAdmin || this.$i.isModerator);
}
},
watch: {
userId: {
handler() {
this.init = this.createFetcher();
},
immediate: true
},
user() {
os.api('ap/get', {
uri: this.user.uri || `${url}/users/${this.user.id}`
}).then(res => {
this.ap = res;
});
}
},
methods: {
number,
bytes,
userPage,
acct,
createFetcher() {
if (this.iAmModerator) {
return () => Promise.all([os.api('users/show', {
userId: this.userId
}), os.api('admin/show-user', {
userId: this.userId
})]).then(([user, info]) => {
this.user = user;
this.info = info;
this.moderator = this.info.isModerator;
this.silenced = this.info.isSilenced;
this.suspended = this.info.isSuspended;
});
} else {
return () => os.api('users/show', {
userId: this.userId
}).then((user) => {
this.user = user;
});
}
},
refreshUser() {
this.init = this.createFetcher();
},
async updateRemoteUser() {
await os.apiWithDialog('federation/update-remote-user', { userId: this.user.id });
this.refreshUser();
},
async resetPassword() {
const { password } = await os.api('admin/reset-password', {
userId: this.user.id,
});
os.alert({
type: 'success',
text: this.$t('newPasswordIs', { password })
});
},
async toggleSilence(v) {
const confirm = await os.confirm({
type: 'warning',
text: v ? this.$ts.silenceConfirm : this.$ts.unsilenceConfirm,
});
if (confirm.canceled) {
this.silenced = !v;
} else {
await os.api(v ? 'admin/silence-user' : 'admin/unsilence-user', { userId: this.user.id });
await this.refreshUser();
}
},
async toggleSuspend(v) {
const confirm = await os.confirm({
type: 'warning',
text: v ? this.$ts.suspendConfirm : this.$ts.unsuspendConfirm,
});
if (confirm.canceled) {
this.suspended = !v;
} else {
await os.api(v ? 'admin/suspend-user' : 'admin/unsuspend-user', { userId: this.user.id });
await this.refreshUser();
}
},
async toggleModerator(v) {
await os.api(v ? 'admin/moderators/add' : 'admin/moderators/remove', { userId: this.user.id });
await this.refreshUser();
},
async deleteAllFiles() {
const confirm = await os.confirm({
type: 'warning',
text: this.$ts.deleteAllFilesConfirm,
});
if (confirm.canceled) return;
const process = async () => {
await os.api('admin/delete-all-files-of-a-user', { userId: this.user.id });
os.success();
};
await process().catch(err => {
os.alert({
type: 'error',
text: err.toString(),
});
});
await this.refreshUser();
},
}
});
</script>
<style lang="scss" scoped>
.aeakzknw {
> .avatar {
display: block;
width: 64px;
height: 64px;
}
}
</style>