commit
						d35f62d0e4
					
				
					 8 changed files with 241 additions and 2 deletions
				
			
		| 
						 | 
					@ -897,6 +897,24 @@ desktop/views/components/window.vue:
 | 
				
			||||||
  popout: "ポップアウト"
 | 
					  popout: "ポップアウト"
 | 
				
			||||||
  close: "閉じる"
 | 
					  close: "閉じる"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					desktop/views/pages/admin/admin.vue:
 | 
				
			||||||
 | 
					  dashboard: "ダッシュボード"
 | 
				
			||||||
 | 
					  drive: "ドライブ"
 | 
				
			||||||
 | 
					  users: "ユーザー"
 | 
				
			||||||
 | 
					  update: "更新"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					desktop/views/pages/admin/admin.dashboard.vue:
 | 
				
			||||||
 | 
					  dashboard: "ダッシュボード"
 | 
				
			||||||
 | 
					  all-users: "全てのユーザー"
 | 
				
			||||||
 | 
					  original-users: "このインスタンスのユーザー"
 | 
				
			||||||
 | 
					  all-notes: "全てのノート"
 | 
				
			||||||
 | 
					  original-notes: "このインスタンスのノート"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					desktop/views/pages/admin/admin.suspend-user.vue:
 | 
				
			||||||
 | 
					  suspend-user: "ユーザーの凍結"
 | 
				
			||||||
 | 
					  suspend: "凍結"
 | 
				
			||||||
 | 
					  suspended: "凍結しました"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
desktop/views/pages/deck/deck.tl-column.vue:
 | 
					desktop/views/pages/deck/deck.tl-column.vue:
 | 
				
			||||||
  is-media-only: "メディア投稿のみ"
 | 
					  is-media-only: "メディア投稿のみ"
 | 
				
			||||||
  is-media-view: "メディアビュー"
 | 
					  is-media-view: "メディアビュー"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,6 +24,7 @@ import updateBanner from './api/update-banner';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import MkIndex from './views/pages/index.vue';
 | 
					import MkIndex from './views/pages/index.vue';
 | 
				
			||||||
import MkDeck from './views/pages/deck/deck.vue';
 | 
					import MkDeck from './views/pages/deck/deck.vue';
 | 
				
			||||||
 | 
					import MkAdmin from './views/pages/admin/admin.vue';
 | 
				
			||||||
import MkUser from './views/pages/user/user.vue';
 | 
					import MkUser from './views/pages/user/user.vue';
 | 
				
			||||||
import MkFavorites from './views/pages/favorites.vue';
 | 
					import MkFavorites from './views/pages/favorites.vue';
 | 
				
			||||||
import MkSelectDrive from './views/pages/selectdrive.vue';
 | 
					import MkSelectDrive from './views/pages/selectdrive.vue';
 | 
				
			||||||
