82
									
								
								src/client/app/admin/views/users.user.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/client/app/admin/views/users.user.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| <template> | ||||
| <div class="kofvwchc"> | ||||
| 	<div> | ||||
| 		<a :href="user | userPage(null, true)"> | ||||
| 			<mk-avatar class="avatar" :user="user" :disable-link="true"/> | ||||
| 		</a> | ||||
| 	</div> | ||||
| 	<div> | ||||
| 		<header> | ||||
| 			<b><mk-user-name :user="user"/></b> | ||||
| 			<span class="username">@{{ user | acct }}</span> | ||||
| 			<span class="is-admin" v-if="user.isAdmin">admin</span> | ||||
| 			<span class="is-moderator" v-if="user.isModerator">moderator</span> | ||||
| 			<span class="is-verified" v-if="user.isVerified" :title="$t('@.verified-user')"><fa icon="star"/></span> | ||||
| 			<span class="is-suspended" v-if="user.isSuspended" :title="$t('@.suspended-user')"><fa :icon="faSnowflake"/></span> | ||||
| 		</header> | ||||
| 		<div> | ||||
| 			<span>{{ $t('users.updatedAt') }}: <mk-time :time="user.updatedAt" mode="detail"/></span> | ||||
| 		</div> | ||||
| 		<div> | ||||
| 			<span>{{ $t('users.createdAt') }}: <mk-time :time="user.createdAt" mode="detail"/></span> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
|  | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import i18n from '../../i18n'; | ||||
| import { faSnowflake } from '@fortawesome/free-regular-svg-icons'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('admin/views/users.vue'), | ||||
| 	props: ['user'], | ||||
| 	data() { | ||||
| 		return { | ||||
| 			faSnowflake | ||||
| 		}; | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .kofvwchc | ||||
| 	display flex | ||||
| 	padding 16px 0 | ||||
| 	border-top solid 1px var(--faceDivider) | ||||
|  | ||||
| 	> div:first-child | ||||
| 		> a | ||||
| 			> .avatar | ||||
| 				width 64px | ||||
| 				height 64px | ||||
|  | ||||
| 	> div:last-child | ||||
| 		flex 1 | ||||
| 		padding-left 16px | ||||
|  | ||||
| 		@media (max-width 500px) | ||||
| 			font-size 14px | ||||
|  | ||||
| 		> header | ||||
| 			> .username | ||||
| 				margin-left 8px | ||||
| 				opacity 0.7 | ||||
|  | ||||
| 			> .is-admin | ||||
| 			> .is-moderator | ||||
| 				flex-shrink 0 | ||||
| 				align-self center | ||||
| 				margin 0 0 0 .5em | ||||
| 				padding 1px 6px | ||||
| 				font-size 80% | ||||
| 				border-radius 3px | ||||
| 				background var(--noteHeaderAdminBg) | ||||
| 				color var(--noteHeaderAdminFg) | ||||
|  | ||||
| 			> .is-verified | ||||
| 			> .is-suspended | ||||
| 				margin 0 0 0 .5em | ||||
| 				color #4dabf7 | ||||
| </style> | ||||
| @@ -3,20 +3,26 @@ | ||||
| 	<ui-card> | ||||
| 		<div slot="title"><fa :icon="faTerminal"/> {{ $t('operation') }}</div> | ||||
| 		<section class="fit-top"> | ||||
| 			<ui-input v-model="target" type="text"> | ||||
| 			<ui-input class="target" v-model="target" type="text"> | ||||
| 				<span>{{ $t('username-or-userid') }}</span> | ||||
| 			</ui-input> | ||||
| 			<ui-button @click="resetPassword"><fa :icon="faKey"/> {{ $t('reset-password') }}</ui-button> | ||||
| 			<ui-horizon-group> | ||||
| 				<ui-button @click="verifyUser" :disabled="verifying"><fa :icon="faCertificate"/> {{ $t('verify') }}</ui-button> | ||||
| 				<ui-button @click="unverifyUser" :disabled="unverifying">{{ $t('unverify') }}</ui-button> | ||||
| 			</ui-horizon-group> | ||||
| 			<ui-horizon-group> | ||||
| 				<ui-button @click="suspendUser" :disabled="suspending"><fa :icon="faSnowflake"/> {{ $t('suspend') }}</ui-button> | ||||
| 				<ui-button @click="unsuspendUser" :disabled="unsuspending">{{ $t('unsuspend') }}</ui-button> | ||||
| 			</ui-horizon-group> | ||||
| 			<ui-button @click="showUser"><fa :icon="faSearch"/> {{ $t('lookup') }}</ui-button> | ||||
| 			<ui-textarea v-if="user" :value="user | json5" readonly tall style="margin-top:16px;"></ui-textarea> | ||||
|  | ||||
| 			<div class="user" v-if="user"> | ||||
| 				<x-user :user='user'/> | ||||
| 				<div class="actions"> | ||||
| 					<ui-button @click="resetPassword"><fa :icon="faKey"/> {{ $t('reset-password') }}</ui-button> | ||||
| 					<ui-horizon-group> | ||||
| 						<ui-button @click="verifyUser" :disabled="verifying"><fa :icon="faCertificate"/> {{ $t('verify') }}</ui-button> | ||||
| 						<ui-button @click="unverifyUser" :disabled="unverifying">{{ $t('unverify') }}</ui-button> | ||||
| 					</ui-horizon-group> | ||||
| 					<ui-horizon-group> | ||||
| 						<ui-button @click="suspendUser" :disabled="suspending"><fa :icon="faSnowflake"/> {{ $t('suspend') }}</ui-button> | ||||
| 						<ui-button @click="unsuspendUser" :disabled="unsuspending">{{ $t('unsuspend') }}</ui-button> | ||||
| 					</ui-horizon-group> | ||||
| 					<ui-textarea v-if="user" :value="user | json5" readonly tall style="margin-top:16px;"></ui-textarea> | ||||
| 				</div> | ||||
| 			</div> | ||||
| 		</section> | ||||
| 	</ui-card> | ||||
|  | ||||
| @@ -47,29 +53,7 @@ | ||||
| 				</ui-select> | ||||
| 			</ui-horizon-group> | ||||
| 			<sequential-entrance animation="entranceFromTop" delay="25"> | ||||
| 				<div class="kofvwchc" v-for="user in users" :key="user.id"> | ||||
| 					<div> | ||||
| 						<a :href="user | userPage(null, true)"> | ||||
| 							<mk-avatar class="avatar" :user="user" :disable-link="true"/> | ||||
| 						</a> | ||||
| 					</div> | ||||
| 					<div> | ||||
| 						<header> | ||||
| 							<b><mk-user-name :user="user"/></b> | ||||
| 							<span class="username">@{{ user | acct }}</span> | ||||
| 							<span class="is-admin" v-if="user.isAdmin">admin</span> | ||||
| 							<span class="is-moderator" v-if="user.isModerator">moderator</span> | ||||
| 							<span class="is-verified" v-if="user.isVerified" :title="$t('@.verified-user')"><fa icon="star"/></span> | ||||
| 							<span class="is-suspended" v-if="user.isSuspended" :title="$t('@.suspended-user')"><fa :icon="faSnowflake"/></span> | ||||
| 						</header> | ||||
| 						<div> | ||||
| 							<span>{{ $t('users.updatedAt') }}: <mk-time :time="user.updatedAt" mode="detail"/></span> | ||||
| 						</div> | ||||
| 						<div> | ||||
| 							<span>{{ $t('users.createdAt') }}: <mk-time :time="user.createdAt" mode="detail"/></span> | ||||
| 						</div> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 				<x-user v-for="user in users" :user='user' :key="user.id"/> | ||||
| 			</sequential-entrance> | ||||
| 			<ui-button v-if="existMore" @click="fetchUsers">{{ $t('@.load-more') }}</ui-button> | ||||
| 		</section> | ||||
| @@ -83,10 +67,13 @@ import i18n from '../../i18n'; | ||||
| import parseAcct from "../../../../misc/acct/parse"; | ||||
| import { faCertificate, faUsers, faTerminal, faSearch, faKey } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faSnowflake } from '@fortawesome/free-regular-svg-icons'; | ||||
| import XUser from './users.user.vue'; | ||||
|  | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('admin/views/users.vue'), | ||||
|  | ||||
| 	components: { | ||||
| 		XUser | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			user: null, | ||||
| @@ -131,6 +118,7 @@ export default Vue.extend({ | ||||
| 	}, | ||||
|  | ||||
| 	methods: { | ||||
| 		/** テキストエリアのユーザーを解決する */ | ||||
| 		async fetchUser() { | ||||
| 			try { | ||||
| 				return await this.$root.api('users/show', this.target.startsWith('@') ? parseAcct(this.target) : { userId: this.target }); | ||||
| @@ -149,16 +137,27 @@ export default Vue.extend({ | ||||
| 			} | ||||
| 		}, | ||||
|  | ||||
| 		/** テキストエリアから処理対象ユーザーを設定する */ | ||||
| 		async showUser() { | ||||
| 			this.user = null; | ||||
| 			const user = await this.fetchUser(); | ||||
| 			this.$root.api('admin/show-user', { userId: user.id }).then(info => { | ||||
| 				this.user = info; | ||||
| 			}); | ||||
| 			this.target = ''; | ||||
| 		}, | ||||
|  | ||||
| 		/** 処理対象ユーザーの情報を更新する */ | ||||
| 		async refreshUser() { | ||||
| 			this.$root.api('admin/show-user', { userId: this.user._id }).then(info => { | ||||
| 				this.user = info; | ||||
| 			}); | ||||
| 		}, | ||||
|  | ||||
| 		async resetPassword() { | ||||
| 			const user = await this.fetchUser(); | ||||
| 			this.$root.api('admin/reset-password', { userId: user.id }).then(res => { | ||||
| 			if (!await this.getConfirmed(this.$t('reset-password-confirm'))) return; | ||||
|  | ||||
| 			this.$root.api('admin/reset-password', { userId: this.user._id }).then(res => { | ||||
| 				this.$root.dialog({ | ||||
| 					type: 'success', | ||||
| 					text: this.$t('password-updated', { password: res.password }) | ||||
| @@ -167,11 +166,12 @@ export default Vue.extend({ | ||||
| 		}, | ||||
|  | ||||
| 		async verifyUser() { | ||||
| 			if (!await this.getConfirmed(this.$t('verify-confirm'))) return; | ||||
|  | ||||
| 			this.verifying = true; | ||||
|  | ||||
| 			const process = async () => { | ||||
| 				const user = await this.fetchUser(); | ||||
| 				await this.$root.api('admin/verify-user', { userId: user.id }); | ||||
| 				await this.$root.api('admin/verify-user', { userId: this.user._id }); | ||||
| 				this.$root.dialog({ | ||||
| 					type: 'success', | ||||
| 					text: this.$t('verified') | ||||
| @@ -186,14 +186,17 @@ export default Vue.extend({ | ||||
| 			}); | ||||
|  | ||||
| 			this.verifying = false; | ||||
|  | ||||
| 			this.refreshUser(); | ||||
| 		}, | ||||
|  | ||||
| 		async unverifyUser() { | ||||
| 			if (!await this.getConfirmed(this.$t('unverify-confirm'))) return; | ||||
|  | ||||
| 			this.unverifying = true; | ||||
|  | ||||
| 			const process = async () => { | ||||
| 				const user = await this.fetchUser(); | ||||
| 				await this.$root.api('admin/unverify-user', { userId: user.id }); | ||||
| 				await this.$root.api('admin/unverify-user', { userId: this.user._id }); | ||||
| 				this.$root.dialog({ | ||||
| 					type: 'success', | ||||
| 					text: this.$t('unverified') | ||||
| @@ -208,14 +211,17 @@ export default Vue.extend({ | ||||
| 			}); | ||||
|  | ||||
| 			this.unverifying = false; | ||||
|  | ||||
| 			this.refreshUser(); | ||||
| 		}, | ||||
|  | ||||
| 		async suspendUser() { | ||||
| 			if (!await this.getConfirmed(this.$t('suspend-confirm'))) return; | ||||
|  | ||||
| 			this.suspending = true; | ||||
|  | ||||
| 			const process = async () => { | ||||
| 				const user = await this.fetchUser(); | ||||
| 				await this.$root.api('admin/suspend-user', { userId: user.id }); | ||||
| 				await this.$root.api('admin/suspend-user', { userId: this.user._id }); | ||||
| 				this.$root.dialog({ | ||||
| 					type: 'success', | ||||
| 					text: this.$t('suspended') | ||||
| @@ -230,14 +236,17 @@ export default Vue.extend({ | ||||
| 			}); | ||||
|  | ||||
| 			this.suspending = false; | ||||
|  | ||||
| 			this.refreshUser(); | ||||
| 		}, | ||||
|  | ||||
| 		async unsuspendUser() { | ||||
| 			if (!await this.getConfirmed(this.$t('unsuspend-confirm'))) return; | ||||
|  | ||||
| 			this.unsuspending = true; | ||||
|  | ||||
| 			const process = async () => { | ||||
| 				const user = await this.fetchUser(); | ||||
| 				await this.$root.api('admin/unsuspend-user', { userId: user.id }); | ||||
| 				await this.$root.api('admin/unsuspend-user', { userId: this.user._id }); | ||||
| 				this.$root.dialog({ | ||||
| 					type: 'success', | ||||
| 					text: this.$t('unsuspended') | ||||
| @@ -252,8 +261,21 @@ export default Vue.extend({ | ||||
| 			}); | ||||
|  | ||||
| 			this.unsuspending = false; | ||||
|  | ||||
| 			this.refreshUser(); | ||||
| 		}, | ||||
|  | ||||
| 		async getConfirmed(text: string): Promise<Boolean> { | ||||
| 			const confirm = await this.$root.dialog({ | ||||
| 				type: 'warning', | ||||
| 				showCancelButton: true, | ||||
| 				title: 'confirm', | ||||
| 				text, | ||||
| 			}); | ||||
|  | ||||
| 			return !confirm.canceled; | ||||
| 		} | ||||
|  | ||||
| 		fetchUsers() { | ||||
| 			this.$root.api('admin/show-users', { | ||||
| 				state: this.state, | ||||
| @@ -277,42 +299,12 @@ export default Vue.extend({ | ||||
| </script> | ||||
|  | ||||
| <style lang="stylus" scoped> | ||||
| .kofvwchc | ||||
| 	display flex | ||||
| 	padding 16px 0 | ||||
| 	border-top solid 1px var(--faceDivider) | ||||
| .target | ||||
| 	margin-bottom 16px !important | ||||
|  | ||||
| 	> div:first-child | ||||
| 		> a | ||||
| 			> .avatar | ||||
| 				width 64px | ||||
| 				height 64px | ||||
| .user | ||||
| 	margin-top 32px | ||||
|  | ||||
| 	> div:last-child | ||||
| 		flex 1 | ||||
| 		padding-left 16px | ||||
|  | ||||
| 		@media (max-width 500px) | ||||
| 			font-size 14px | ||||
|  | ||||
| 		> header | ||||
| 			> .username | ||||
| 				margin-left 8px | ||||
| 				opacity 0.7 | ||||
|  | ||||
| 			> .is-admin | ||||
| 			> .is-moderator | ||||
| 				flex-shrink 0 | ||||
| 				align-self center | ||||
| 				margin 0 0 0 .5em | ||||
| 				padding 1px 6px | ||||
| 				font-size 80% | ||||
| 				border-radius 3px | ||||
| 				background var(--noteHeaderAdminBg) | ||||
| 				color var(--noteHeaderAdminFg) | ||||
|  | ||||
| 			> .is-verified | ||||
| 			> .is-suspended | ||||
| 				margin 0 0 0 .5em | ||||
| 				color #4dabf7 | ||||
| 	> .actions | ||||
| 		margin-left 80px | ||||
| </style> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 MeiMei
					MeiMei