tweak client
This commit is contained in:
		
							parent
							
								
									a6fff86099
								
							
						
					
					
						commit
						f880d0631c
					
				
					 8 changed files with 175 additions and 167 deletions
				
			
		|  | @ -848,6 +848,8 @@ rateLimitExceeded: "レート制限を超えました" | ||||||
| cropImage: "画像のクロップ" | cropImage: "画像のクロップ" | ||||||
| cropImageAsk: "画像をクロップしますか?" | cropImageAsk: "画像をクロップしますか?" | ||||||
| file: "ファイル" | file: "ファイル" | ||||||
|  | recentNHours: "直近{n}時間" | ||||||
|  | recentNDays: "直近{n}日" | ||||||
| 
 | 
 | ||||||
| _emailUnavailable: | _emailUnavailable: | ||||||
|   used: "既に使用されています" |   used: "既に使用されています" | ||||||
|  |  | ||||||
|  | @ -1,11 +1,13 @@ | ||||||
| import { db } from '@/db/postgre.js'; | import { db } from '@/db/postgre.js'; | ||||||
| import { Instance } from '@/models/entities/instance.js'; | import { Instance } from '@/models/entities/instance.js'; | ||||||
| import { Packed } from '@/misc/schema.js'; | import { Packed } from '@/misc/schema.js'; | ||||||
|  | import { fetchMeta } from '@/misc/fetch-meta.js'; | ||||||
| 
 | 
 | ||||||