| 
						 | 
					@ -55,6 +56,7 @@ init(async (launch) => {
 | 
				
			||||||
		routes: [
 | 
							routes: [
 | 
				
			||||||
			{ path: '/', name: 'index', component: MkIndex },
 | 
								{ path: '/', name: 'index', component: MkIndex },
 | 
				
			||||||
			{ path: '/deck', name: 'deck', component: MkDeck },
 | 
								{ path: '/deck', name: 'deck', component: MkDeck },
 | 
				
			||||||
 | 
								{ path: '/admin', name: 'admin', component: MkAdmin },
 | 
				
			||||||
			{ path: '/i/customize-home', component: MkHomeCustomize },
 | 
								{ path: '/i/customize-home', component: MkHomeCustomize },
 | 
				
			||||||
			{ path: '/i/favorites', component: MkFavorites },
 | 
								{ path: '/i/favorites', component: MkFavorites },
 | 
				
			||||||
			{ path: '/i/messaging/:user', component: MkMessagingRoom },
 | 
								{ path: '/i/messaging/:user', component: MkMessagingRoom },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										35
									
								
								src/client/app/desktop/views/pages/admin/admin.dashboard.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/client/app/desktop/views/pages/admin/admin.dashboard.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,35 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
						<h1>%i18n:@dashboard%</h1>
 | 
				
			||||||
 | 
						<p><b>%i18n:@all-users%</b>: <span>{{ stats.usersCount | number }}</span></p>
 | 
				
			||||||
 | 
						<p><b>%i18n:@original-users%</b>: <span>{{ stats.originalUsersCount | number }}</span></p>
 | 
				
			||||||
 | 
						<p><b>%i18n:@all-notes%</b>: <span>{{ stats.notesCount | number }}</span></p>
 | 
				
			||||||
 | 
						<p><b>%i18n:@original-notes%</b>: <span>{{ stats.originalNotesCount | number }}</span></p>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from "vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								stats: null
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						created() {
 | 
				
			||||||
 | 
							(this as any).api('stats').then(stats => {
 | 
				
			||||||
 | 
								this.stats = stats;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
 | 
					h1
 | 
				
			||||||
 | 
						margin 0 0 1em 0
 | 
				
			||||||
 | 
						padding 0 0 8px 0
 | 
				
			||||||
 | 
						font-size 1em
 | 
				
			||||||
 | 
						color #555
 | 
				
			||||||
 | 
						border-bottom solid 1px #eee
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,39 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div>
 | 
				
			||||||
 | 
						<header>%i18n:@suspend-user%</header>
 | 
				
			||||||
 | 
						<input v-model="username"/>
 | 
				
			||||||
 | 
						<button @click="suspendUser" :disabled="suspending">%i18n:@suspend%</button>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from "vue";
 | 
				
			||||||
 | 
					import parseAcct from "../../../../../../misc/acct/parse";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								username: null,
 | 
				
			||||||
 | 
								suspending: false
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							async suspendUser() {
 | 
				
			||||||
 | 
								this.suspending = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const user = await (this as any).os.api(
 | 
				
			||||||
 | 
									"users/show",
 | 
				
			||||||
 | 
									parseAcct(this.username)
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								await (this as any).os.api("admin/suspend-user", {
 | 
				
			||||||
 | 
									userId: user.id
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								this.suspending = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								(this as any).os.apis.dialog({ text: "%i18n:@suspended%" });
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
							
								
								
									
										90
									
								
								src/client/app/desktop/views/pages/admin/admin.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/client/app/desktop/views/pages/admin/admin.vue
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,90 @@
 | 
				
			||||||
 | 
					<template>
 | 
				
			||||||
 | 
					<div class="mk-admin">
 | 
				
			||||||
 | 
						<nav>
 | 
				
			||||||
 | 
							<ul>
 | 
				
			||||||
 | 
								<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:chalkboard .fw%%i18n:@dashboard%</li>
 | 
				
			||||||
 | 
								<!-- <li @click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li> -->
 | 
				
			||||||
 | 
								<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:@drive%</li> -->
 | 
				
			||||||
 | 
								<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> -->
 | 
				
			||||||
 | 
							</ul>
 | 
				
			||||||
 | 
						</nav>
 | 
				
			||||||
 | 
						<main>
 | 
				
			||||||
 | 
							<div v-if="page == 'dashboard'">
 | 
				
			||||||
 | 
								<x-dashboard/>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<div v-if="page == 'users'">
 | 
				
			||||||
 | 
								<x-suspend-user/>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<div v-if="page == 'drive'"></div>
 | 
				
			||||||
 | 
							<div v-if="page == 'update'"></div>
 | 
				
			||||||
 | 
						</main>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
					import Vue from "vue";
 | 
				
			||||||
 | 
					import XDashboard from "./admin.dashboard.vue";
 | 
				
			||||||
 | 
					import XSuspendUser from "./admin.suspend-user.vue";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Vue.extend({
 | 
				
			||||||
 | 
						components: {
 | 
				
			||||||
 | 
							XDashboard,
 | 
				
			||||||
 | 
							XSuspendUser
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						data() {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								page: 'dashboard'
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						methods: {
 | 
				
			||||||
 | 
							nav(page: string) {
 | 
				
			||||||
 | 
								this.page = page;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="stylus" scoped>
 | 
				
			||||||
 | 
					@import '~const.styl'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.mk-admin
 | 
				
			||||||
 | 
						display flex
 | 
				
			||||||
 | 
						height 100%
 | 
				
			||||||
 | 
						margin 32px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> nav
 | 
				
			||||||
 | 
							flex 0 0 250px
 | 
				
			||||||
 | 
							width 100%
 | 
				
			||||||
 | 
							height 100%
 | 
				
			||||||
 | 
							padding 16px 0 0 0
 | 
				
			||||||
 | 
							overflow auto
 | 
				
			||||||
 | 
							border-right solid 1px #ddd
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							> ul
 | 
				
			||||||
 | 
								list-style none
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								> li
 | 
				
			||||||
 | 
									display block
 | 
				
			||||||
 | 
									padding 10px 16px
 | 
				
			||||||
 | 
									margin 0
 | 
				
			||||||
 | 
									color #666
 | 
				
			||||||
 | 
									cursor pointer
 | 
				
			||||||
 | 
									user-select none
 | 
				
			||||||
 | 
									transition margin-left 0.2s ease
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									> [data-fa]
 | 
				
			||||||
 | 
										margin-right 4px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									&:hover
 | 
				
			||||||
 | 
										color #555
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									&.active
 | 
				
			||||||
 | 
										margin-left 8px
 | 
				
			||||||
 | 
										color $theme-color !important
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						> main
 | 
				
			||||||
 | 
							width 100%
 | 
				
			||||||
 | 
							padding 16px 32px
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
import { performance } from 'perf_hooks';
 | 
					import { performance } from 'perf_hooks';
 | 
				
			||||||
import limitter from './limitter';
 | 
					import limitter from './limitter';
 | 
				
			||||||
import { IUser } from '../../models/user';
 | 
					import { IUser, isLocalUser } from '../../models/user';
 | 
				
			||||||
import { IApp } from '../../models/app';
 | 
					import { IApp } from '../../models/app';
 | 
				
			||||||
import endpoints from './endpoints';
 | 
					import endpoints from './endpoints';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,6 +21,10 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
 | 
				
			||||||
		return rej('YOUR_ACCOUNT_HAS_BEEN_SUSPENDED');
 | 
							return rej('YOUR_ACCOUNT_HAS_BEEN_SUSPENDED');
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ep.meta.requireAdmin && !(isLocalUser(user) && user.isAdmin)) {
 | 
				
			||||||
 | 
							return rej('YOU_ARE_NOT_ADMIN');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (app && ep.meta.kind) {
 | 
						if (app && ep.meta.kind) {
 | 
				
			||||||
		if (!app.permission.some(p => p === ep.meta.kind)) {
 | 
							if (!app.permission.some(p => p === ep.meta.kind)) {
 | 
				
			||||||
			return rej('PERMISSION_DENIED');
 | 
								return rej('PERMISSION_DENIED');
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -14,6 +14,11 @@ export interface IEndpointMeta {
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	requireCredential?: boolean;
 | 
						requireCredential?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * 管理者のみ使えるエンドポイントか否か
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						requireAdmin?: boolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * エンドポイントのリミテーションに関するやつ
 | 
						 * エンドポイントのリミテーションに関するやつ
 | 
				
			||||||
	 * 省略した場合はリミテーションは無いものとして解釈されます。
 | 
						 * 省略した場合はリミテーションは無いものとして解釈されます。
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										46
									
								
								src/server/api/endpoints/admin/suspend-user.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								src/server/api/endpoints/admin/suspend-user.ts
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					import $ from 'cafy';
 | 
				
			||||||
 | 
					import ID from '../../../../misc/cafy-id';
 | 
				
			||||||
 | 
					import getParams from '../../get-params';
 | 
				
			||||||
 | 
					import User from '../../../../models/user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const meta = {
 | 
				
			||||||
 | 
					  desc: {
 | 
				
			||||||
 | 
					    ja: '指定したユーザーを凍結します。',
 | 
				
			||||||
 | 
					    en: 'Suspend a user.'
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  requireCredential: true,
 | 
				
			||||||
 | 
					  requireAdmin: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  params: {
 | 
				
			||||||
 | 
					    userId: $.type(ID).note({
 | 
				
			||||||
 | 
					      desc: {
 | 
				
			||||||
 | 
					        ja: '対象のユーザーID',
 | 
				
			||||||
 | 
					        en: 'The user ID which you want to suspend'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default (params: any) => new Promise(async (res, rej) => {
 | 
				
			||||||
 | 
					  const [ps, psErr] = getParams(meta, params);
 | 
				
			||||||
 | 
					  if (psErr) return rej(psErr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const user = await User.findOne({
 | 
				
			||||||
 | 
					    _id: ps.userId
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (user == null) {
 | 
				
			||||||
 | 
					    return rej('user not found');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await User.findOneAndUpdate({
 | 
				
			||||||
 | 
					    _id: user._id
 | 
				
			||||||
 | 
					  }, {
 | 
				
			||||||
 | 
					      $set: {
 | 
				
			||||||
 | 
					        isSuspended: true
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  res();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue