From ed17636fb9852263b33e37fe9cc6eb20d2aafbc8 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 13 Aug 2020 17:58:16 +0900
Subject: [PATCH] WIP: Improve admin dashboard

---
 locales/ja-JP.yml                      |  1 +
 src/client/components/ui/container.vue | 16 +++++-
 src/client/pages/instance/index.vue    | 67 +++++++++++++++++++++-----
 3 files changed, 71 insertions(+), 13 deletions(-)

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index e241ca9ff..073a49762 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -562,6 +562,7 @@ metrics: "メトリクス"
 overview: "概要"
 logs: "ログ"
 delayed: "遅延"
+database: "データベース"
 
 _sidebar:
   full: "フル"
diff --git a/src/client/components/ui/container.vue b/src/client/components/ui/container.vue
index 41b05544c..247bfdbb9 100644
--- a/src/client/components/ui/container.vue
+++ b/src/client/components/ui/container.vue
@@ -1,6 +1,6 @@
 <template>
-<div class="ukygtjoj _panel" :class="{ naked, hideHeader: !showHeader, scrollable }" v-size="{ max: [380], el: resizeBaseEl }">
-	<header v-if="showHeader">
+<div class="ukygtjoj _panel" :class="{ naked, hideHeader: !showHeader, scrollable, closed: !showBody }" v-size="{ max: [380], el: resizeBaseEl }">
+	<header v-if="showHeader" ref="header">
 		<div class="title"><slot name="header"></slot></div>
 		<slot name="func"></slot>
 		<button class="_button" v-if="bodyTogglable" @click="() => showBody = !showBody">
@@ -62,6 +62,18 @@ export default Vue.extend({
 			faAngleUp, faAngleDown
 		};
 	},
+	mounted() {
+		this.$watch('showBody', showBody => {
+			this.$el.style.minHeight = `${this.$refs.header.offsetHeight}px`;
+			if (showBody) {
+				this.$el.style.flexBasis = `auto`;
+			} else {
+				this.$el.style.flexBasis = `${this.$refs.header.offsetHeight}px`;
+			}
+		}, {
+			immediate: true
+		});
+	},
 	methods: {
 		toggleContent(show: boolean) {
 			if (!this.bodyTogglable) return;
diff --git a/src/client/pages/instance/index.vue b/src/client/pages/instance/index.vue
index de0590781..5b5b8657c 100644
--- a/src/client/pages/instance/index.vue
+++ b/src/client/pages/instance/index.vue
@@ -6,11 +6,11 @@
 	<mk-folder>
 		<template #header><fa :icon="faTachometerAlt"/> {{ $t('overview') }}</template>
 
-		<div class="sboqnrfi">
-			<mk-instance-stats :chart-limit="300" :detailed="true"/>
+		<div class="sboqnrfi" :style="{ gridTemplateRows: overviewHeight }">
+			<mk-instance-stats :chart-limit="300" :detailed="true" class="stats" ref="stats"/>
 
 			<div class="column">
-				<mk-container :body-togglable="false" :resize-base-el="() => $el">
+				<mk-container :body-togglable="true" :resize-base-el="() => $el" class="info">
 					<template #header><fa :icon="faInfoCircle"/>{{ $t('instanceInfo') }}</template>
 
 					<div class="_content">
@@ -22,8 +22,16 @@
 						<div class="_keyValue"><b>Redis</b><span>v{{ serverInfo.redis }}</span></div>
 					</div>
 				</mk-container>
+				
+				<mk-container :body-togglable="true" :scrollable="true" :resize-base-el="() => $el" class="db">
+					<template #header><fa :icon="faDatabase"/>{{ $t('database') }}</template>
 
-				<mkw-federation/>
+					<div class="_content" v-if="dbInfo">
+						<div class="_keyValue" v-for="table in Object.entries(dbInfo)"><b>{{ table[0] }}</b><span>{{ table[1].count | number }}</span><span>{{ table[1].size | bytes }}</span></div>
+					</div>
+				</mk-container>
+
+				<mkw-federation class="fed"/>
 			</div>
 		</div>
 	</mk-folder>
@@ -161,7 +169,7 @@
 
 <script lang="ts">
 import Vue from 'vue';
-import { faServer, faExchangeAlt, faMicrochip, faHdd, faStream, faTrashAlt, faInfoCircle, faExclamationTriangle, faTachometerAlt, faHeartbeat, faClipboardList } from '@fortawesome/free-solid-svg-icons';
+import { faDatabase, faServer, faExchangeAlt, faMicrochip, faHdd, faStream, faTrashAlt, faInfoCircle, faExclamationTriangle, faTachometerAlt, faHeartbeat, faClipboardList } from '@fortawesome/free-solid-svg-icons';
 import Chart from 'chart.js';
 import VueJsonPretty from 'vue-json-pretty';
 import MkInstanceStats from '../../components/instance-stats.vue';
@@ -218,7 +226,9 @@ export default Vue.extend({
 			logLevel: 'all',
 			logDomain: '',
 			modLogs: [],
-			faServer, faExchangeAlt, faMicrochip, faHdd, faStream, faTrashAlt, faInfoCircle, faExclamationTriangle, faTachometerAlt, faHeartbeat, faClipboardList,
+			dbInfo: null,
+			overviewHeight: '1fr',
+			faDatabase, faServer, faExchangeAlt, faMicrochip, faHdd, faStream, faTrashAlt, faInfoCircle, faExclamationTriangle, faTachometerAlt, faHeartbeat, faClipboardList,
 		}
 	},
 
@@ -485,6 +495,20 @@ export default Vue.extend({
 				});
 			});
 		});
+
+		this.$root.api('admin/get-table-stats', {}).then(res => {
+			this.dbInfo = res;
+		});
+
+		this.$nextTick(() => {
+			const ro = new ResizeObserver((entries, observer) => {
+				if (this.$refs.stats) {
+					this.overviewHeight = this.$refs.stats.$el.offsetHeight + 'px';
+				}
+			});
+
+			ro.observe(this.$refs.stats.$el);
+		});
 	},
 
 	beforeDestroy() {
@@ -590,11 +614,32 @@ export default Vue.extend({
 			grid-template-rows: 1fr;
 			gap: 16px 16px;
 
+			> .stats {
+				height: min-content;
+			}
+
 			> .column {
-				display: grid;
-				grid-template-columns: 1fr;
-				grid-template-rows: auto 1fr;
-				gap: 16px 16px;
+				display: flex;
+				flex-direction: column;
+
+				> .info {
+					flex-shrink: 0;
+					flex-grow: 0;
+				}
+
+				> .db {
+					flex: 1;
+					flex-grow: 0;
+				}
+
+				> .fed {
+					flex: 1;
+					flex-grow: 0;
+				}
+
+				> *:not(:last-child) {
+					margin-bottom: var(--margin);
+				}
 			}
 		}
 
@@ -608,7 +653,7 @@ export default Vue.extend({
 		.vkyrmkwb {
 			display: grid;
 			grid-template-columns: 0.5fr 1fr 1fr;
-			grid-template-rows: 385px;
+			grid-template-rows: 400px;
 			gap: 16px 16px;
 		}