feat: 🎸 update network state (#950)

This commit is contained in:
divisey
2024-08-09 14:59:24 +08:00
committed by GitHub
parent c32745f374
commit e480795383
3 changed files with 148 additions and 114 deletions

View File

@@ -655,8 +655,10 @@
"enrollments": "Enrollments",
"noLikesYet": "No likes yet",
"apiConnectTime": "API Connect Time ({{apiUrl}})",
"storageConnectTime": "Storage Connect Time",
"ipInfo": "IP Information",
"platformInfo": "Platform Information",
"networkState": "Network State",
"connectError": "Connect Error"
"connectError": "Connect Error",
"refresh": "refresh"
}

View File

@@ -654,9 +654,11 @@
"enrollNow": "加入练习",
"enrollments": "参加的课程",
"noLikesYet": "还没有点赞",
"apiConnectTime": "API 延时 ({{apiUrl}})",
"apiConnectTime": "接口服务器延时 ({{apiUrl}})",
"storageConnectTime": "资源服务器延时",
"ipInfo": "IP 信息",
"platformInfo": "设备信息",
"networkState": "网络状态",
"connectError": "连接错误"
"connectError": "连接错误",
"refresh": "刷新"
}

View File

@@ -1,77 +1,133 @@
import { Client } from "@/api";
import { t } from "i18next";
import { useState, useContext, useEffect, useMemo } from "react";
import React, {
useState,
useContext,
useEffect,
useMemo,
useRef,
useImperativeHandle,
} from "react";
import { AppSettingsProviderContext } from "@renderer/context";
import { LoaderIcon } from "lucide-react";
import { STORAGE_WORKER_ENDPOINT } from "@/constants";
import { Button } from "@/renderer/components/ui";
export const NetworkState = () => {
const { apiUrl, EnjoyApp } = useContext(AppSettingsProviderContext);
let timeoutId: ReturnType<typeof setTimeout | null> = null;
const [apiConnected, setApiConnected] = useState(false);
const [apiConnecting, setApiConnecting] = useState(false);
const [apiConnectError, setApiConnectError] = useState(false);
const [apiConnectTime, setApiConnectTime] = useState<number>(null);
const [ipInfoError, setIpInfoError] = useState(false);
const [ipInfo, setIpInfo] = useState(null);
const [platformInfo, setPlatformInfo] = useState<PlatformInfo>(null);
const [refreshing, setRefreshing] = useState(false);
const apiStateRef = useRef(null);
const storeageStateRef = useRef(null);
const ipStateRef = useRef(null);
const items = useMemo(() => {
const apiStateColor =
apiConnectTime < 200
? "text-green-500"
: apiConnectTime < 800
? "text-yellow-500"
: "text-red-500";
return [
{
title: t("apiConnectTime", { apiUrl }),
loading: !apiConnected && apiConnecting,
error: apiConnectError,
value: <span className={apiStateColor}>{apiConnectTime}ms</span>,
ref: apiStateRef,
refresh: true,
action: () => getConnectDelayTime(apiUrl + "/up"),
},
{
title: t("storageConnectTime"),
refresh: true,
ref: storeageStateRef,
action: () => getConnectDelayTime(STORAGE_WORKER_ENDPOINT),
},
{
title: t("ipInfo"),
loading: false,
error: ipInfoError,
value: (
<span>
{ipInfo
? `${ipInfo.ip} (${ipInfo.city}, ${ipInfo.country_name})`
: "-"}
</span>
),
ref: ipStateRef,
refresh: true,
action: async () =>
await fetch("https://ipapi.co/json")
.then((resp) => resp.json())
.then((info) => ({
text: `${info.ip} (${info.city}, ${info.country_name})`,
})),
},
{
title: t("platformInfo"),
loading: false,
error: false,
value: (
<span>
{platformInfo
? `${platformInfo.platform} ${platformInfo.arch} ${platformInfo.version}`
: "-"}
</span>
),
refresh: false,
action: async () =>
await EnjoyApp.app.getPlatformInfo().then((info) => ({
text: `${info.platform} ${info.arch} ${info.version}`,
})),
},
];
}, [
apiConnectTime,
apiConnecting,
apiConnected,
apiConnectError,
ipInfo,
ipInfoError,
platformInfo,
]);
}, [apiUrl]);
async function handleRefresh() {
if (refreshing) return;
setRefreshing(true);
await Promise.all([
apiStateRef?.current.getConnectState({ force: true }),
storeageStateRef?.current.getConnectState({ force: true }),
ipStateRef?.current.getConnectState({ force: true }),
]);
setRefreshing(false);
}
async function getConnectDelayTime(url: string) {
const startTime = new Date().getTime();
await fetch(url);
const duration = new Date().getTime() - startTime;
return {
color:
duration < 200
? "text-green-500"
: duration < 800
? "text-yellow-500"
: "text-red-500",
text: `${duration}ms`,
};
}
return (
<div className="py-4">
<div className="flex items-start justify-between">
<div className="mb-2">{t("networkState")}</div>
<Button variant="secondary" size="sm" onClick={handleRefresh}>
{t("refresh")}
</Button>
</div>
<div>
{items.map((item, index) => (
<NetworkStateItem key={index} {...item} />
))}
</div>
</div>
);
};
const NetworkStateItem = React.forwardRef(function (
{
title,
action,
refresh,
}: {
title: string;
refresh: boolean;
action: () => Promise<{ color?: string; text: string }>;
},
ref
) {
useImperativeHandle(ref, () => {
return { getConnectState };
});
let timeoutId: ReturnType<typeof setTimeout | null> = null;
const [connected, setConnected] = useState(false);
const [connecting, setConnecting] = useState(false);
const [connectError, setConnectError] = useState(false);
const [color, setColor] = useState("");
const [text, setText] = useState("");
useEffect(() => {
pollingAction();
getPlatformInfo();
startPolling();
return () => {
if (timeoutId) {
@@ -80,71 +136,45 @@ export const NetworkState = () => {
};
}, []);
async function pollingAction() {
await Promise.all([getApiConnectTime(), getIpInfo()]);
async function startPolling() {
await getConnectState();
timeoutId = setTimeout(() => pollingAction(), 10000);
}
async function getApiConnectTime() {
setApiConnecting(true);
try {
const client = new Client({ baseUrl: apiUrl });
const startTime = new Date().getTime();
await client.up();
const endTime = new Date().getTime();
setApiConnectTime(endTime - startTime);
setApiConnected(true);
} catch (error) {
setApiConnectError(true);
setApiConnected(false);
}
setApiConnecting(false);
}
async function getIpInfo() {
try {
await fetch("https://ipapi.co/json")
.then((resp) => resp.json())
.then((info) => setIpInfo(info));
} catch (error) {
setIpInfoError(true);
if (refresh) {
timeoutId = setTimeout(() => startPolling(), 10000);
}
}
async function getPlatformInfo() {
const info = await EnjoyApp.app.getPlatformInfo();
setPlatformInfo(info);
async function getConnectState(
{ force }: { force?: boolean } = { force: false }
) {
if (force) setConnected(false);
setConnecting(true);
try {
const { color, text } = await action();
setColor(color);
setText(text);
setConnected(true);
} catch (error) {
setConnectError(true);
setConnected(false);
}
setConnecting(false);
}
return (
<div className="py-4">
<div className="flex items-start justify-between">
<div className="mb-2">{t("networkState")}</div>
</div>
<div>
{items.map((item, index) => {
return (
<div
key={index}
className="text-sm text-muted-foreground flex justify-between my-2"
>
<div className="">{item.title}</div>
<div className="">
{item.loading ? (
<LoaderIcon className="w-4 h-4 animate-spin" />
) : item.error ? (
<span className="text-red-500">{t("connectError")}</span>
) : (
item.value
)}
</div>
</div>
);
})}
<div className="text-sm text-muted-foreground flex justify-between my-2">
<div className="">{title}</div>
<div className="">
{!connected && connecting ? (
<LoaderIcon className="w-4 h-4 animate-spin" />
) : connectError ? (
<span className="text-red-500">{t("connectError")}</span>
) : (
<span className={color}>{text}</span>
)}
</div>
</div>
);
};
});