diff --git a/locales/ja.yml b/locales/ja.yml
index 15f3d936de663107a8eaba98ea0c5d2261cce488..580ade0ed34729882d5d6a8937ff4a4092ae75a4 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/paages/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/views/pages/admin/admin.dashboard.vue b/src/client/app/desktop/views/pages/admin/admin.dashboard.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ec43b9384063ecbc884027c64c04b6dc4e12bfe2
--- /dev/null
+++ b/src/client/app/desktop/views/pages/admin/admin.dashboard.vue
@@ -0,0 +1,27 @@
+<template>
+<div>
+	<header>%i18n:@dashboard%</header>
+
+	<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>
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 0000000000000000000000000000000000000000..d47a4795ee3c6f23316ee0484e10fbeecf9007fe
--- /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("%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 0000000000000000000000000000000000000000..03a356c4a0f5c4fd9a87044f7222ad357666780c
--- /dev/null
+++ b/src/client/app/desktop/views/pages/admin/admin.vue
@@ -0,0 +1,35 @@
+<template>
+<div>
+	<nav>
+		<ul>
+			<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%i18n:@dashborad%</li>
+			<li @click="nav('drive')" :class="{ active: page == 'drive' }">%i18n:@drive%</li>
+			<li @click="nav('users')" :class="{ active: page == 'users' }">%i18n:@users%</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 == 'drive'"></div>
+		<div v-if="page == 'users'">
+			<x-suspend-user/>
+		</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
+  }
+});
+</script>
diff --git a/src/server/api/call.ts b/src/server/api/call.ts
index 1d0e8587629f71ce467d935c760dc979a52ac433..e4bb30b695309c098374933d69ca557fefbe2357 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 332a051ae1749594c0588676f6dccc987b421fbf..d4a44070e638c9ad9d39890a487d7d08bb138c48 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 0000000000000000000000000000000000000000..8698120cdb245b854119401a6e49549a0640414d
--- /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();
+});