From 2cbd929eac8ffcefc2c1bbca735cd7e92fdb2e22 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 7 Aug 2021 10:24:50 +0900
Subject: [PATCH] =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=82=B9=E3=82=BF=E3=83=B3?=
 =?UTF-8?q?=E3=82=B9=E4=B8=80=E8=A6=A7=E3=83=9A=E3=83=BC=E3=82=B8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 locales/ja-JP.yml                             |   2 +
 src/client/menu.ts                            |   5 +
 .../pages/{instance => }/federation.vue       | 198 ++++++++++++------
 src/client/pages/instance/index.vue           |   2 +-
 4 files changed, 146 insertions(+), 61 deletions(-)
 rename src/client/pages/{instance => }/federation.vue (50%)

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 08fd3c912..eed17edb1 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -765,6 +765,8 @@ customCss: "カスタムCSS"
 customCssWarn: "この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。"
 global: "グローバル"
 squareAvatars: "アイコンを四角形で表示"
+sent: "送信"
+received: "受信"
 searchResult: "検索結果"
 
 _docs: 
diff --git a/src/client/menu.ts b/src/client/menu.ts
index b00fa7a6a..0b5341f97 100644
--- a/src/client/menu.ts
+++ b/src/client/menu.ts
@@ -113,6 +113,11 @@ export const menuDef = {
 		icon: 'fas fa-satellite-dish',
 		to: '/channels',
 	},
