Improve admin page
This commit is contained in:
		| @@ -1089,6 +1089,32 @@ admin/views/dashboard.vue: | |||||||
|   disableRegistration: "Disable new user registration" |   disableRegistration: "Disable new user registration" | ||||||
|   disableLocalTimeline: "Disable the local timeline" |   disableLocalTimeline: "Disable the local timeline" | ||||||
|  |  | ||||||
|  | admin/views/charts.vue: | ||||||
|  |   title: "チャート" | ||||||
|  |   per-day: "1日ごと" | ||||||
|  |   per-hour: "1時間ごと" | ||||||
|  |   federation: "フェデレーション" | ||||||
|  |   notes: "投稿" | ||||||
|  |   users: "ユーザー" | ||||||
|  |   drive: "ドライブ" | ||||||
|  |   network: "ネットワーク" | ||||||
|  |   charts: | ||||||
|  |     federation-instances: "インスタンスの増減" | ||||||
|  |     federation-instances-total: "インスタンスの積算" | ||||||
|  |     notes: "投稿の増減 (統合)" | ||||||
|  |     local-notes: "投稿の増減 (ローカル)" | ||||||
|  |     remote-notes: "投稿の増減 (リモート)" | ||||||
|  |     notes-total: "投稿の積算" | ||||||
|  |     users: "ユーザーの増減" | ||||||
|  |     users-total: "ユーザーの積算" | ||||||
|  |     drive: "ドライブ使用量の増減" | ||||||
|  |     drive-total: "ドライブ使用量の積算" | ||||||
|  |     drive-files: "ドライブのファイル数の増減" | ||||||
|  |     drive-files-total: "ドライブのファイル数の積算" | ||||||
|  |     network-requests: "リクエスト" | ||||||
|  |     network-time: "応答時間" | ||||||
|  |     network-usage: "通信量" | ||||||
|  |  | ||||||
| admin/views/users.vue: | admin/views/users.vue: | ||||||
|   suspend-user: "ユーザーの凍結" |   suspend-user: "ユーザーの凍結" | ||||||
|   suspend: "凍結" |   suspend: "凍結" | ||||||
|   | |||||||
							
								
								
									
										429
									
								
								src/client/app/admin/views/charts.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										429
									
								
								src/client/app/admin/views/charts.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,429 @@ | |||||||
|  | <template> | ||||||
|  | <div class="qvgidhudpqhjttdhxubzuyrhyzgslujw"> | ||||||
|  | 	<header> | ||||||
|  | 		<b>%i18n:@title%:</b> | ||||||
|  | 		<select v-model="src"> | ||||||
|  | 			<optgroup label="%i18n:@federation%"> | ||||||
|  | 				<option value="federation-instances">%i18n:@charts.federation-instances%</option> | ||||||
|  | 				<option value="federation-instances-total">%i18n:@charts.federation-instances-total%</option> | ||||||
|  | 			</optgroup> | ||||||
|  | 			<optgroup label="%i18n:@users%"> | ||||||
|  | 				<option value="users">%i18n:@charts.users%</option> | ||||||
|  | 				<option value="users-total">%i18n:@charts.users-total%</option> | ||||||
|  | 			</optgroup> | ||||||
|  | 			<optgroup label="%i18n:@notes%"> | ||||||
|  | 				<option value="notes">%i18n:@charts.notes%</option> | ||||||
|  | 				<option value="local-notes">%i18n:@charts.local-notes%</option> | ||||||
|  | 				<option value="remote-notes">%i18n:@charts.remote-notes%</option> | ||||||
|  | 				<option value="notes-total">%i18n:@charts.notes-total%</option> | ||||||
|  | 			</optgroup> | ||||||
|  | 			<optgroup label="%i18n:@drive%"> | ||||||
|  | 				<option value="drive-files">%i18n:@charts.drive-files%</option> | ||||||
|  | 				<option value="drive-files-total">%i18n:@charts.drive-files-total%</option> | ||||||
|  | 				<option value="drive">%i18n:@charts.drive%</option> | ||||||
|  | 				<option value="drive-total">%i18n:@charts.drive-total%</option> | ||||||
|  | 			</optgroup> | ||||||
|  | 			<optgroup label="%i18n:@network%"> | ||||||
|  | 				<option value="network-requests">%i18n:@charts.network-requests%</option> | ||||||
|  | 				<option value="network-time">%i18n:@charts.network-time%</option> | ||||||
|  | 				<option value="network-usage">%i18n:@charts.network-usage%</option> | ||||||
|  | 			</optgroup> | ||||||
|  | 		</select> | ||||||
|  | 		<div> | ||||||
|  | 			<span @click="span = 'day'" :class="{ active: span == 'day' }">%i18n:@per-day%</span> | <span @click="span = 'hour'" :class="{ active: span == 'hour' }">%i18n:@per-hour%</span> | ||||||
|  | 		</div> | ||||||
|  | 	</header> | ||||||
|  | 	<div ref="chart"></div> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script lang="ts"> | ||||||
|  | import Vue from 'vue'; | ||||||
|  | import * as ApexCharts from 'apexcharts'; | ||||||
|  |  | ||||||
|  | const limit = 60; | ||||||
|  |  | ||||||
|  | const sum = (...arr) => arr.reduce((r, a) => r.map((b, i) => a[i] + b)); | ||||||
|  | const negate = arr => arr.map(x => -x); | ||||||
|  |  | ||||||
|  | export default Vue.extend({ | ||||||
|  | 	data() { | ||||||
|  | 		return { | ||||||
|  | 			chart: null, | ||||||
|  | 			src: 'notes', | ||||||
|  | 			span: 'hour', | ||||||
|  | 			chartInstance: null | ||||||
|  | 		}; | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	computed: { | ||||||
|  | 		data(): any { | ||||||
|  | 			if (this.chart == null) return null; | ||||||
|  | 			switch (this.src) { | ||||||
|  | 				case 'federation-instances': return this.federationInstancesChart(false); | ||||||
|  | 				case 'federation-instances-total': return this.federationInstancesChart(true); | ||||||
|  | 				case 'users': return this.usersChart(false); | ||||||
|  | 				case 'users-total': return this.usersChart(true); | ||||||
|  | 				case 'notes': return this.notesChart('combined'); | ||||||
|  | 				case 'local-notes': return this.notesChart('local'); | ||||||
|  | 				case 'remote-notes': return this.notesChart('remote'); | ||||||
|  | 				case 'notes-total': return this.notesTotalChart(); | ||||||
|  | 				case 'drive': return this.driveChart(); | ||||||
|  | 				case 'drive-total': return this.driveTotalChart(); | ||||||
|  | 				case 'drive-files': return this.driveFilesChart(); | ||||||
|  | 				case 'drive-files-total': return this.driveFilesTotalChart(); | ||||||
|  | 				case 'network-requests': return this.networkRequestsChart(); | ||||||
|  | 				case 'network-time': return this.networkTimeChart(); | ||||||
|  | 				case 'network-usage': return this.networkUsageChart(); | ||||||
|  | 			} | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		stats(): any[] { | ||||||
|  | 			const stats = | ||||||
|  | 				this.span == 'day' ? this.chart.perDay : | ||||||
|  | 				this.span == 'hour' ? this.chart.perHour : | ||||||
|  | 				null; | ||||||
|  |  | ||||||
|  | 			return stats; | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	watch: { | ||||||
|  | 		src() { | ||||||
|  | 			this.render(); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		span() { | ||||||
|  | 			this.render(); | ||||||
|  | 		} | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	async mounted() { | ||||||
|  | 		this.now = new Date(); | ||||||
|  |  | ||||||
|  | 		const [perHour, perDay] = await Promise.all([Promise.all([ | ||||||
|  | 			(this as any).api('charts/federation', { limit: limit, span: 'hour' }), | ||||||
|  | 			(this as any).api('charts/users', { limit: limit, span: 'hour' }), | ||||||
|  | 			(this as any).api('charts/notes', { limit: limit, span: 'hour' }), | ||||||
|  | 			(this as any).api('charts/drive', { limit: limit, span: 'hour' }), | ||||||
|  | 			(this as any).api('charts/network', { limit: limit, span: 'hour' }) | ||||||
|  | 		]), Promise.all([ | ||||||
|  | 			(this as any).api('charts/federation', { limit: limit, span: 'day' }), | ||||||
|  | 			(this as any).api('charts/users', { limit: limit, span: 'day' }), | ||||||
|  | 			(this as any).api('charts/notes', { limit: limit, span: 'day' }), | ||||||
|  | 			(this as any).api('charts/drive', { limit: limit, span: 'day' }), | ||||||
|  | 			(this as any).api('charts/network', { limit: limit, span: 'day' }) | ||||||
|  | 		])]); | ||||||
|  |  | ||||||
|  | 		const chart = { | ||||||
|  | 			perHour: { | ||||||
|  | 				federation: perHour[0], | ||||||
|  | 				users: perHour[1], | ||||||
|  | 				notes: perHour[2], | ||||||
|  | 				drive: perHour[3], | ||||||
|  | 				network: perHour[4] | ||||||
|  | 			}, | ||||||
|  | 			perDay: { | ||||||
|  | 				federation: perDay[0], | ||||||
|  | 				users: perDay[1], | ||||||
|  | 				notes: perDay[2], | ||||||
|  | 				drive: perDay[3], | ||||||
|  | 				network: perDay[4] | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		this.chart = chart; | ||||||
|  |  | ||||||
|  | 		this.render(); | ||||||
|  | 	}, | ||||||
|  |  | ||||||
|  | 	methods: { | ||||||
|  | 		render() { | ||||||
|  | 			if (this.chartInstance) { | ||||||
|  | 				this.chartInstance.destroy(); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			this.chartInstance = new ApexCharts(this.$refs.chart, Object.assign({ | ||||||
|  | 				chart: { | ||||||
|  | 					type: 'area', | ||||||
|  | 					height: 300, | ||||||
|  | 					animations: { | ||||||
|  | 						dynamicAnimation: { | ||||||
|  | 							enabled: false | ||||||
|  | 						} | ||||||
|  | 					}, | ||||||
|  | 					toolbar: { | ||||||
|  | 						show: false | ||||||
|  | 					} | ||||||
|  | 				}, | ||||||
|  | 				dataLabels: { | ||||||
|  | 					enabled: false | ||||||
|  | 				}, | ||||||
|  | 				grid: { | ||||||
|  | 					clipMarkers: false, | ||||||
|  | 				}, | ||||||
|  | 				stroke: { | ||||||
|  | 					curve: 'straight', | ||||||
|  | 					width: 2 | ||||||
|  | 				}, | ||||||
|  | 				xaxis: { | ||||||
|  | 					type: 'datetime' | ||||||
|  | 				}, | ||||||
|  | 				yaxis: { | ||||||
|  | 				} | ||||||
|  | 			}, this.data)); | ||||||
|  |  | ||||||
|  | 			this.chartInstance.render(); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		getDate(i: number) { | ||||||
|  | 			const y = this.now.getFullYear(); | ||||||
|  | 			const m = this.now.getMonth(); | ||||||
|  | 			const d = this.now.getDate(); | ||||||
|  | 			const h = this.now.getHours(); | ||||||
|  |  | ||||||
|  | 			return ( | ||||||
|  | 				this.span == 'day' ? new Date(y, m, d - i) : | ||||||
|  | 				this.span == 'hour' ? new Date(y, m, d, h - i) : | ||||||
|  | 				null | ||||||
|  | 			); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		format(arr) { | ||||||
|  | 			return arr.map((v, i) => ({ x: this.getDate(i).getTime(), y: v })); | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		federationInstancesChart(total: boolean): any { | ||||||
|  | 			return { | ||||||
|  | 				series: [{ | ||||||
|  | 					data: this.format(total | ||||||
|  | 						? this.stats.federation.instance.total | ||||||
|  | 						: sum(this.stats.federation.instance.inc, negate(this.stats.federation.instance.dec)) | ||||||
|  | 					) | ||||||
|  | 				}] | ||||||
|  | 			}; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		notesChart(type: string): any { | ||||||
|  | 			return { | ||||||
|  | 				series: [{ | ||||||
|  | 					name: 'All', | ||||||
|  | 					data: this.format(type == 'combined' | ||||||
|  | 						? sum(this.stats.notes.local.inc, negate(this.stats.notes.local.dec), this.stats.notes.remote.inc, negate(this.stats.notes.remote.dec)) | ||||||
|  | 						: sum(this.stats.notes[type].inc, negate(this.stats.notes[type].dec)) | ||||||
|  | 					) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Renotes', | ||||||
|  | 					data: this.format(type == 'combined' | ||||||
|  | 						? sum(this.stats.notes.local.diffs.renote, this.stats.notes.remote.diffs.renote) | ||||||
|  | 						: this.stats.notes[type].diffs.renote | ||||||
|  | 					) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Replies', | ||||||
|  | 					data: this.format(type == 'combined' | ||||||
|  | 						? sum(this.stats.notes.local.diffs.reply, this.stats.notes.remote.diffs.reply) | ||||||
|  | 						: this.stats.notes[type].diffs.reply | ||||||
|  | 					) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Normal', | ||||||
|  | 					data: this.format(type == 'combined' | ||||||
|  | 						? sum(this.stats.notes.local.diffs.normal, this.stats.notes.remote.diffs.normal) | ||||||
|  | 						: this.stats.notes[type].diffs.normal | ||||||
|  | 					) | ||||||
|  | 				}] | ||||||
|  | 			}; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		notesTotalChart(): any { | ||||||
|  | 			return { | ||||||
|  | 				series: [{ | ||||||
|  | 					name: 'Combined', | ||||||
|  | 					data: this.format(sum(this.stats.notes.local.total, this.stats.notes.remote.total)) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Local', | ||||||
|  | 					data: this.format(this.stats.notes.local.total) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Remote', | ||||||
|  | 					data: this.format(this.stats.notes.remote.total) | ||||||
|  | 				}] | ||||||
|  | 			}; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		usersChart(total: boolean): any { | ||||||
|  | 			return { | ||||||
|  | 				series: [{ | ||||||
|  | 					name: 'Combined', | ||||||
|  | 					data: this.format(total | ||||||
|  | 						? sum(this.stats.users.local.total, this.stats.users.remote.total) | ||||||
|  | 						: sum(this.stats.users.local.inc, negate(this.stats.users.local.dec), this.stats.users.remote.inc, negate(this.stats.users.remote.dec)) | ||||||
|  | 					) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Local', | ||||||
|  | 					data: this.format(total | ||||||
|  | 						? this.stats.users.local.total | ||||||
|  | 						: sum(this.stats.users.local.inc, negate(this.stats.users.local.dec)) | ||||||
|  | 					) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Remote', | ||||||
|  | 					data: this.format(total | ||||||
|  | 						? this.stats.users.remote.total | ||||||
|  | 						: sum(this.stats.users.remote.inc, negate(this.stats.users.remote.dec)) | ||||||
|  | 					) | ||||||
|  | 				}] | ||||||
|  | 			}; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		driveChart(): any { | ||||||
|  | 			return { | ||||||
|  | 				series: [{ | ||||||
|  | 					name: 'All', | ||||||
|  | 					data: this.format( | ||||||
|  | 						sum( | ||||||
|  | 							this.stats.drive.local.incSize, | ||||||
|  | 							negate(this.stats.drive.local.decSize), | ||||||
|  | 							this.stats.drive.remote.incSize, | ||||||
|  | 							negate(this.stats.drive.remote.decSize) | ||||||
|  | 						) | ||||||
|  | 					) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Local +', | ||||||
|  | 					data: this.format(this.stats.drive.local.incSize) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Local -', | ||||||
|  | 					data: this.format(negate(this.stats.drive.local.decSize)) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Remote +', | ||||||
|  | 					data: this.format(this.stats.drive.remote.incSize) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Remote -', | ||||||
|  | 					data: this.format(negate(this.stats.drive.remote.decSize)) | ||||||
|  | 				}] | ||||||
|  | 			}; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		driveTotalChart(): any { | ||||||
|  | 			return { | ||||||
|  | 				series: [{ | ||||||
|  | 					name: 'Combined', | ||||||
|  | 					data: this.format(sum(this.stats.drive.local.totalSize, this.stats.drive.remote.totalSize)) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Local', | ||||||
|  | 					data: this.format(this.stats.drive.local.totalSize) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Remote', | ||||||
|  | 					data: this.format(this.stats.drive.remote.totalSize) | ||||||
|  | 				}] | ||||||
|  | 			}; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		driveFilesChart(): any { | ||||||
|  | 			return { | ||||||
|  | 				series: [{ | ||||||
|  | 					name: 'All', | ||||||
|  | 					data: this.format( | ||||||
|  | 						sum( | ||||||
|  | 							this.stats.drive.local.incCount, | ||||||
|  | 							negate(this.stats.drive.local.decCount), | ||||||
|  | 							this.stats.drive.remote.incCount, | ||||||
|  | 							negate(this.stats.drive.remote.decCount) | ||||||
|  | 						) | ||||||
|  | 					) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Local +', | ||||||
|  | 					data: this.format(this.stats.drive.local.incCount) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Local -', | ||||||
|  | 					data: this.format(negate(this.stats.drive.local.decCount)) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Remote +', | ||||||
|  | 					data: this.format(this.stats.drive.remote.incCount) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Remote -', | ||||||
|  | 					data: this.format(negate(this.stats.drive.remote.decCount)) | ||||||
|  | 				}] | ||||||
|  | 			}; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		driveFilesTotalChart(): any { | ||||||
|  | 			return { | ||||||
|  | 				series: [{ | ||||||
|  | 					name: 'Combined', | ||||||
|  | 					data: this.format(sum(this.stats.drive.local.totalCount, this.stats.drive.remote.totalCount)) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Local', | ||||||
|  | 					data: this.format(this.stats.drive.local.totalCount) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Remote', | ||||||
|  | 					data: this.format(this.stats.drive.remote.totalCount) | ||||||
|  | 				}] | ||||||
|  | 			}; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		networkRequestsChart(): any { | ||||||
|  | 			return { | ||||||
|  | 				series: [{ | ||||||
|  | 					name: 'Incoming', | ||||||
|  | 					data: this.format(this.stats.network.incomingRequests) | ||||||
|  | 				}] | ||||||
|  | 			}; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		networkTimeChart(): any { | ||||||
|  | 			const data = []; | ||||||
|  |  | ||||||
|  | 			for (let i = 0; i < limit; i++) { | ||||||
|  | 				data.push(this.stats.network.incomingRequests[i] != 0 ? (this.stats.network.totalTime[i] / this.stats.network.incomingRequests[i]) : 0); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			return { | ||||||
|  | 				series: [{ | ||||||
|  | 					name: 'Avg time', | ||||||
|  | 					data: this.format(data) | ||||||
|  | 				}] | ||||||
|  | 			}; | ||||||
|  | 		}, | ||||||
|  |  | ||||||
|  | 		networkUsageChart(): any { | ||||||
|  | 			return { | ||||||
|  | 				series: [{ | ||||||
|  | 					name: 'Incoming', | ||||||
|  | 					data: this.format(this.stats.network.incomingBytes) | ||||||
|  | 				}, { | ||||||
|  | 					name: 'Outgoing', | ||||||
|  | 					data: this.format(this.stats.network.outgoingBytes) | ||||||
|  | 				}] | ||||||
|  | 			}; | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="stylus" scoped> | ||||||
|  | .qvgidhudpqhjttdhxubzuyrhyzgslujw | ||||||
|  | 	display block | ||||||
|  | 	flex 1 | ||||||
|  | 	padding 32px | ||||||
|  | 	padding-bottom 8px | ||||||
|  | 	box-shadow 0 2px 4px rgba(0, 0, 0, 0.1) | ||||||
|  | 	background var(--face) | ||||||
|  | 	border-radius 8px | ||||||
|  |  | ||||||
|  | 	> header | ||||||
|  | 		display flex | ||||||
|  | 		padding 0 0 8px 0 | ||||||
|  | 		font-size 1em | ||||||
|  | 		color #555 | ||||||
|  | 		border-bottom solid 1px #eee | ||||||
|  |  | ||||||
|  | 		> b | ||||||
|  | 			margin-right 8px | ||||||
|  |  | ||||||
|  | 		> *:last-child | ||||||
|  | 			margin-left auto | ||||||
|  |  | ||||||
|  | 			* | ||||||
|  | 				&:not(.active) | ||||||
|  | 					color var(--primary) | ||||||
|  | 					cursor pointer | ||||||
|  |  | ||||||
|  | </style> | ||||||
| @@ -51,6 +51,10 @@ | |||||||
| 		</div> | 		</div> | ||||||
| 	</div> | 	</div> | ||||||
|  |  | ||||||
|  | 	<div class="charts"> | ||||||
|  | 		<x-charts/> | ||||||
|  | 	</div> | ||||||
|  |  | ||||||
| 	<div class="cpu-memory"> | 	<div class="cpu-memory"> | ||||||
| 		<x-cpu-memory :connection="connection"/> | 		<x-cpu-memory :connection="connection"/> | ||||||
| 	</div> | 	</div> | ||||||
| @@ -60,10 +64,12 @@ | |||||||
| <script lang="ts"> | <script lang="ts"> | ||||||
| import Vue from "vue"; | import Vue from "vue"; | ||||||
| import XCpuMemory from "./cpu-memory.vue"; | import XCpuMemory from "./cpu-memory.vue"; | ||||||
|  | import XCharts from "./charts.vue"; | ||||||
|  |  | ||||||
| export default Vue.extend({ | export default Vue.extend({ | ||||||
| 	components: { | 	components: { | ||||||
| 		XCpuMemory | 		XCpuMemory, | ||||||
|  | 		XCharts | ||||||
| 	}, | 	}, | ||||||
| 	data() { | 	data() { | ||||||
| 		return { | 		return { | ||||||
| @@ -144,6 +150,9 @@ export default Vue.extend({ | |||||||
| 					font-size 70% | 					font-size 70% | ||||||
| 					opacity 0.7 | 					opacity 0.7 | ||||||
|  |  | ||||||
|  | 	> .charts | ||||||
|  | 		margin-bottom 16px | ||||||
|  |  | ||||||
| 	> .cpu-memory | 	> .cpu-memory | ||||||
| 		margin-bottom 16px | 		margin-bottom 16px | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 syuilo
					syuilo