mirror of
https://github.com/clash-verge-rev/clash-verge-rev
synced 2025-05-05 02:53:43 +08:00
feat: unlock test page
This commit is contained in:
parent
36142656a4
commit
bcaafa67a3
5988
scripts/check.sh
Normal file
5988
scripts/check.sh
Normal file
File diff suppressed because one or more lines are too long
53
src-tauri/Cargo.lock
generated
53
src-tauri/Cargo.lock
generated
@ -1157,6 +1157,7 @@ dependencies = [
|
|||||||
"parking_lot",
|
"parking_lot",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"port_scanner",
|
"port_scanner",
|
||||||
|
"regex",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"reqwest_dav",
|
"reqwest_dav",
|
||||||
"runas",
|
"runas",
|
||||||
@ -1351,10 +1352,29 @@ version = "0.18.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
"time",
|
"time",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cookie_store"
|
||||||
|
version = "0.21.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2eac901828f88a5241ee0600950ab981148a18f2f756900ffba1b125ca6a3ef9"
|
||||||
|
dependencies = [
|
||||||
|
"cookie",
|
||||||
|
"document-features",
|
||||||
|
"idna",
|
||||||
|
"log",
|
||||||
|
"publicsuffix",
|
||||||
|
"serde",
|
||||||
|
"serde_derive",
|
||||||
|
"serde_json",
|
||||||
|
"time",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@ -1929,6 +1949,15 @@ dependencies = [
|
|||||||
"const-random",
|
"const-random",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "document-features"
|
||||||
|
version = "0.2.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d"
|
||||||
|
dependencies = [
|
||||||
|
"litrs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "downcast-rs"
|
name = "downcast-rs"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@ -3752,6 +3781,12 @@ version = "0.7.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
|
checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "litrs"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "local-ip-address"
|
name = "local-ip-address"
|
||||||
version = "0.5.7"
|
version = "0.5.7"
|
||||||
@ -5444,6 +5479,22 @@ dependencies = [
|
|||||||
"prost",
|
"prost",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "psl-types"
|
||||||
|
version = "2.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "publicsuffix"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f42ea446cab60335f76979ec15e12619a2165b5ae2c12166bef27d283a9fadf"
|
||||||
|
dependencies = [
|
||||||
|
"idna",
|
||||||
|
"psl-types",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "qoi"
|
name = "qoi"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@ -5849,6 +5900,8 @@ checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"cookie",
|
||||||
|
"cookie_store",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
@ -37,7 +37,8 @@ percent-encoding = "2.3.1"
|
|||||||
window-shadows = { version = "0.2.2" }
|
window-shadows = { version = "0.2.2" }
|
||||||
tokio = { version = "1.43", features = ["full"] }
|
tokio = { version = "1.43", features = ["full"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
|
reqwest = { version = "0.12", features = ["json", "rustls-tls", "cookies"] }
|
||||||
|
regex = "1.10.5"
|
||||||
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", rev = "3d748b5" }
|
sysproxy = { git = "https://github.com/clash-verge-rev/sysproxy-rs", rev = "3d748b5" }
|
||||||
image = "0.25.5"
|
image = "0.25.5"
|
||||||
imageproc = "0.25.0"
|
imageproc = "0.25.0"
|
||||||
|
1204
src-tauri/src/cmd/media_unlock_checker.rs
Normal file
1204
src-tauri/src/cmd/media_unlock_checker.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -6,6 +6,7 @@ pub type CmdResult<T = ()> = Result<T, String>;
|
|||||||
// Command modules
|
// Command modules
|
||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod clash;
|
pub mod clash;
|
||||||
|
pub mod media_unlock_checker;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
pub mod profile;
|
pub mod profile;
|
||||||
pub mod proxy;
|
pub mod proxy;
|
||||||
@ -20,6 +21,7 @@ pub mod webdav;
|
|||||||
// Re-export all command functions for backwards compatibility
|
// Re-export all command functions for backwards compatibility
|
||||||
pub use app::*;
|
pub use app::*;
|
||||||
pub use clash::*;
|
pub use clash::*;
|
||||||
|
pub use media_unlock_checker::*;
|
||||||
pub use network::*;
|
pub use network::*;
|
||||||
pub use profile::*;
|
pub use profile::*;
|
||||||
pub use proxy::*;
|
pub use proxy::*;
|
||||||
|
@ -209,6 +209,9 @@ pub fn run() {
|
|||||||
cmd::export_diagnostic_info,
|
cmd::export_diagnostic_info,
|
||||||
// get system info for display
|
// get system info for display
|
||||||
cmd::get_system_info,
|
cmd::get_system_info,
|
||||||
|
// media unlock checker
|
||||||
|
cmd::get_unlock_items,
|
||||||
|
cmd::check_media_unlock,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
21
src/components/center.tsx
Normal file
21
src/components/center.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Box, BoxProps } from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface CenterProps extends BoxProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Center: React.FC<CenterProps> = ({ children, ...props }) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
display="flex"
|
||||||
|
justifyContent="center"
|
||||||
|
alignItems="center"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
@ -22,6 +22,7 @@
|
|||||||
"Label-Connections": "Connections",
|
"Label-Connections": "Connections",
|
||||||
"Label-Rules": "Rules",
|
"Label-Rules": "Rules",
|
||||||
"Label-Logs": "Logs",
|
"Label-Logs": "Logs",
|
||||||
|
"Label-Unlock": "Test",
|
||||||
"Label-Settings": "Settings",
|
"Label-Settings": "Settings",
|
||||||
"Proxies": "Proxies",
|
"Proxies": "Proxies",
|
||||||
"Proxy Groups": "Proxy Groups",
|
"Proxy Groups": "Proxy Groups",
|
||||||
@ -190,6 +191,7 @@
|
|||||||
"Clear": "Clear",
|
"Clear": "Clear",
|
||||||
"Test": "Test",
|
"Test": "Test",
|
||||||
"Test All": "Test All",
|
"Test All": "Test All",
|
||||||
|
"Testing...": "Testing...",
|
||||||
"Create Test": "Create Test",
|
"Create Test": "Create Test",
|
||||||
"Edit Test": "Edit Test",
|
"Edit Test": "Edit Test",
|
||||||
"Icon": "Icon",
|
"Icon": "Icon",
|
||||||
@ -555,5 +557,13 @@
|
|||||||
"ORG": "ORG",
|
"ORG": "ORG",
|
||||||
"Location": "Location",
|
"Location": "Location",
|
||||||
"Timezone": "Timezone",
|
"Timezone": "Timezone",
|
||||||
"Auto refresh": "Auto refresh"
|
"Auto refresh": "Auto refresh",
|
||||||
|
"Unlock Test": "Unlock Test",
|
||||||
|
"Pending": "Pending",
|
||||||
|
"Yes": "Yes",
|
||||||
|
"No": "No",
|
||||||
|
"Failed": "Failed",
|
||||||
|
"Completed": "Completed",
|
||||||
|
"Bahamut Anime": "Bahamut Anime",
|
||||||
|
"Disallowed ISP": "Disallowed ISP"
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
"Label-Connections": "连 接",
|
"Label-Connections": "连 接",
|
||||||
"Label-Rules": "规 则",
|
"Label-Rules": "规 则",
|
||||||
"Label-Logs": "日 志",
|
"Label-Logs": "日 志",
|
||||||
|
"Label-Unlock": "测 试",
|
||||||
"Label-Settings": "设 置",
|
"Label-Settings": "设 置",
|
||||||
"Proxies": "代理",
|
"Proxies": "代理",
|
||||||
"Proxy Groups": "代理组",
|
"Proxy Groups": "代理组",
|
||||||
@ -190,6 +191,7 @@
|
|||||||
"Clear": "清除",
|
"Clear": "清除",
|
||||||
"Test": "测试",
|
"Test": "测试",
|
||||||
"Test All": "测试全部",
|
"Test All": "测试全部",
|
||||||
|
"Testing...": "测试中...",
|
||||||
"Create Test": "新建测试",
|
"Create Test": "新建测试",
|
||||||
"Edit Test": "编辑测试",
|
"Edit Test": "编辑测试",
|
||||||
"Icon": "图标",
|
"Icon": "图标",
|
||||||
@ -555,5 +557,13 @@
|
|||||||
"ORG": "组织",
|
"ORG": "组织",
|
||||||
"Location": "位置",
|
"Location": "位置",
|
||||||
"Timezone": "时区",
|
"Timezone": "时区",
|
||||||
"Auto refresh": "自动刷新"
|
"Auto refresh": "自动刷新",
|
||||||
|
"Unlock Test": "解锁测试",
|
||||||
|
"Pending": "待检测",
|
||||||
|
"Yes": "支持",
|
||||||
|
"No": "不支持",
|
||||||
|
"Failed": "测试失败",
|
||||||
|
"Completed": "检测完成",
|
||||||
|
"Bahamut Anime": "动画疯",
|
||||||
|
"Disallowed ISP": "不允许的 ISP"
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import SettingsPage from "./settings";
|
|||||||
import ConnectionsPage from "./connections";
|
import ConnectionsPage from "./connections";
|
||||||
import RulesPage from "./rules";
|
import RulesPage from "./rules";
|
||||||
import HomePage from "./home";
|
import HomePage from "./home";
|
||||||
|
import UnlockPage from "./unlock";
|
||||||
import { BaseErrorBoundary } from "@/components/base";
|
import { BaseErrorBoundary } from "@/components/base";
|
||||||
|
|
||||||
import ProxiesSvg from "@/assets/image/itemicon/proxies.svg?react";
|
import ProxiesSvg from "@/assets/image/itemicon/proxies.svg?react";
|
||||||
@ -22,6 +23,7 @@ import SubjectRoundedIcon from "@mui/icons-material/SubjectRounded";
|
|||||||
import WifiTetheringRoundedIcon from "@mui/icons-material/WifiTetheringRounded";
|
import WifiTetheringRoundedIcon from "@mui/icons-material/WifiTetheringRounded";
|
||||||
import SettingsRoundedIcon from "@mui/icons-material/SettingsRounded";
|
import SettingsRoundedIcon from "@mui/icons-material/SettingsRounded";
|
||||||
import HomeRoundedIcon from "@mui/icons-material/HomeRounded";
|
import HomeRoundedIcon from "@mui/icons-material/HomeRounded";
|
||||||
|
import LockOpenRoundedIcon from "@mui/icons-material/LockOpenRounded";
|
||||||
|
|
||||||
export const routers = [
|
export const routers = [
|
||||||
{
|
{
|
||||||
@ -60,6 +62,12 @@ export const routers = [
|
|||||||
icon: [<SubjectRoundedIcon />, <LogsSvg />],
|
icon: [<SubjectRoundedIcon />, <LogsSvg />],
|
||||||
element: <LogsPage />,
|
element: <LogsPage />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Label-Unlock",
|
||||||
|
path: "/unlock",
|
||||||
|
icon: [<LockOpenRoundedIcon />],
|
||||||
|
element: <UnlockPage />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: "Label-Settings",
|
label: "Label-Settings",
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
|
395
src/pages/unlock.tsx
Normal file
395
src/pages/unlock.tsx
Normal file
@ -0,0 +1,395 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Divider,
|
||||||
|
Typography,
|
||||||
|
Chip,
|
||||||
|
Tooltip,
|
||||||
|
CircularProgress,
|
||||||
|
alpha,
|
||||||
|
useTheme,
|
||||||
|
} from "@mui/material";
|
||||||
|
import Grid from "@mui/material/Grid2";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { BasePage, BaseEmpty } from "@/components/base";
|
||||||
|
import { useLockFn } from "ahooks";
|
||||||
|
import {
|
||||||
|
CheckCircleOutlined,
|
||||||
|
CancelOutlined,
|
||||||
|
HelpOutline,
|
||||||
|
PendingOutlined,
|
||||||
|
RefreshRounded,
|
||||||
|
AccessTimeOutlined,
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
|
||||||
|
// 定义流媒体检测项类型
|
||||||
|
interface UnlockItem {
|
||||||
|
name: string;
|
||||||
|
status: string;
|
||||||
|
region?: string | null;
|
||||||
|
check_time?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用于存储测试结果的本地存储键名
|
||||||
|
const UNLOCK_RESULTS_STORAGE_KEY = "clash_verge_unlock_results";
|
||||||
|
const UNLOCK_RESULTS_TIME_KEY = "clash_verge_unlock_time";
|
||||||
|
|
||||||
|
const UnlockPage = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
// 保存所有流媒体检测项的状态
|
||||||
|
const [unlockItems, setUnlockItems] = useState<UnlockItem[]>([]);
|
||||||
|
// 是否正在执行全部检测
|
||||||
|
const [isCheckingAll, setIsCheckingAll] = useState(false);
|
||||||
|
// 记录正在检测中的项目
|
||||||
|
const [loadingItems, setLoadingItems] = useState<string[]>([]);
|
||||||
|
// 最后检测时间
|
||||||
|
const [lastCheckTime, setLastCheckTime] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// 按首字母排序项目
|
||||||
|
const sortItemsByName = (items: UnlockItem[]) => {
|
||||||
|
return [...items].sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 保存测试结果到本地存储
|
||||||
|
const saveResultsToStorage = (items: UnlockItem[], time: string | null) => {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(UNLOCK_RESULTS_STORAGE_KEY, JSON.stringify(items));
|
||||||
|
if (time) {
|
||||||
|
localStorage.setItem(UNLOCK_RESULTS_TIME_KEY, time);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to save results to storage:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 从本地存储加载测试结果
|
||||||
|
const loadResultsFromStorage = (): {
|
||||||
|
items: UnlockItem[] | null;
|
||||||
|
time: string | null;
|
||||||
|
} => {
|
||||||
|
try {
|
||||||
|
const itemsJson = localStorage.getItem(UNLOCK_RESULTS_STORAGE_KEY);
|
||||||
|
const time = localStorage.getItem(UNLOCK_RESULTS_TIME_KEY);
|
||||||
|
|
||||||
|
if (itemsJson) {
|
||||||
|
return {
|
||||||
|
items: JSON.parse(itemsJson) as UnlockItem[],
|
||||||
|
time,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to load results from storage:", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { items: null, time: null };
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载时获取初始检测项列表
|
||||||
|
useEffect(() => {
|
||||||
|
// 尝试从本地存储加载上次测试结果
|
||||||
|
const { items: storedItems, time } = loadResultsFromStorage();
|
||||||
|
|
||||||
|
if (storedItems && storedItems.length > 0) {
|
||||||
|
// 如果有存储的结果,优先使用
|
||||||
|
setUnlockItems(storedItems);
|
||||||
|
setLastCheckTime(time);
|
||||||
|
|
||||||
|
// 后台同时获取最新的初始状态(但不更新UI)
|
||||||
|
getUnlockItems(false);
|
||||||
|
} else {
|
||||||
|
// 没有存储的结果,获取初始状态
|
||||||
|
getUnlockItems(true);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 获取所有解锁检测项列表
|
||||||
|
const getUnlockItems = async (updateUI: boolean = true) => {
|
||||||
|
try {
|
||||||
|
const items = await invoke<UnlockItem[]>("get_unlock_items");
|
||||||
|
const sortedItems = sortItemsByName(items);
|
||||||
|
|
||||||
|
if (updateUI) {
|
||||||
|
setUnlockItems(sortedItems);
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Failed to get unlock items:", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 执行全部项目检测
|
||||||
|
const checkAllMedia = useLockFn(async () => {
|
||||||
|
try {
|
||||||
|
setIsCheckingAll(true);
|
||||||
|
const result = await invoke<UnlockItem[]>("check_media_unlock");
|
||||||
|
const sortedItems = sortItemsByName(result);
|
||||||
|
|
||||||
|
// 更新UI
|
||||||
|
setUnlockItems(sortedItems);
|
||||||
|
const currentTime = new Date().toLocaleString();
|
||||||
|
setLastCheckTime(currentTime);
|
||||||
|
|
||||||
|
// 保存结果到本地存储
|
||||||
|
saveResultsToStorage(sortedItems, currentTime);
|
||||||
|
|
||||||
|
setIsCheckingAll(false);
|
||||||
|
} catch (err: any) {
|
||||||
|
setIsCheckingAll(false);
|
||||||
|
console.error("Failed to check media unlock:", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 根据项目名称检测单个流媒体服务
|
||||||
|
const checkSingleMedia = useLockFn(async (name: string) => {
|
||||||
|
try {
|
||||||
|
// 将该项目添加到加载状态
|
||||||
|
setLoadingItems((prev) => [...prev, name]);
|
||||||
|
|
||||||
|
// 执行检测
|
||||||
|
const result = await invoke<UnlockItem[]>("check_media_unlock");
|
||||||
|
|
||||||
|
// 找到对应的检测结果
|
||||||
|
const targetItem = result.find((item: UnlockItem) => item.name === name);
|
||||||
|
|
||||||
|
if (targetItem) {
|
||||||
|
// 更新单个检测项结果并按名称排序
|
||||||
|
const updatedItems = sortItemsByName(
|
||||||
|
unlockItems.map((item: UnlockItem) =>
|
||||||
|
item.name === name ? targetItem : item,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// 更新UI
|
||||||
|
setUnlockItems(updatedItems);
|
||||||
|
const currentTime = new Date().toLocaleString();
|
||||||
|
setLastCheckTime(currentTime);
|
||||||
|
|
||||||
|
// 保存结果到本地存储
|
||||||
|
saveResultsToStorage(updatedItems, currentTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除加载状态
|
||||||
|
setLoadingItems((prev) => prev.filter((item) => item !== name));
|
||||||
|
} catch (err: any) {
|
||||||
|
setLoadingItems((prev) => prev.filter((item) => item !== name));
|
||||||
|
console.error(`Failed to check ${name}:`, err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取状态对应的颜色
|
||||||
|
const getStatusColor = (status: string) => {
|
||||||
|
if (status === "Pending") return "default";
|
||||||
|
if (status === "Yes") return "success";
|
||||||
|
if (status === "No") return "error";
|
||||||
|
if (status === "Soon") return "warning";
|
||||||
|
if (status.includes("Failed")) return "error";
|
||||||
|
if (status === "Completed") return "info";
|
||||||
|
if (
|
||||||
|
status === "Disallowed ISP" ||
|
||||||
|
status === "Blocked" ||
|
||||||
|
status === "Unsupported Country"
|
||||||
|
) {
|
||||||
|
return "error";
|
||||||
|
}
|
||||||
|
return "default";
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取状态对应的图标
|
||||||
|
const getStatusIcon = (status: string) => {
|
||||||
|
if (status === "Pending") return <PendingOutlined />;
|
||||||
|
if (status === "Yes") return <CheckCircleOutlined />;
|
||||||
|
if (status === "No") return <CancelOutlined />;
|
||||||
|
if (status === "Soon") return <AccessTimeOutlined />;
|
||||||
|
if (status.includes("Failed")) return <HelpOutline />;
|
||||||
|
return <HelpOutline />;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取状态对应的背景色
|
||||||
|
const getStatusBgColor = (status: string) => {
|
||||||
|
if (status === "Yes") return alpha(theme.palette.success.main, 0.05);
|
||||||
|
if (status === "No") return alpha(theme.palette.error.main, 0.05);
|
||||||
|
if (status === "Soon") return alpha(theme.palette.warning.main, 0.05);
|
||||||
|
if (status.includes("Failed")) return alpha(theme.palette.error.main, 0.03);
|
||||||
|
if (status === "Completed") return alpha(theme.palette.info.main, 0.05);
|
||||||
|
return "transparent";
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取状态对应的边框色
|
||||||
|
const getStatusBorderColor = (status: string) => {
|
||||||
|
if (status === "Yes") return theme.palette.success.main;
|
||||||
|
if (status === "No") return theme.palette.error.main;
|
||||||
|
if (status === "Soon") return theme.palette.warning.main;
|
||||||
|
if (status.includes("Failed")) return theme.palette.error.main;
|
||||||
|
if (status === "Completed") return theme.palette.info.main;
|
||||||
|
return theme.palette.divider;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDark = theme.palette.mode === "dark";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BasePage
|
||||||
|
title={t("Unlock Test")}
|
||||||
|
header={
|
||||||
|
<Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="small"
|
||||||
|
disabled={isCheckingAll}
|
||||||
|
onClick={checkAllMedia}
|
||||||
|
startIcon={
|
||||||
|
isCheckingAll ? (
|
||||||
|
<CircularProgress size={16} color="inherit" />
|
||||||
|
) : (
|
||||||
|
<RefreshRounded />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{isCheckingAll ? t("Testing...") : t("Test All")}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{unlockItems.length === 0 ? (
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
height: "50%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<BaseEmpty text={t("No unlock test items")} />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Grid container spacing={1.5} columns={{ xs: 1, sm: 2, md: 3 }}>
|
||||||
|
{unlockItems.map((item) => (
|
||||||
|
<Grid size={1}>
|
||||||
|
<Card
|
||||||
|
variant="outlined"
|
||||||
|
sx={{
|
||||||
|
height: "100%",
|
||||||
|
borderRadius: 2,
|
||||||
|
borderLeft: `4px solid ${getStatusBorderColor(item.status)}`,
|
||||||
|
backgroundColor: isDark ? "#282a36" : "#ffffff",
|
||||||
|
position: "relative",
|
||||||
|
overflow: "hidden",
|
||||||
|
"&:hover": {
|
||||||
|
backgroundColor: isDark
|
||||||
|
? alpha(theme.palette.primary.dark, 0.05)
|
||||||
|
: alpha(theme.palette.primary.light, 0.05),
|
||||||
|
},
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{ p: 1.3, flex: 1 }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="subtitle1"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "1rem",
|
||||||
|
color: "text.primary",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</Typography>
|
||||||
|
<Tooltip title={t("Test now")}>
|
||||||
|
<span>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
color="primary"
|
||||||
|
disabled={
|
||||||
|
loadingItems.includes(item.name) || isCheckingAll
|
||||||
|
}
|
||||||
|
sx={{
|
||||||
|
minWidth: "32px",
|
||||||
|
width: "32px",
|
||||||
|
height: "32px",
|
||||||
|
//p: 0,
|
||||||
|
borderRadius: "50%",
|
||||||
|
}}
|
||||||
|
onClick={() => checkSingleMedia(item.name)}
|
||||||
|
>
|
||||||
|
{loadingItems.includes(item.name) ? (
|
||||||
|
<CircularProgress size={16} />
|
||||||
|
) : (
|
||||||
|
<RefreshRounded fontSize="small" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
flexWrap: "wrap",
|
||||||
|
gap: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Chip
|
||||||
|
label={t(item.status)}
|
||||||
|
color={getStatusColor(item.status)}
|
||||||
|
size="small"
|
||||||
|
icon={getStatusIcon(item.status)}
|
||||||
|
sx={{
|
||||||
|
fontWeight:
|
||||||
|
item.status === "Pending" ? "normal" : "bold",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{item.region && (
|
||||||
|
<Chip
|
||||||
|
label={item.region}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
color="info"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Divider
|
||||||
|
sx={{
|
||||||
|
borderStyle: "dashed",
|
||||||
|
borderColor: alpha(theme.palette.divider, 0.2),
|
||||||
|
mx: 1,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box sx={{ px: 1.5, py: 0.2 }}>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
sx={{
|
||||||
|
display: "block",
|
||||||
|
color: "text.secondary",
|
||||||
|
fontSize: "0.7rem",
|
||||||
|
textAlign: "right",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.check_time || "-- --"}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
|
</BasePage>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UnlockPage;
|
Loading…
x
Reference in New Issue
Block a user