+	federation: {
+		title: 'federation',
+		icon: 'fas fa-globe',
+		to: '/federation',
+	},
 	emojis: {
 		title: 'emojis',
 		icon: 'fas fa-laugh',
diff --git a/src/client/pages/instance/federation.vue b/src/client/pages/federation.vue
similarity index 50%
rename from src/client/pages/instance/federation.vue
rename to src/client/pages/federation.vue
index 96f72fed4..59867b8e5 100644
--- a/src/client/pages/instance/federation.vue
+++ b/src/client/pages/federation.vue
@@ -1,9 +1,12 @@
 <template>
-<div class="enuoauvw">
+<div class="taeiyria">
 	<div class="query">
-		<MkInput v-model:value="host" :debounce="true"><span>{{ $ts.host }}</span></MkInput>
-		<div class="inputs" style="display: flex;">
-			<MkSelect v-model:value="state" style="margin: 0; flex: 1;">
+		<MkInput v-model="host" :debounce="true" class="_inputNoTopMargin">
+			<template #prefix><i class="fas fa-search"></i></template>
+			<template #label>{{ $ts.host }}</template>
+		</MkInput>
+		<div class="_inputSplit _inputNoBottomMargin">
+			<MkSelect v-model="state">
 				<template #label>{{ $ts.state }}</template>
 				<option value="all">{{ $ts.all }}</option>
 				<option value="federating">{{ $ts.federating }}</option>
@@ -13,7 +16,7 @@
 				<option value="blocked">{{ $ts.blocked }}</option>
 				<option value="notResponding">{{ $ts.notResponding }}</option>
 			</MkSelect>
-			<MkSelect v-model:value="sort" style="margin: 0; flex: 1;">
+			<MkSelect v-model="sort">
 				<template #label>{{ $ts.sort }}</template>
 				<option value="+pubSub">{{ $ts.pubSub }} ({{ $ts.descendingOrder }})</option>
 				<option value="-pubSub">{{ $ts.pubSub }} ({{ $ts.ascendingOrder }})</option>
@@ -38,16 +41,53 @@
 	</div>
 
 	<MkPagination :pagination="pagination" #default="{items}" ref="instances" :key="host + state">
-		<div class="ppgwaixt _block" v-for="instance in items" :key="instance.id" @click="info(instance)">
-			<div class="host"><i class="fas fa-circle indicator" :class="getStatus(instance)"></i><b>{{ instance.host }}</b></div>
-			<div class="status">
-				<span class="sub" v-if="instance.followersCount > 0"><i class="fas fa-caret-down icon"></i>Sub</span>
-				<span class="sub" v-else><i class="fas fa-caret-down icon"></i>-</span>
-				<span class="pub" v-if="instance.followingCount > 0"><i class="fas fa-caret-up icon"></i>Pub</span>
-				<span class="pub" v-else><i class="fas fa-caret-up icon"></i>-</span>
-				<span class="lastCommunicatedAt"><i class="fas fa-exchange-alt icon"></i><MkTime :time="instance.lastCommunicatedAt"/></span>
-				<span class="latestStatus"><i class="fas fa-traffic-light icon"></i>{{ instance.latestStatus || '-' }}</span>
-			</div>
+		<div class="dqokceoi">
+			<MkA class="instance" v-for="instance in items" :key="instance.id" :to="`/instance-info/${instance.host}`">
+				<div class="host"><img :src="instance.faviconUrl">{{ instance.host }}</div>
+				<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 class="sub" v-if="instance.followersCount > 0"><i class="fas fa-caret-down icon"></i>Sub</span>
+						<span class="sub" v-else><i class="fas fa-caret-down icon"></i>-</span>
+						<span class="pub" v-if="instance.followingCount > 0"><i class="fas fa-caret-up icon"></i>Pub</span>
+						<span class="pub" v-else><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>
 		</div>
 	</MkPagination>
 </div>
@@ -59,7 +99,6 @@ import MkButton from '@client/components/ui/button.vue';
 import MkInput from '@client/components/ui/input.vue';
 import MkSelect from '@client/components/ui/select.vue';
 import MkPagination from '@client/components/ui/pagination.vue';
-import MkInstanceInfo from './instance.vue';
 import * as os from '@client/os';
 import * as symbols from '@client/symbols';
 
@@ -117,69 +156,108 @@ export default defineComponent({
 
 	methods: {
 		getStatus(instance) {
-			if (instance.isSuspended) return 'off';
-			if (instance.isNotResponding) return 'red';
-			return 'green';
+			if (instance.isSuspended) return 'suspended';
+			if (instance.isNotResponding) return 'error';
+			return 'alive';
 		},
-
-		info(instance) {
-			os.popup(MkInstanceInfo, {
-				instance: instance
-			}, {}, 'closed');
-		}
 	}
 });
 </script>
 
 <style lang="scss" scoped>
-.enuoauvw {
+.taeiyria {
 	> .query {
-		margin: var(--margin);
+		background: var(--bg);
+		padding: 16px;
+		border-bottom: solid 0.5px var(--divider);
 	}
 }
 
-.ppgwaixt {
-	cursor: pointer;
+.dqokceoi {
+	display: grid;
+	grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
+	grid-gap: 12px;
 	padding: 16px;
 
-	&:hover {
-		color: var(--accent);
-	}
+	> .instance {
+		padding: 16px;
+		border: solid 1px var(--divider);
+		border-radius: 6px;
 
-	> .host {
-		> .indicator {
-			font-size: 70%;
-			vertical-align: baseline;
-			margin-right: 4px;
+		&:hover {
+			border: solid 1px var(--accent);
+			text-decoration: none;
+		}
 
-			&.green {
-				color: #49c5ba;
-			}
+		> .host {
+			font-weight: bold;
+			white-space: nowrap;
+			overflow: hidden;
+			text-overflow: ellipsis;
 
-			&.yellow {
-				color: #c5a549;
-			}
-
-			&.red {
-				color: #c54949;
-			}
-
-			&.off {
-				color: rgba(0, 0, 0, 0.5);
+			> img {
+				width: 18px;
+				height: 18px;
+				margin-right: 6px;
+				vertical-align: middle;
 			}
 		}
-	}
 
-	> .status {
-		display: flex;
-		align-items: center;
-		font-size: 90%;
+		> .table {
+			display: grid;
+			grid-template-columns: repeat(auto-fill, minmax(60px, 1fr));
+			grid-gap: 6px;
+			margin: 6px 0;
+			font-size: 70%;
 
-		> span {
-			flex: 1;
-			
-			> .icon {
-				margin-right: 6px;
+			> .cell {
+				> .key, > .value {
+					white-space: nowrap;
+					overflow: hidden;
+					text-overflow: ellipsis;
+				}
+
+				> .key {
+					opacity: 0.7;
+				}
+
+				> .value {
+				}
+			}
+		}
+
+		> .footer {
+			display: flex;
+			align-items: center;
+
+			> .status {
+				&.suspended {
+					opacity: 0.5;
+				}
+
+				&.error {
+					color: var(--error);
+				}
+
+				&.alive {
+					color: var(--success);
+				}
+			}
+
+			> .pubSub {
+				margin-left: 8px;
+			}
+
+			> .right {
+				margin-left: auto;
+				font-size: 0.9em;
+
+				> .latestStatus {
+					border: solid 1px var(--divider);
+					border-radius: 4px;
+					margin: 0 8px;
+					padding: 0 4px;
+				}
 			}
 		}
 	}
diff --git a/src/client/pages/instance/index.vue b/src/client/pages/instance/index.vue
index acd46518f..90dd29d55 100644
--- a/src/client/pages/instance/index.vue
+++ b/src/client/pages/instance/index.vue
@@ -100,7 +100,7 @@ export default defineComponent({
 				case 'overview': return defineAsyncComponent(() => import('./overview.vue'));
 				case 'users': return defineAsyncComponent(() => import('./users.vue'));
 				case 'emojis': return defineAsyncComponent(() => import('./emojis.vue'));
-				case 'federation': return defineAsyncComponent(() => import('./federation.vue'));
+				case 'federation': return defineAsyncComponent(() => import('../federation.vue'));
 				case 'queue': return defineAsyncComponent(() => import('./queue.vue'));
 				case 'files': return defineAsyncComponent(() => import('./files.vue'));
 				case 'announcements': return defineAsyncComponent(() => import('./announcements.vue'));