| export const InstanceRepository = db.getRepository(Instance).extend({ | export const InstanceRepository = db.getRepository(Instance).extend({ | ||||||
| 	async pack( | 	async pack( | ||||||
| 		instance: Instance, | 		instance: Instance, | ||||||
| 	): Promise<Packed<'FederationInstance'>> { | 	): Promise<Packed<'FederationInstance'>> { | ||||||
|  | 		const meta = await fetchMeta(); | ||||||
| 		return { | 		return { | ||||||
| 			id: instance.id, | 			id: instance.id, | ||||||
| 			caughtAt: instance.caughtAt.toISOString(), | 			caughtAt: instance.caughtAt.toISOString(), | ||||||
|  | @ -18,6 +20,7 @@ export const InstanceRepository = db.getRepository(Instance).extend({ | ||||||
| 			lastCommunicatedAt: instance.lastCommunicatedAt.toISOString(), | 			lastCommunicatedAt: instance.lastCommunicatedAt.toISOString(), | ||||||
| 			isNotResponding: instance.isNotResponding, | 			isNotResponding: instance.isNotResponding, | ||||||
| 			isSuspended: instance.isSuspended, | 			isSuspended: instance.isSuspended, | ||||||
|  | 			isBlocked: meta.blockedHosts.includes(instance.host), | ||||||
| 			softwareName: instance.softwareName, | 			softwareName: instance.softwareName, | ||||||
| 			softwareVersion: instance.softwareVersion, | 			softwareVersion: instance.softwareVersion, | ||||||
| 			openRegistrations: instance.openRegistrations, | 			openRegistrations: instance.openRegistrations, | ||||||
|  |  | ||||||
|  | @ -52,6 +52,10 @@ export const packedFederationInstanceSchema = { | ||||||
| 			type: 'boolean', | 			type: 'boolean', | ||||||
| 			optional: false, nullable: false, | 			optional: false, nullable: false, | ||||||
| 		}, | 		}, | ||||||
|  | 		isBlocked: { | ||||||
|  | 			type: 'boolean', | ||||||
|  | 			optional: false, nullable: false, | ||||||
|  | 		}, | ||||||
| 		softwareName: { | 		softwareName: { | ||||||
| 			type: 'string', | 			type: 'string', | ||||||
| 			optional: false, nullable: true, | 			optional: false, nullable: true, | ||||||
|  |  | ||||||
|  | @ -79,7 +79,8 @@ function resolve() { | ||||||
| 			align-items: center; | 			align-items: center; | ||||||
| 			padding: 14px; | 			padding: 14px; | ||||||
| 			border-radius: 8px; | 			border-radius: 8px; | ||||||
| 			background-image: linear-gradient(45deg, rgb(255 196 0 / 15%) 16.67%, transparent 16.67%, transparent 50%, rgb(255 196 0 / 15%) 50%, rgb(255 196 0 / 15%) 66.67%, transparent 66.67%, transparent 100%); | 			--c: rgb(255 196 0 / 15%); | ||||||
|  | 			background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%); | ||||||
| 			background-size: 16px 16px; | 			background-size: 16px 16px; | ||||||
| 
 | 
 | ||||||
| 			> .avatar { | 			> .avatar { | ||||||
|  |  | ||||||
							
								
								
									
										96
									
								
								packages/client/src/components/instance-info.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								packages/client/src/components/instance-info.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,96 @@ | ||||||
|  | <template> | ||||||
|  | <div :class="[$style.root, { yellow: instance.isNotResponding, red: instance.isBlocked, gray: instance.isSuspended }]"> | ||||||
|  | 	<img v-if="instance.iconUrl" class="icon" :src="instance.iconUrl" alt=""/> | ||||||
|  | 	<div class="body"> | ||||||
|  | 		<span class="host">{{ instance.host }}</span> | ||||||
|  | 		<span class="sub">{{ instance.softwareName || '?' }} {{ instance.softwareVersion }}</span> | ||||||
|  | 	</div> | ||||||
|  | 	<MkMiniChart v-if="chart" class="chart" :src="chart.requests.received"/> | ||||||
|  | </div> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script lang="ts" setup> | ||||||
|  | import * as misskey from 'misskey-js'; | ||||||
|  | import MkMiniChart from '@/components/mini-chart.vue'; | ||||||
|  | import * as os from '@/os'; | ||||||
|  | 
 | ||||||
|  | const props = defineProps<{ | ||||||
|  | 	instance: misskey.entities.Instance; | ||||||
|  | }>(); | ||||||
|  | 
 | ||||||
|  | const chart = $ref(null); | ||||||
|  | 
 | ||||||
|  | os.api('charts/instance', { host: props.instance.host, limit: 16, span: 'hour' }).then(res => { | ||||||
|  | 	chart = res; | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="scss" module> | ||||||
|  | .root { | ||||||
|  | 	$bodyTitleHieght: 18px; | ||||||
|  | 	$bodyInfoHieght: 16px; | ||||||
|  | 
 | ||||||
|  | 	display: flex; | ||||||
|  | 	align-items: center; | ||||||
|  | 	padding: 16px; | ||||||
|  | 	background: var(--panel); | ||||||
|  | 	border-radius: 8px; | ||||||
|  | 
 | ||||||
|  | 	> :global(.icon) { | ||||||
|  | 		display: block; | ||||||
|  | 		width: ($bodyTitleHieght + $bodyInfoHieght); | ||||||
|  | 		height: ($bodyTitleHieght + $bodyInfoHieght); | ||||||
|  | 		object-fit: cover; | ||||||
|  | 		border-radius: 4px; | ||||||
|  | 		margin-right: 8px; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	> :global(.body) { | ||||||
|  | 		flex: 1; | ||||||
|  | 		overflow: hidden; | ||||||
|  | 		font-size: 0.9em; | ||||||
|  | 		color: var(--fg); | ||||||
|  | 		padding-right: 8px; | ||||||
|  | 
 | ||||||
|  | 		> :global(.host) { | ||||||
|  | 			display: block; | ||||||
|  | 			width: 100%; | ||||||
|  | 			white-space: nowrap; | ||||||
|  | 			overflow: hidden; | ||||||
|  | 			text-overflow: ellipsis; | ||||||
|  | 			line-height: $bodyTitleHieght; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		> :global(.sub) { | ||||||
|  | 			font-size: 75%; | ||||||
|  | 			opacity: 0.7; | ||||||
|  | 			line-height: $bodyInfoHieght; | ||||||
|  | 			white-space: nowrap; | ||||||
|  | 			overflow: hidden; | ||||||
|  | 			text-overflow: ellipsis; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	> :global(.chart) { | ||||||
|  | 		height: 30px; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	&:global(.yellow) { | ||||||
|  | 		--c: rgb(255 196 0 / 15%); | ||||||
|  | 		background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%); | ||||||
|  | 		background-size: 16px 16px; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	&:global(.red) { | ||||||
|  | 		--c: rgb(255 0 0 / 15%); | ||||||
|  | 		background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%); | ||||||
|  | 		background-size: 16px 16px; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	&:global(.gray) { | ||||||
|  | 		--c: var(--bg); | ||||||
|  | 		background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%); | ||||||
|  | 		background-size: 16px 16px; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | </style> | ||||||
|  | @ -41,51 +41,8 @@ | ||||||
| 
 | 
 | ||||||
| 			<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination"> | 			<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination"> | ||||||
| 				<div class="dqokceoi"> | 				<div class="dqokceoi"> | ||||||
| 					<MkA v-for="instance in items" :key="instance.id" class="instance" :to="`/instance-info/${instance.host}`"> | 					<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Last communicated: ${new Date(instance.lastCommunicatedAt).toLocaleString()}\nStatus: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`" :behavior="'window'"> | ||||||
| 						<div class="host"><img :src="instance.faviconUrl">{{ instance.host }}</div> | 						<MkInstanceInfo :instance="instance"/> | ||||||
| 						<div class="table"> |  | ||||||
| 							<div class="cell"> |  | ||||||
| 								<div class="key">{{ $ts.registeredAt }}</div> |  | ||||||
| 								<div class="value"><MkTime :time="instance.caughtAt"/></div> |  | ||||||
| 							</div> |  | ||||||
| 							<div class="cell"> |  | ||||||
| 								<div class="key">{{ $ts.software }}</div> |  | ||||||
| 								<div class="value">{{ instance.softwareName || `(${$ts.unknown})` }}</div> |  | ||||||
| 							</div> |  | ||||||
| 							<div class="cell"> |  | ||||||
| 								<div class="key">{{ $ts.version }}</div> |  | ||||||
| 								<div class="value">{{ instance.softwareVersion || `(${$ts.unknown})` }}</div> |  | ||||||
| 							</div> |  | ||||||
| 							<div class="cell"> |  | ||||||
| 								<div class="key">{{ $ts.users }}</div> |  | ||||||
| 								<div class="value">{{ instance.usersCount }}</div> |  | ||||||
| 							</div> |  | ||||||
| 							<div class="cell"> |  | ||||||
| 								<div class="key">{{ $ts.notes }}</div> |  | ||||||
| 								<div class="value">{{ instance.notesCount }}</div> |  | ||||||
| 							</div> |  | ||||||
| 							<div class="cell"> |  | ||||||
| 								<div class="key">{{ $ts.sent }}</div> |  | ||||||
| 								<div class="value"><MkTime v-if="instance.latestRequestSentAt" :time="instance.latestRequestSentAt"/><span v-else>N/A</span></div> |  | ||||||
| 							</div> |  | ||||||
| 							<div class="cell"> |  | ||||||
| 								<div class="key">{{ $ts.received }}</div> |  | ||||||
| 								<div class="value"><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></div> |  | ||||||
| 							</div> |  | ||||||
| 						</div> |  | ||||||
| 						<div class="footer"> |  | ||||||
| 							<span class="status" :class="getStatus(instance)">{{ getStatus(instance) }}</span> |  | ||||||
| 							<span class="pubSub"> |  | ||||||
| 								<span v-if="instance.followersCount > 0" class="sub"><i class="fas fa-caret-down icon"></i>Sub</span> |  | ||||||
| 								<span v-else class="sub"><i class="fas fa-caret-down icon"></i>-</span> |  | ||||||
| 								<span v-if="instance.followingCount > 0" class="pub"><i class="fas fa-caret-up icon"></i>Pub</span> |  | ||||||
| 								<span v-else class="pub"><i class="fas fa-caret-up icon"></i>-</span> |  | ||||||
| 							</span> |  | ||||||
| 							<span class="right"> |  | ||||||
| 								<span class="latestStatus">{{ instance.latestStatus || '-' }}</span> |  | ||||||
| 								<span class="lastCommunicatedAt"><MkTime :time="instance.lastCommunicatedAt"/></span> |  | ||||||
| 							</span> |  | ||||||
| 						</div> |  | ||||||
| 					</MkA> | 					</MkA> | ||||||
| 				</div> | 				</div> | ||||||
| 			</MkPagination> | 			</MkPagination> | ||||||
|  | @ -100,6 +57,7 @@ import MkButton from '@/components/ui/button.vue'; | ||||||
| import MkInput from '@/components/form/input.vue'; | import MkInput from '@/components/form/input.vue'; | ||||||
| import MkSelect from '@/components/form/select.vue'; | import MkSelect from '@/components/form/select.vue'; | ||||||
| import MkPagination from '@/components/ui/pagination.vue'; | import MkPagination from '@/components/ui/pagination.vue'; | ||||||
|  | import MkInstanceInfo from '@/components/instance-info.vue'; | ||||||
| import FormSplit from '@/components/form/split.vue'; | import FormSplit from '@/components/form/split.vue'; | ||||||
| import * as os from '@/os'; | import * as os from '@/os'; | ||||||
| import { i18n } from '@/i18n'; | import { i18n } from '@/i18n'; | ||||||
|  | @ -127,9 +85,10 @@ const pagination = { | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| function getStatus(instance) { | function getStatus(instance) { | ||||||
| 	if (instance.isSuspended) return 'suspended'; | 	if (instance.isSuspended) return 'Suspended'; | ||||||
| 	if (instance.isNotResponding) return 'error'; | 	if (instance.isBlocked) return 'Blocked'; | ||||||
| 	return 'alive'; | 	if (instance.isNotResponding) return 'Error'; | ||||||
|  | 	return 'Alive'; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const headerActions = $computed(() => []); | const headerActions = $computed(() => []); | ||||||
|  | @ -156,86 +115,8 @@ definePageMetadata({ | ||||||
| 	grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); | 	grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); | ||||||
| 	grid-gap: 12px; | 	grid-gap: 12px; | ||||||
| 
 | 
 | ||||||
| 	> .instance { | 	> .instance:hover { | ||||||
| 		padding: 16px; | 		text-decoration: none; | ||||||
| 		background: var(--panel); |  | ||||||
| 		border-radius: 8px; |  | ||||||
| 
 |  | ||||||
| 		&:hover { |  | ||||||
| 			text-decoration: none; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		> .host { |  | ||||||
| 			font-weight: bold; |  | ||||||
| 			white-space: nowrap; |  | ||||||
| 			overflow: hidden; |  | ||||||
| 			text-overflow: ellipsis; |  | ||||||
| 
 |  | ||||||
| 			> img { |  | ||||||
| 				width: 18px; |  | ||||||
| 				height: 18px; |  | ||||||
| 				margin-right: 6px; |  | ||||||
| 				vertical-align: middle; |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		> .table { |  | ||||||
| 			display: grid; |  | ||||||
| 			grid-template-columns: repeat(auto-fill, minmax(60px, 1fr)); |  | ||||||
| 			grid-gap: 6px; |  | ||||||
| 			margin: 6px 0; |  | ||||||
| 			font-size: 70%; |  | ||||||
| 
 |  | ||||||
| 			> .cell { |  | ||||||
| 				> .key, > .value { |  | ||||||
| 					white-space: nowrap; |  | ||||||
| 					overflow: hidden; |  | ||||||
| 					text-overflow: ellipsis; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				> .key { |  | ||||||
| 					opacity: 0.7; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				> .value { |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		> .footer { |  | ||||||
| 			display: flex; |  | ||||||
| 			align-items: center; |  | ||||||
| 			font-size: 0.9em; |  | ||||||
| 
 |  | ||||||
| 			> .status { |  | ||||||
| 				&.suspended { |  | ||||||
| 					opacity: 0.5; |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				&.error { |  | ||||||
| 					color: var(--error); |  | ||||||
| 				} |  | ||||||
| 
 |  | ||||||
| 				&.alive { |  | ||||||
| 					color: var(--success); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			> .pubSub { |  | ||||||
| 				margin-left: 8px; |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			> .right { |  | ||||||
| 				margin-left: auto; |  | ||||||
| 
 |  | ||||||
| 				> .latestStatus { |  | ||||||
| 					border: solid 1px var(--divider); |  | ||||||
| 					border-radius: 4px; |  | ||||||
| 					margin: 0 8px; |  | ||||||
| 					padding: 0 4px; |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| <template> | <template> | ||||||
| <MkStickyContainer> | <MkStickyContainer> | ||||||
| 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> | 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> | ||||||
| 	<MkSpacer :content-max="600" :margin-min="16" :margin-max="32"> | 	<MkSpacer v-if="instance" :content-max="600" :margin-min="16" :margin-max="32"> | ||||||
| 		<div v-if="instance" class="_formRoot"> | 		<div v-if="tab === 'overview'" class="_formRoot"> | ||||||
| 			<div class="fnfelxur"> | 			<div class="fnfelxur"> | ||||||
| 				<img :src="instance.iconUrl || instance.faviconUrl" alt="" class="icon"/> | 				<img :src="instance.iconUrl || instance.faviconUrl" alt="" class="icon"/> | ||||||
| 			</div> | 			</div> | ||||||
|  | @ -64,37 +64,6 @@ | ||||||
| 				</MkKeyValue> | 				</MkKeyValue> | ||||||
| 			</FormSection> | 			</FormSection> | ||||||
| 
 | 
 | ||||||
| 			<FormSection> |  | ||||||
| 				<template #label>{{ $ts.statistics }}</template> |  | ||||||
| 				<div class="cmhjzshl"> |  | ||||||
| 					<div class="selects"> |  | ||||||
| 						<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;"> |  | ||||||
| 							<option value="instance-requests">{{ $ts._instanceCharts.requests }}</option> |  | ||||||
| 							<option value="instance-users">{{ $ts._instanceCharts.users }}</option> |  | ||||||
| 							<option value="instance-users-total">{{ $ts._instanceCharts.usersTotal }}</option> |  | ||||||
| 							<option value="instance-notes">{{ $ts._instanceCharts.notes }}</option> |  | ||||||
| 							<option value="instance-notes-total">{{ $ts._instanceCharts.notesTotal }}</option> |  | ||||||
| 							<option value="instance-ff">{{ $ts._instanceCharts.ff }}</option> |  | ||||||
| 							<option value="instance-ff-total">{{ $ts._instanceCharts.ffTotal }}</option> |  | ||||||
| 							<option value="instance-drive-usage">{{ $ts._instanceCharts.cacheSize }}</option> |  | ||||||
| 							<option value="instance-drive-usage-total">{{ $ts._instanceCharts.cacheSizeTotal }}</option> |  | ||||||
| 							<option value="instance-drive-files">{{ $ts._instanceCharts.files }}</option> |  | ||||||
| 							<option value="instance-drive-files-total">{{ $ts._instanceCharts.filesTotal }}</option> |  | ||||||
| 						</MkSelect> |  | ||||||
| 						<MkSelect v-model="chartSpan" style="margin: 0;"> |  | ||||||
| 							<option value="hour">{{ $ts.perHour }}</option> |  | ||||||
| 							<option value="day">{{ $ts.perDay }}</option> |  | ||||||
| 						</MkSelect> |  | ||||||
| 					</div> |  | ||||||
| 					<div class="chart"> |  | ||||||
| 						<MkChart :src="chartSrc" :span="chartSpan" :limit="90" :args="{ host: host }" :detailed="true"></MkChart> |  | ||||||
| 					</div> |  | ||||||
| 				</div> |  | ||||||
| 			</FormSection> |  | ||||||
| 
 |  | ||||||
| 			<MkObjectView tall :value="instance"> |  | ||||||
| 			</MkObjectView> |  | ||||||
| 
 |  | ||||||
| 			<FormSection> | 			<FormSection> | ||||||
| 				<template #label>Well-known resources</template> | 				<template #label>Well-known resources</template> | ||||||
| 				<FormLink :to="`https://${host}/.well-known/host-meta`" external style="margin-bottom: 8px;">host-meta</FormLink> | 				<FormLink :to="`https://${host}/.well-known/host-meta`" external style="margin-bottom: 8px;">host-meta</FormLink> | ||||||
|  | @ -104,6 +73,35 @@ | ||||||
| 				<FormLink :to="`https://${host}/manifest.json`" external style="margin-bottom: 8px;">manifest.json</FormLink> | 				<FormLink :to="`https://${host}/manifest.json`" external style="margin-bottom: 8px;">manifest.json</FormLink> | ||||||
| 			</FormSection> | 			</FormSection> | ||||||
| 		</div> | 		</div> | ||||||
|  | 		<div v-if="tab === 'chart'" class="_formRoot"> | ||||||
|  | 			<div class="cmhjzshl"> | ||||||
|  | 				<div class="selects"> | ||||||
|  | 					<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;"> | ||||||
|  | 						<option value="instance-requests">{{ $ts._instanceCharts.requests }}</option> | ||||||
|  | 						<option value="instance-users">{{ $ts._instanceCharts.users }}</option> | ||||||
|  | 						<option value="instance-users-total">{{ $ts._instanceCharts.usersTotal }}</option> | ||||||
|  | 						<option value="instance-notes">{{ $ts._instanceCharts.notes }}</option> | ||||||
|  | 						<option value="instance-notes-total">{{ $ts._instanceCharts.notesTotal }}</option> | ||||||
|  | 						<option value="instance-ff">{{ $ts._instanceCharts.ff }}</option> | ||||||
|  | 						<option value="instance-ff-total">{{ $ts._instanceCharts.ffTotal }}</option> | ||||||
|  | 						<option value="instance-drive-usage">{{ $ts._instanceCharts.cacheSize }}</option> | ||||||
|  | 						<option value="instance-drive-usage-total">{{ $ts._instanceCharts.cacheSizeTotal }}</option> | ||||||
|  | 						<option value="instance-drive-files">{{ $ts._instanceCharts.files }}</option> | ||||||
|  | 						<option value="instance-drive-files-total">{{ $ts._instanceCharts.filesTotal }}</option> | ||||||
|  | 					</MkSelect> | ||||||
|  | 				</div> | ||||||
|  | 				<div class="charts"> | ||||||
|  | 					<div class="label">{{ i18n.t('recentNHours', { n: 90 }) }}</div> | ||||||
|  | 					<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ host: host }" :detailed="true"></MkChart> | ||||||
|  | 					<div class="label">{{ i18n.t('recentNDays', { n: 90 }) }}</div> | ||||||
|  | 					<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ host: host }" :detailed="true"></MkChart> | ||||||
|  | 				</div> | ||||||
|  | 			</div> | ||||||
|  | 		</div> | ||||||
|  | 		<div v-if="tab === 'raw'" class="_formRoot"> | ||||||
|  | 			<MkObjectView tall :value="instance"> | ||||||
|  | 			</MkObjectView> | ||||||
|  | 		</div> | ||||||
| 	</MkSpacer> | 	</MkSpacer> | ||||||
| </MkStickyContainer> | </MkStickyContainer> | ||||||
| </template> | </template> | ||||||
|  | @ -125,17 +123,18 @@ import number from '@/filters/number'; | ||||||
| import bytes from '@/filters/bytes'; | import bytes from '@/filters/bytes'; | ||||||
| import { iAmModerator } from '@/account'; | import { iAmModerator } from '@/account'; | ||||||
| import { definePageMetadata } from '@/scripts/page-metadata'; | import { definePageMetadata } from '@/scripts/page-metadata'; | ||||||
|  | import { i18n } from '@/i18n'; | ||||||
| 
 | 
 | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	host: string; | 	host: string; | ||||||
| }>(); | }>(); | ||||||
| 
 | 
 | ||||||
|  | let tab = $ref('overview'); | ||||||
| let meta = $ref<misskey.entities.DetailedInstanceMetadata | null>(null); | let meta = $ref<misskey.entities.DetailedInstanceMetadata | null>(null); | ||||||
| let instance = $ref<misskey.entities.Instance | null>(null); | let instance = $ref<misskey.entities.Instance | null>(null); | ||||||
| let suspended = $ref(false); | let suspended = $ref(false); | ||||||
| let isBlocked = $ref(false); | let isBlocked = $ref(false); | ||||||
| let chartSrc = $ref('instance-requests'); | let chartSrc = $ref('instance-requests'); | ||||||
| let chartSpan = $ref('hour'); |  | ||||||
| 
 | 
 | ||||||
| async function fetch() { | async function fetch() { | ||||||
| 	if (iAmModerator) { | 	if (iAmModerator) { | ||||||
|  | @ -184,11 +183,26 @@ const headerActions = $computed(() => [{ | ||||||
| 	}, | 	}, | ||||||
| }]); | }]); | ||||||
| 
 | 
 | ||||||
| const headerTabs = $computed(() => []); | const headerTabs = $computed(() => [{ | ||||||
|  | 	active: tab === 'overview', | ||||||
|  | 	title: i18n.ts.overview, | ||||||
|  | 	icon: 'fas fa-info-circle', | ||||||
|  | 	onClick: () => { tab = 'overview'; }, | ||||||
|  | }, { | ||||||
|  | 	active: tab === 'chart', | ||||||
|  | 	title: i18n.ts.charts, | ||||||
|  | 	icon: 'fas fa-chart-simple', | ||||||
|  | 	onClick: () => { tab = 'chart'; }, | ||||||
|  | }, { | ||||||
|  | 	active: tab === 'raw', | ||||||
|  | 	title: 'Raw data', | ||||||
|  | 	icon: 'fas fa-code', | ||||||
|  | 	onClick: () => { tab = 'raw'; }, | ||||||
|  | }]); | ||||||
| 
 | 
 | ||||||
| definePageMetadata({ | definePageMetadata({ | ||||||
| 	title: props.host, | 	title: props.host, | ||||||
| 	icon: 'fas fa-info-circle', | 	icon: 'fas fa-server', | ||||||
| 	bg: 'var(--bg)', | 	bg: 'var(--bg)', | ||||||
| }); | }); | ||||||
| </script> | </script> | ||||||
|  | @ -208,5 +222,12 @@ definePageMetadata({ | ||||||
| 		display: flex; | 		display: flex; | ||||||
| 		margin: 0 0 16px 0; | 		margin: 0 0 16px 0; | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	> .charts { | ||||||
|  | 		> .label { | ||||||
|  | 			margin-bottom: 12px; | ||||||
|  | 			font-weight: bold; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
| </style> | </style> | ||||||
|  |  | ||||||
|  | @ -11,13 +11,13 @@ export class I18n<T extends Record<string, any>> { | ||||||
| 
 | 
 | ||||||
| 	// string にしているのは、ドット区切りでのパス指定を許可するため
 | 	// string にしているのは、ドット区切りでのパス指定を許可するため
 | ||||||
| 	// なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
 | 	// なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
 | ||||||
| 	public t(key: string, args?: Record<string, string>): string { | 	public t(key: string, args?: Record<string, string | number>): string { | ||||||
| 		try { | 		try { | ||||||
| 			let str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string; | 			let str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string; | ||||||
| 
 | 
 | ||||||
| 			if (args) { | 			if (args) { | ||||||
| 				for (const [k, v] of Object.entries(args)) { | 				for (const [k, v] of Object.entries(args)) { | ||||||
| 					str = str.replace(`{${k}}`, v); | 					str = str.replace(`{${k}}`, v.toString()); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			return str; | 			return str; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue