diff --git a/locales/ja.yml b/locales/ja.yml
index 15f3d936de..08e437fbf7 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -897,6 +897,24 @@ desktop/views/components/window.vue:
   popout: "ポップアウト"
   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:
   is-media-only: "メディア投稿のみ"
   is-media-view: "メディアビュー"
diff --git a/src/client/app/desktop/script.ts b/src/client/app/desktop/script.ts
index 8175ce9b66..8dc0482191 100644
--- a/src/client/app/desktop/script.ts
+++ b/src/client/app/desktop/script.ts
@@ -24,6 +24,7 @@ import updateBanner from './api/update-banner';
 
 import MkIndex from './views/pages/index.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 MkFavorites from './views/pages/favorites.vue';
 import MkSelectDrive from './views/pages/selectdrive.vue';
@@ -55,6 +56,7 @@ init(async (launch) => {
 		routes: [
 			{ path: '/', name: 'index', component: MkIndex },
 			{ path: '/deck', name: 'deck', component: MkDeck },
+			{ path: '/admin', name: 'admin', component: MkAdmin },
 			{ path: '/i/customize-home', component: MkHomeCustomize },
 			{ path: '/i/favorites', component: MkFavorites },
 			{ path: '/i/messaging/:user', component: MkMessagingRoom },
diff --git a/src/client/app/desktop/views/pages/admin/admin.dashboard.vue b/src/client/app/desktop/views/pages/admin/admin.dashboard.vue
new file mode 100644
index 0000000000..4e6ada6cdb
--- /dev/null
+++ b/src/client/app/desktop/views/pages/admin/admin.dashboard.vue
@@ -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>
diff --git a/src/client/app/desktop/views/pages/admin/admin.suspend-user.vue b/src/client/app/desktop/views/pages/admin/admin.suspend-user.vue
new file mode 100644
index 0000000000..01d0301eb1
--- /dev/null
+++ b/src/client/app/desktop/views/pages/admin/admin.suspend-user.vue
@@ -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>
diff --git a/src/client/app/desktop/views/pages/admin/admin.vue b/src/client/app/desktop/views/pages/admin/admin.vue
new file mode 100644
index 0000000000..a2b8d47ff6
--- /dev/null
+++ b/src/client/app/desktop/views/pages/admin/admin.vue
@@ -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>
diff --git a/src/server/api/call.ts b/src/server/api/call.ts
index 1d0e858762..e4bb30b695 100644
--- a/src/server/api/call.ts
+++ b/src/server/api/call.ts
@@ -1,6 +1,6 @@
 import { performance } from 'perf_hooks';
 import limitter from './limitter';
-import { IUser } from '../../models/user';
+import { IUser, isLocalUser } from '../../models/user';
 import { IApp } from '../../models/app';
 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');
 	}
 
+	if (ep.meta.requireAdmin && !(isLocalUser(user) && user.isAdmin)) {
+		return rej('YOU_ARE_NOT_ADMIN');
+	}
+
 	if (app && ep.meta.kind) {
 		if (!app.permission.some(p => p === ep.meta.kind)) {
 			return rej('PERMISSION_DENIED');
@@ -53,7 +57,7 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
 		const time = after - before;
 
 		if (time > 1000) {
-			console.warn(`SLOW API CALL DETECTED: ${ep.name} (${ time }ms)`);
+			console.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`);
 		}
 	} catch (e) {
 		rej(e);
diff --git a/src/server/api/endpoints.ts b/src/server/api/endpoints.ts
index 332a051ae1..d4a44070e6 100644
--- a/src/server/api/endpoints.ts
+++ b/src/server/api/endpoints.ts
@@ -14,6 +14,11 @@ export interface IEndpointMeta {
 	 */
 	requireCredential?: boolean;
 
+	/**
+	 * 管理者のみ使えるエンドポイントか否か
+	 */
+	requireAdmin?: boolean;
+
 	/**
 	 * エンドポイントのリミテーションに関するやつ
 	 * 省略した場合はリミテーションは無いものとして解釈されます。
diff --git a/src/server/api/endpoints/admin/suspend-user.ts b/src/server/api/endpoints/admin/suspend-user.ts
new file mode 100644
index 0000000000..8698120cdb
--- /dev/null
+++ b/src/server/api/endpoints/admin/suspend-user.ts
@@ -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();
+});