サーバー情報ウィジェット
This commit is contained in:
		
							parent
							
								
									e74a47916d
								
							
						
					
					
						commit
						c6fe798092
					
				
					 12 changed files with 757 additions and 8 deletions
				
			
		|  | @ -1030,6 +1030,7 @@ _widgets: | |||
|   slideshow: "スライドショー" | ||||
|   button: "ボタン" | ||||
|   onlineUsers: "オンラインユーザー" | ||||
|   serverMetric: "サーバーメトリクス" | ||||
| 
 | ||||
| _cw: | ||||
|   hide: "隠す" | ||||
|  |  | |||
|  | @ -36,7 +36,7 @@ import { faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons'; | |||
| import { faDownload, faLink, faICursor, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; | ||||
| import copyToClipboard from '@/scripts/copy-to-clipboard'; | ||||
| import MkDriveFileThumbnail from './drive-file-thumbnail.vue'; | ||||
| import bytes from '../filters/bytes'; | ||||
| import bytes from '@/filters/bytes'; | ||||
| import * as os from '@/os'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
|  |  | |||
|  | @ -14,6 +14,7 @@ export default function(app: App) { | |||
| 	app.component('MkwFederation', defineAsyncComponent(() => import('./federation.vue'))); | ||||
| 	app.component('MkwPostForm', defineAsyncComponent(() => import('./post-form.vue'))); | ||||
| 	app.component('MkwSlideshow', defineAsyncComponent(() => import('./slideshow.vue'))); | ||||
| 	app.component('MkwServerMetric', defineAsyncComponent(() => import('./server-metric/index.vue'))); | ||||
| 	app.component('MkwOnlineUsers', defineAsyncComponent(() => import('./online-users.vue'))); | ||||
| 	app.component('MkwButton', defineAsyncComponent(() => import('./button.vue'))); | ||||
| } | ||||
|  | @ -32,6 +33,7 @@ export const widgets = [ | |||
| 	'federation', | ||||
| 	'postForm', | ||||
| 	'slideshow', | ||||
| 	'serverMetric', | ||||
| 	'onlineUsers', | ||||
| 	'button', | ||||
| ]; | ||||
|  |  | |||
							
								
								
									
										174
									
								
								src/client/widgets/server-metric/cpu-mem.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								src/client/widgets/server-metric/cpu-mem.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,174 @@ | |||
| <template> | ||||
| <div class="lcfyofjk"> | ||||
| 	<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`"> | ||||
| 		<defs> | ||||
| 			<linearGradient :id="cpuGradientId" x1="0" x2="0" y1="1" y2="0"> | ||||
| 				<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop> | ||||
| 				<stop offset="100%" stop-color="hsl(0, 80%, 70%)"></stop> | ||||
| 			</linearGradient> | ||||
| 			<mask :id="cpuMaskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY"> | ||||
| 				<polygon | ||||
| 					:points="cpuPolygonPoints" | ||||
| 					fill="#fff" | ||||
| 					fill-opacity="0.5" | ||||
| 				/> | ||||
| 				<polyline | ||||
| 					:points="cpuPolylinePoints" | ||||
| 					fill="none" | ||||
| 					stroke="#fff" | ||||
| 					stroke-width="1" | ||||
| 				/> | ||||
| 				<circle | ||||
| 					:cx="cpuHeadX" | ||||
| 					:cy="cpuHeadY" | ||||
| 					r="1.5" | ||||
| 					fill="#fff" | ||||
| 				/> | ||||
| 			</mask> | ||||
| 		</defs> | ||||
| 		<rect | ||||
| 			x="-2" y="-2" | ||||
| 			:width="viewBoxX + 4" :height="viewBoxY + 4" | ||||
| 			:style="`stroke: none; fill: url(#${ cpuGradientId }); mask: url(#${ cpuMaskId })`" | ||||
| 		/> | ||||
| 		<text x="1" y="5">CPU <tspan>{{ cpuP }}%</tspan></text> | ||||
| 	</svg> | ||||
| 	<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`"> | ||||
| 		<defs> | ||||
| 			<linearGradient :id="memGradientId" x1="0" x2="0" y1="1" y2="0"> | ||||
| 				<stop offset="0%" stop-color="hsl(180, 80%, 70%)"></stop> | ||||
| 				<stop offset="100%" stop-color="hsl(0, 80%, 70%)"></stop> | ||||
| 			</linearGradient> | ||||
| 			<mask :id="memMaskId" x="0" y="0" :width="viewBoxX" :height="viewBoxY"> | ||||
| 				<polygon | ||||
| 					:points="memPolygonPoints" | ||||
| 					fill="#fff" | ||||
| 					fill-opacity="0.5" | ||||
| 				/> | ||||
| 				<polyline | ||||
| 					:points="memPolylinePoints" | ||||
| 					fill="none" | ||||
| 					stroke="#fff" | ||||
| 					stroke-width="1" | ||||
| 				/> | ||||
| 				<circle | ||||
| 					:cx="memHeadX" | ||||
| 					:cy="memHeadY" | ||||
| 					r="1.5" | ||||
| 					fill="#fff" | ||||
| 				/> | ||||
| 			</mask> | ||||
| 		</defs> | ||||
| 		<rect | ||||
| 			x="-2" y="-2" | ||||
| 			:width="viewBoxX + 4" :height="viewBoxY + 4" | ||||
| 			:style="`stroke: none; fill: url(#${ memGradientId }); mask: url(#${ memMaskId })`" | ||||
| 		/> | ||||
| 		<text x="1" y="5">MEM <tspan>{{ memP }}%</tspan></text> | ||||
| 	</svg> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { v4 as uuid } from 'uuid'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| 		connection: { | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		meta: { | ||||
| 			required: true, | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			viewBoxX: 50, | ||||
| 			viewBoxY: 30, | ||||
| 			stats: [], | ||||
| 			cpuGradientId: uuid(), | ||||
| 			cpuMaskId: uuid(), | ||||
| 			memGradientId: uuid(), | ||||
| 			memMaskId: uuid(), | ||||
| 			cpuPolylinePoints: '', | ||||
| 			memPolylinePoints: '', | ||||
| 			cpuPolygonPoints: '', | ||||
| 			memPolygonPoints: '', | ||||
| 			cpuHeadX: null, | ||||
| 			cpuHeadY: null, | ||||
| 			memHeadX: null, | ||||
| 			memHeadY: null, | ||||
| 			cpuP: '', | ||||
| 			memP: '' | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.connection.on('stats', this.onStats); | ||||
| 		this.connection.on('statsLog', this.onStatsLog); | ||||
| 		this.connection.send('requestLog', { | ||||
| 			id: Math.random().toString().substr(2, 8) | ||||
| 		}); | ||||
| 	}, | ||||
| 	beforeUnmount() { | ||||
| 		this.connection.off('stats', this.onStats); | ||||
| 		this.connection.off('statsLog', this.onStatsLog); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		onStats(stats) { | ||||
| 			this.stats.push(stats); | ||||
| 			if (this.stats.length > 50) this.stats.shift(); | ||||
| 
 | ||||
| 			const cpuPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - s.cpu) * this.viewBoxY]); | ||||
| 			const memPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.mem.used / this.meta.mem.total)) * this.viewBoxY]); | ||||
| 			this.cpuPolylinePoints = cpuPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' '); | ||||
| 			this.memPolylinePoints = memPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' '); | ||||
| 
 | ||||
| 			this.cpuPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.cpuPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`; | ||||
| 			this.memPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.memPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`; | ||||
| 
 | ||||
| 			this.cpuHeadX = cpuPolylinePoints[cpuPolylinePoints.length - 1][0]; | ||||
| 			this.cpuHeadY = cpuPolylinePoints[cpuPolylinePoints.length - 1][1]; | ||||
| 			this.memHeadX = memPolylinePoints[memPolylinePoints.length - 1][0]; | ||||
| 			this.memHeadY = memPolylinePoints[memPolylinePoints.length - 1][1]; | ||||
| 
 | ||||
| 			this.cpuP = (stats.cpu * 100).toFixed(0); | ||||
| 			this.memP = (stats.mem.used / this.meta.mem.total * 100).toFixed(0); | ||||
| 		}, | ||||
| 		onStatsLog(statsLog) { | ||||
| 			for (const stats of [...statsLog].reverse()) { | ||||
| 				this.onStats(stats); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .lcfyofjk { | ||||
| 	display: flex; | ||||
| 
 | ||||
| 	> svg { | ||||
| 		display: block; | ||||
| 		padding: 10px; | ||||
| 		width: 50%; | ||||
| 
 | ||||
| 		&:first-child { | ||||
| 			padding-right: 5px; | ||||
| 		} | ||||
| 
 | ||||
| 		&:last-child { | ||||
| 			padding-left: 5px; | ||||
| 		} | ||||
| 
 | ||||
| 		> text { | ||||
| 			font-size: 5px; | ||||
| 			fill: currentColor; | ||||
| 
 | ||||
| 			> tspan { | ||||
| 				opacity: 0.5; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										78
									
								
								src/client/widgets/server-metric/cpu.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/client/widgets/server-metric/cpu.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,78 @@ | |||
| <template> | ||||
| <div class="vrvdvrys"> | ||||
| 	<XPie class="pie" :value="usage"/> | ||||
| 	<div> | ||||
| 		<p><fa :icon="faMicrochip"/>CPU</p> | ||||
| 		<p>{{ meta.cpu.cores }} Logical cores</p> | ||||
| 		<p>{{ meta.cpu.model }}</p> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faMicrochip } from '@fortawesome/free-solid-svg-icons'; | ||||
| import XPie from './pie.vue'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XPie | ||||
| 	}, | ||||
| 	props: { | ||||
| 		connection: { | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		meta: { | ||||
| 			required: true, | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			usage: 0, | ||||
| 			faMicrochip, | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.connection.on('stats', this.onStats); | ||||
| 	}, | ||||
| 	beforeUnmount() { | ||||
| 		this.connection.off('stats', this.onStats); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		onStats(stats) { | ||||
| 			this.usage = stats.cpu; | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .vrvdvrys { | ||||
| 	display: flex; | ||||
| 	padding: 16px; | ||||
| 
 | ||||
| 	> .pie { | ||||
| 		height: 82px; | ||||
| 		flex-shrink: 0; | ||||
| 		margin-right: 16px; | ||||
| 	} | ||||
| 
 | ||||
| 	> div { | ||||
| 		flex: 1; | ||||
| 
 | ||||
| 		> p { | ||||
| 			margin: 0; | ||||
| 			font-size: 0.8em; | ||||
| 
 | ||||
| 			&:first-child { | ||||
| 				font-weight: bold; | ||||
| 				margin-bottom: 4px; | ||||
| 
 | ||||
| 				> [data-icon] { | ||||
| 					margin-right: 4px; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										72
									
								
								src/client/widgets/server-metric/disk.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/client/widgets/server-metric/disk.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,72 @@ | |||
| <template> | ||||
| <div class="zbwaqsat"> | ||||
| 	<XPie class="pie" :value="usage"/> | ||||
| 	<div> | ||||
| 		<p><fa :icon="faHdd"/>Disk</p> | ||||
| 		<p>Total: {{ bytes(total, 1) }}</p> | ||||
| 		<p>Free: {{ bytes(available, 1) }}</p> | ||||
| 		<p>Used: {{ bytes(used, 1) }}</p> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faHdd } from '@fortawesome/free-solid-svg-icons'; | ||||
| import XPie from './pie.vue'; | ||||
| import bytes from '@/filters/bytes'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XPie | ||||
| 	}, | ||||
| 	props: { | ||||
| 		meta: { | ||||
| 			required: true, | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			usage: this.meta.fs.used / this.meta.fs.total, | ||||
| 			total: this.meta.fs.total, | ||||
| 			used: this.meta.fs.used, | ||||
| 			available: this.meta.fs.total - this.meta.fs.used, | ||||
| 			faHdd, | ||||
| 		}; | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		bytes | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .zbwaqsat { | ||||
| 	display: flex; | ||||
| 	padding: 16px; | ||||
| 
 | ||||
| 	> .pie { | ||||
| 		height: 82px; | ||||
| 		flex-shrink: 0; | ||||
| 		margin-right: 16px; | ||||
| 	} | ||||
| 
 | ||||
| 	> div { | ||||
| 		flex: 1; | ||||
| 
 | ||||
| 		> p { | ||||
| 			margin: 0; | ||||
| 			font-size: 0.8em; | ||||
| 
 | ||||
| 			&:first-child { | ||||
| 				font-weight: bold; | ||||
| 				margin-bottom: 4px; | ||||
| 
 | ||||
| 				> [data-icon] { | ||||
| 					margin-right: 4px; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										84
									
								
								src/client/widgets/server-metric/index.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/client/widgets/server-metric/index.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,84 @@ | |||
| <template> | ||||
| <MkContainer :show-header="props.showHeader" :naked="props.transparent"> | ||||
| 	<template #header><Fa :icon="faServer"/>{{ $ts._widgets.serverMetric }}</template> | ||||
| 	<template #func><button @click="toggleView()" class="_button"><Fa :icon="faSort"/></button></template> | ||||
| 
 | ||||
| 	<div class="mkw-serverMetric" v-if="meta"> | ||||
| 		<XCpuMemory v-if="props.view === 0" :connection="connection" :meta="meta"/> | ||||
| 		<XNet v-if="props.view === 1" :connection="connection" :meta="meta"/> | ||||
| 		<XCpu v-if="props.view === 2" :connection="connection" :meta="meta"/> | ||||
| 		<XMemory v-if="props.view === 3" :connection="connection" :meta="meta"/> | ||||
| 		<XDisk v-if="props.view === 4" :connection="connection" :meta="meta"/> | ||||
| 	</div> | ||||
| </MkContainer> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faServer, faSort } from '@fortawesome/free-solid-svg-icons'; | ||||
| import define from '../define'; | ||||
| import MkContainer from '@/components/ui/container.vue'; | ||||
| import XCpuMemory from './cpu-mem.vue'; | ||||
| import XNet from './net.vue'; | ||||
| import XCpu from './cpu.vue'; | ||||
| import XMemory from './mem.vue'; | ||||
| import XDisk from './disk.vue'; | ||||
| import * as os from '@/os'; | ||||
| 
 | ||||
| const widget = define({ | ||||
| 	name: 'serverMetric', | ||||
| 	props: () => ({ | ||||
| 		showHeader: { | ||||
| 			type: 'boolean', | ||||
| 			default: true, | ||||
| 		}, | ||||
| 		transparent: { | ||||
| 			type: 'boolean', | ||||
| 			default: false, | ||||
| 		}, | ||||
| 		view: { | ||||
| 			type: 'number', | ||||
| 			default: 0, | ||||
| 			hidden: true, | ||||
| 		}, | ||||
| 	}) | ||||
| }); | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	extends: widget, | ||||
| 	components: { | ||||
| 		MkContainer, | ||||
| 		XCpuMemory, | ||||
| 		XNet, | ||||
| 		XCpu, | ||||
| 		XMemory, | ||||
| 		XDisk, | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			meta: null, | ||||
| 			connection: null, | ||||
| 			faServer, faSort, | ||||
| 		}; | ||||
| 	}, | ||||
| 	created() { | ||||
| 		os.api('server-info', {}).then(res => { | ||||
| 			this.meta = res; | ||||
| 		}); | ||||
| 		this.connection = os.stream.useSharedConnection('serverStats'); | ||||
| 	}, | ||||
| 	unmounted() { | ||||
| 		this.connection.dispose(); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		toggleView() { | ||||
| 			if (this.props.view == 4) { | ||||
| 				this.props.view = 0; | ||||
| 			} else { | ||||
| 				this.props.view++; | ||||
| 			} | ||||
| 			this.save(); | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
							
								
								
									
										87
									
								
								src/client/widgets/server-metric/mem.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/client/widgets/server-metric/mem.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,87 @@ | |||
| <template> | ||||
| <div class="zlxnikvl"> | ||||
| 	<XPie class="pie" :value="usage"/> | ||||
| 	<div> | ||||
| 		<p><fa :icon="faMemory"/>RAM</p> | ||||
| 		<p>Total: {{ bytes(total, 1) }}</p> | ||||
| 		<p>Used: {{ bytes(used, 1) }}</p> | ||||
| 		<p>Free: {{ bytes(free, 1) }}</p> | ||||
| 	</div> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faMemory } from '@fortawesome/free-solid-svg-icons'; | ||||
| import XPie from './pie.vue'; | ||||
| import bytes from '@/filters/bytes'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XPie | ||||
| 	}, | ||||
| 	props: { | ||||
| 		connection: { | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		meta: { | ||||
| 			required: true, | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			usage: 0, | ||||
| 			total: 0, | ||||
| 			used: 0, | ||||
| 			free: 0, | ||||
| 			faMemory, | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.connection.on('stats', this.onStats); | ||||
| 	}, | ||||
| 	beforeUnmount() { | ||||
| 		this.connection.off('stats', this.onStats); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		onStats(stats) { | ||||
| 			this.usage = stats.mem.used / this.meta.mem.total; | ||||
| 			this.total = this.meta.mem.total; | ||||
| 			this.used = stats.mem.used; | ||||
| 			this.free = this.meta.mem.total - stats.mem.used; | ||||
| 		}, | ||||
| 		bytes | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .zlxnikvl { | ||||
| 	display: flex; | ||||
| 	padding: 16px; | ||||
| 
 | ||||
| 	> .pie { | ||||
| 		height: 82px; | ||||
| 		flex-shrink: 0; | ||||
| 		margin-right: 16px; | ||||
| 	} | ||||
| 
 | ||||
| 	> div { | ||||
| 		flex: 1; | ||||
| 
 | ||||
| 		> p { | ||||
| 			margin: 0; | ||||
| 			font-size: 0.8em; | ||||
| 
 | ||||
| 			&:first-child { | ||||
| 				font-weight: bold; | ||||
| 				margin-bottom: 4px; | ||||
| 
 | ||||
| 				> [data-icon] { | ||||
| 					margin-right: 4px; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										148
									
								
								src/client/widgets/server-metric/net.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								src/client/widgets/server-metric/net.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,148 @@ | |||
| <template> | ||||
| <div class="oxxrhrto"> | ||||
| 	<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`"> | ||||
| 		<polygon | ||||
| 			:points="inPolygonPoints" | ||||
| 			fill="#94a029" | ||||
| 			fill-opacity="0.5" | ||||
| 		/> | ||||
| 		<polyline | ||||
| 			:points="inPolylinePoints" | ||||
| 			fill="none" | ||||
| 			stroke="#94a029" | ||||
| 			stroke-width="1" | ||||
| 		/> | ||||
| 		<circle | ||||
| 			:cx="inHeadX" | ||||
| 			:cy="inHeadY" | ||||
| 			r="1.5" | ||||
| 			fill="#94a029" | ||||
| 		/> | ||||
| 		<text x="1" y="5">NET rx <tspan>{{ bytes(inRecent) }}</tspan></text> | ||||
| 	</svg> | ||||
| 	<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`"> | ||||
| 		<polygon | ||||
| 			:points="outPolygonPoints" | ||||
| 			fill="#ff9156" | ||||
| 			fill-opacity="0.5" | ||||
| 		/> | ||||
| 		<polyline | ||||
| 			:points="outPolylinePoints" | ||||
| 			fill="none" | ||||
| 			stroke="#ff9156" | ||||
| 			stroke-width="1" | ||||
| 		/> | ||||
| 		<circle | ||||
| 			:cx="outHeadX" | ||||
| 			:cy="outHeadY" | ||||
| 			r="1.5" | ||||
| 			fill="#ff9156" | ||||
| 		/> | ||||
| 		<text x="1" y="5">NET tx <tspan>{{ bytes(outRecent) }}</tspan></text> | ||||
| 	</svg> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import bytes from '@/filters/bytes'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| 		connection: { | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		meta: { | ||||
| 			required: true, | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			viewBoxX: 50, | ||||
| 			viewBoxY: 30, | ||||
| 			stats: [], | ||||
| 			inPolylinePoints: '', | ||||
| 			outPolylinePoints: '', | ||||
| 			inPolygonPoints: '', | ||||
| 			outPolygonPoints: '', | ||||
| 			inHeadX: null, | ||||
| 			inHeadY: null, | ||||
| 			outHeadX: null, | ||||
| 			outHeadY: null, | ||||
| 			inRecent: 0, | ||||
| 			outRecent: 0 | ||||
| 		}; | ||||
| 	}, | ||||
| 	mounted() { | ||||
| 		this.connection.on('stats', this.onStats); | ||||
| 		this.connection.on('statsLog', this.onStatsLog); | ||||
| 		this.connection.send('requestLog', { | ||||
| 			id: Math.random().toString().substr(2, 8) | ||||
| 		}); | ||||
| 	}, | ||||
| 	beforeUnmount() { | ||||
| 		this.connection.off('stats', this.onStats); | ||||
| 		this.connection.off('statsLog', this.onStatsLog); | ||||
| 	}, | ||||
| 	methods: { | ||||
| 		onStats(stats) { | ||||
| 			this.stats.push(stats); | ||||
| 			if (this.stats.length > 50) this.stats.shift(); | ||||
| 
 | ||||
| 			const inPeak = Math.max(1024 * 64, Math.max(...this.stats.map(s => s.net.rx))); | ||||
| 			const outPeak = Math.max(1024 * 64, Math.max(...this.stats.map(s => s.net.tx))); | ||||
| 
 | ||||
| 			const inPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.net.rx / inPeak)) * this.viewBoxY]); | ||||
| 			const outPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.net.tx / outPeak)) * this.viewBoxY]); | ||||
| 			this.inPolylinePoints = inPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' '); | ||||
| 			this.outPolylinePoints = outPolylinePoints.map(xy => `${xy[0]},${xy[1]}`).join(' '); | ||||
| 
 | ||||
| 			this.inPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.inPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`; | ||||
| 			this.outPolygonPoints = `${this.viewBoxX - (this.stats.length - 1)},${this.viewBoxY} ${this.outPolylinePoints} ${this.viewBoxX},${this.viewBoxY}`; | ||||
| 
 | ||||
| 			this.inHeadX = inPolylinePoints[inPolylinePoints.length - 1][0]; | ||||
| 			this.inHeadY = inPolylinePoints[inPolylinePoints.length - 1][1]; | ||||
| 			this.outHeadX = outPolylinePoints[outPolylinePoints.length - 1][0]; | ||||
| 			this.outHeadY = outPolylinePoints[outPolylinePoints.length - 1][1]; | ||||
| 
 | ||||
| 			this.inRecent = stats.net.rx; | ||||
| 			this.outRecent = stats.net.tx; | ||||
| 		}, | ||||
| 		onStatsLog(statsLog) { | ||||
| 			for (const stats of [...statsLog].reverse()) { | ||||
| 				this.onStats(stats); | ||||
| 			} | ||||
| 		}, | ||||
| 		bytes | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .oxxrhrto { | ||||
| 	display: flex; | ||||
| 
 | ||||
| 	> svg { | ||||
| 		display: block; | ||||
| 		padding: 10px; | ||||
| 		width: 50%; | ||||
| 
 | ||||
| 		&:first-child { | ||||
| 			padding-right: 5px; | ||||
| 		} | ||||
| 
 | ||||
| 		&:last-child { | ||||
| 			padding-left: 5px; | ||||
| 		} | ||||
| 
 | ||||
| 		> text { | ||||
| 			font-size: 5px; | ||||
| 			fill: currentColor; | ||||
| 
 | ||||
| 			> tspan { | ||||
| 				opacity: 0.5; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										65
									
								
								src/client/widgets/server-metric/pie.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/client/widgets/server-metric/pie.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,65 @@ | |||
| <template> | ||||
| <svg class="hsalcinq" viewBox="0 0 1 1" preserveAspectRatio="none"> | ||||
| 	<circle | ||||
| 		:r="r" | ||||
| 		cx="50%" cy="50%" | ||||
| 		fill="none" | ||||
| 		stroke-width="0.1" | ||||
| 		stroke="rgba(0, 0, 0, 0.05)" | ||||
| 	/> | ||||
| 	<circle | ||||
| 		:r="r" | ||||
| 		cx="50%" cy="50%" | ||||
| 		:stroke-dasharray="Math.PI * (r * 2)" | ||||
| 		:stroke-dashoffset="strokeDashoffset" | ||||
| 		fill="none" | ||||
| 		stroke-width="0.1" | ||||
| 		:stroke="color" | ||||
| 	/> | ||||
| 	<text x="50%" y="50%" dy="0.05" text-anchor="middle">{{ (value * 100).toFixed(0) }}%</text> | ||||
| </svg> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	props: { | ||||
| 		value: { | ||||
| 			type: Number, | ||||
| 			required: true | ||||
| 		} | ||||
| 	}, | ||||
| 	data() { | ||||
| 		return { | ||||
| 			r: 0.45 | ||||
| 		}; | ||||
| 	}, | ||||
| 	computed: { | ||||
| 		color(): string { | ||||
| 			return `hsl(${180 - (this.value * 180)}, 80%, 70%)`; | ||||
| 		}, | ||||
| 		strokeDashoffset(): number { | ||||
| 			return (1 - this.value) * (Math.PI * (this.r * 2)); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .hsalcinq { | ||||
| 	display: block; | ||||
| 	height: 100%; | ||||
| 
 | ||||
| 	> circle { | ||||
| 		transform-origin: center; | ||||
| 		transform: rotate(-90deg); | ||||
| 		transition: stroke-dashoffset 0.5s ease; | ||||
| 	} | ||||
| 
 | ||||
| 	> text { | ||||
| 		font-size: 0.15px; | ||||
| 		fill: currentColor; | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  | @ -6,6 +6,9 @@ const ev = new Xev(); | |||
| 
 | ||||
| const interval = 2000; | ||||
| 
 | ||||
| const roundCpu = (num: number) => Math.round(num * 1000) / 1000; | ||||
| const round = (num: number) => Math.round(num * 10) / 10; | ||||
| 
 | ||||
| /** | ||||
|  * Report server stats regularly | ||||
|  */ | ||||
|  | @ -23,18 +26,18 @@ export default function() { | |||
| 		const fsStats = await fs(); | ||||
| 
 | ||||
| 		const stats = { | ||||
| 			cpu: cpu, | ||||
| 			cpu: roundCpu(cpu), | ||||
| 			mem: { | ||||
| 				used: memStats.used, | ||||
| 				active: memStats.active, | ||||
| 				used: round(memStats.used), | ||||
| 				active: round(memStats.active), | ||||
| 			}, | ||||
| 			net: { | ||||
| 				rx: Math.max(0, netStats.rx_sec), | ||||
| 				tx: Math.max(0, netStats.tx_sec), | ||||
| 				rx: round(Math.max(0, netStats.rx_sec)), | ||||
| 				tx: round(Math.max(0, netStats.tx_sec)), | ||||
| 			}, | ||||
| 			fs: { | ||||
| 				r: Math.max(0, fsStats.rIO_sec), | ||||
| 				w: Math.max(0, fsStats.wIO_sec), | ||||
| 				r: round(Math.max(0, fsStats.rIO_sec)), | ||||
| 				w: round(Math.max(0, fsStats.wIO_sec)), | ||||
| 			} | ||||
| 		}; | ||||
| 		ev.emit('serverStats', stats); | ||||
|  |  | |||
							
								
								
									
										35
									
								
								src/server/api/endpoints/server-info.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/server/api/endpoints/server-info.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,35 @@ | |||
| import * as os from 'os'; | ||||
| import * as si from 'systeminformation'; | ||||
| import define from '../define'; | ||||
| 
 | ||||
| export const meta = { | ||||
| 	requireCredential: false as const, | ||||
| 
 | ||||
| 	desc: { | ||||
| 	}, | ||||
| 
 | ||||
| 	tags: ['meta'], | ||||
| 
 | ||||
| 	params: { | ||||
| 	}, | ||||
| }; | ||||
| 
 | ||||
| export default define(meta, async () => { | ||||
| 	const memStats = await si.mem(); | ||||
| 	const fsStats = await si.fsSize(); | ||||
| 
 | ||||
| 	return { | ||||
| 		machine: os.hostname(), | ||||
| 		cpu: { | ||||
| 			model: os.cpus()[0].model, | ||||
| 			cores: os.cpus().length | ||||
| 		}, | ||||
| 		mem: { | ||||
| 			total: memStats.total | ||||
| 		}, | ||||
| 		fs: { | ||||
| 			total: fsStats[0].size, | ||||
| 			used: fsStats[0].used, | ||||
| 		}, | ||||
| 	}; | ||||
| }); | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue