Resolve #5063
This commit is contained in:
		
							parent
							
								
									b0280355e8
								
							
						
					
					
						commit
						4f284e1bc0
					
				
					 4 changed files with 98 additions and 13 deletions
				
			
		|  | @ -1226,8 +1226,12 @@ admin/views/index.vue: | |||
|   abuse: "スパム報告" | ||||
|   queue: "ジョブキュー" | ||||
|   logs: "ログ" | ||||
|   db: "データベース" | ||||
|   back-to-misskey: "Misskeyに戻る" | ||||
| 
 | ||||
| admin/views/db.vue: | ||||
|   tables: "テーブル" | ||||
| 
 | ||||
| admin/views/dashboard.vue: | ||||
|   dashboard: "ダッシュボード" | ||||
|   accounts: "アカウント" | ||||
|  |  | |||
							
								
								
									
										39
									
								
								src/client/app/admin/views/db.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/client/app/admin/views/db.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | |||
| <template> | ||||
| <div> | ||||
| 	<ui-card> | ||||
| 		<template #title><fa :icon="faDatabase"/> {{ $t('tables') }}</template> | ||||
| 		<section v-if="tables"> | ||||
| 			<div v-for="table in Object.keys(tables)"><b>{{ table }}</b> {{ tables[table].count }} {{ tables[table].size | bytes }}</div> | ||||
| 		</section> | ||||
| 	</ui-card> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import Vue from 'vue'; | ||||
| import i18n from '../../i18n'; | ||||
| import { faDatabase } from '@fortawesome/free-solid-svg-icons'; | ||||
| 
 | ||||
| export default Vue.extend({ | ||||
| 	i18n: i18n('admin/views/db.vue'), | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			tables: null, | ||||
| 			faDatabase | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	mounted() { | ||||
| 		this.fetch(); | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		fetch() { | ||||
| 			this.$root.api('admin/get-table-stats').then(tables => { | ||||
| 				this.tables = tables; | ||||
| 			}); | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
|  | @ -22,6 +22,7 @@ | |||
| 			<li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>{{ $t('instance') }}</li> | ||||
| 			<li @click="nav('queue')" :class="{ active: page == 'queue' }"><fa :icon="faTasks" fixed-width/>{{ $t('queue') }}</li> | ||||
| 			<li @click="nav('logs')" :class="{ active: page == 'logs' }"><fa :icon="faStream" fixed-width/>{{ $t('logs') }}</li> | ||||
| 			<li @click="nav('db')" :class="{ active: page == 'db' }"><fa :icon="faDatabase" fixed-width/>{{ $t('db') }}</li> | ||||
| 			<li @click="nav('moderators')" :class="{ active: page == 'moderators' }"><fa :icon="faHeadset" fixed-width/>{{ $t('moderators') }}</li> | ||||
| 			<li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>{{ $t('users') }}</li> | ||||
| 			<li @click="nav('drive')" :class="{ active: page == 'drive' }"><fa icon="cloud" fixed-width/>{{ $t('@.drive') }}</li> | ||||
|  | @ -43,6 +44,7 @@ | |||
| 			<div v-if="page == 'instance'"><x-instance/></div> | ||||
| 			<div v-if="page == 'queue'"><x-queue/></div> | ||||
| 			<div v-if="page == 'logs'"><x-logs/></div> | ||||
| 			<div v-if="page == 'db'"><x-db/></div> | ||||
| 			<div v-if="page == 'moderators'"><x-moderators/></div> | ||||
| 			<div v-if="page == 'users'"><x-users/></div> | ||||
| 			<div v-if="page == 'emoji'"><x-emoji/></div> | ||||
|  | @ -59,19 +61,20 @@ | |||
| import Vue from 'vue'; | ||||
| import i18n from '../../i18n'; | ||||
| import { version } from '../../config'; | ||||
| import XDashboard from "./dashboard.vue"; | ||||
| import XInstance from "./instance.vue"; | ||||
| import XQueue from "./queue.vue"; | ||||
| import XLogs from "./logs.vue"; | ||||
| import XModerators from "./moderators.vue"; | ||||
| import XEmoji from "./emoji.vue"; | ||||
| import XAnnouncements from "./announcements.vue"; | ||||
| import XUsers from "./users.vue"; | ||||
| import XDrive from "./drive.vue"; | ||||
| import XAbuse from "./abuse.vue"; | ||||
| import XFederation from "./federation.vue"; | ||||
| import XDashboard from './dashboard.vue'; | ||||
| import XInstance from './instance.vue'; | ||||
| import XQueue from './queue.vue'; | ||||
| import XLogs from './logs.vue'; | ||||
| import XDb from './db.vue'; | ||||
| import XModerators from './moderators.vue'; | ||||
| import XEmoji from './emoji.vue'; | ||||
| import XAnnouncements from './announcements.vue'; | ||||
| import XUsers from './users.vue'; | ||||
| import XDrive from './drive.vue'; | ||||
| import XAbuse from './abuse.vue'; | ||||
| import XFederation from './federation.vue'; | ||||
| 
 | ||||
| import { faHeadset, faArrowLeft, faGlobe, faExclamationCircle, faTasks, faStream } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faHeadset, faArrowLeft, faGlobe, faExclamationCircle, faTasks, faStream, faDatabase } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { faGrin } from '@fortawesome/free-regular-svg-icons'; | ||||
| 
 | ||||
| // Detect the user agent | ||||
|  | @ -85,6 +88,7 @@ export default Vue.extend({ | |||
| 		XInstance, | ||||
| 		XQueue, | ||||
| 		XLogs, | ||||
| 		XDb, | ||||
| 		XModerators, | ||||
| 		XEmoji, | ||||
| 		XAnnouncements, | ||||
|  | @ -108,7 +112,8 @@ export default Vue.extend({ | |||
| 			faGlobe, | ||||
| 			faExclamationCircle, | ||||
| 			faTasks, | ||||
| 			faStream | ||||
| 			faStream, | ||||
| 			faDatabase, | ||||
| 		}; | ||||
| 	}, | ||||
| 	methods: { | ||||
|  |  | |||
							
								
								
									
										37
									
								
								src/server/api/endpoints/admin/get-table-stats.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/server/api/endpoints/admin/get-table-stats.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| import define from '../../define'; | ||||
| import { getConnection } from 'typeorm'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	requireCredential: false, | ||||
| 
 | ||||
| 	desc: { | ||||
| 		'en-US': 'Get table stats' | ||||
| 	}, | ||||
| 
 | ||||
| 	tags: ['meta'], | ||||
| 
 | ||||
| 	params: { | ||||
| 	}, | ||||
| }; | ||||
| 
 | ||||
| export default define(meta, async () => { | ||||
| 	const sizes = await | ||||
| 		getConnection().query(` | ||||
| 			SELECT relname AS "table", reltuples as "count", pg_total_relation_size(C.oid) AS "size" | ||||
| 			FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) | ||||
| 			WHERE nspname NOT IN ('pg_catalog', 'information_schema') | ||||
| 				AND C.relkind <> 'i' | ||||
| 				AND nspname !~ '^pg_toast';`)
 | ||||
| 		.then(recs => { | ||||
| 			const res = {} as Record<string, { count: number; size: number; }>; | ||||
| 			for (const rec of recs) { | ||||
| 				res[rec.table] = { | ||||
| 					count: parseInt(rec.count, 10), | ||||
| 					size: parseInt(rec.size, 10), | ||||
| 				}; | ||||
| 			} | ||||
| 			return res; | ||||
| 		}); | ||||
| 
 | ||||
| 	return sizes; | ||||
| }); | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue