feat: 🎸 update network state (#950)
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -654,9 +654,11 @@
|
||||
"enrollNow": "加入练习",
|
||||
"enrollments": "参加的课程",
|
||||
"noLikesYet": "还没有点赞",
|
||||
"apiConnectTime": "API 延时 ({{apiUrl}})",
|
||||
"apiConnectTime": "接口服务器延时 ({{apiUrl}})",
|
||||
"storageConnectTime": "资源服务器延时",
|
||||
"ipInfo": "IP 信息",
|
||||
"platformInfo": "设备信息",
|
||||
"networkState": "网络状态",
|
||||
"connectError": "连接错误"
|
||||
"connectError": "连接错误",
|
||||
"refresh": "刷新"
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user