mirror of
https://github.com/Sagit-chu/flvx.git
synced 2026-10-04 01:06:36 +08:00
fix: migrate frontend to Vite 7 and resolve lint warnings
This commit is contained in:
@@ -19,7 +19,7 @@ jobs:
|
|||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '20'
|
node-version: '20.19.0'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm install --legacy-peer-deps
|
run: npm install --legacy-peer-deps
|
||||||
|
|||||||
@@ -45,7 +45,7 @@
|
|||||||
"@react-aria/visually-hidden": "3.8.25",
|
"@react-aria/visually-hidden": "3.8.25",
|
||||||
"@react-types/shared": "3.30.0",
|
"@react-types/shared": "3.30.0",
|
||||||
"@tailwindcss/postcss": "4.1.11",
|
"@tailwindcss/postcss": "4.1.11",
|
||||||
"@tailwindcss/vite": "4.1.11",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@types/react-beautiful-dnd": "^13.1.8",
|
"@types/react-beautiful-dnd": "^13.1.8",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"clsx": "2.1.1",
|
"clsx": "2.1.1",
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
"@types/react-dom": "18.3.0",
|
"@types/react-dom": "18.3.0",
|
||||||
"@typescript-eslint/eslint-plugin": "8.31.1",
|
"@typescript-eslint/eslint-plugin": "8.31.1",
|
||||||
"@typescript-eslint/parser": "8.31.1",
|
"@typescript-eslint/parser": "8.31.1",
|
||||||
"@vitejs/plugin-react": "4.4.1",
|
"@vitejs/plugin-react": "^5.1.3",
|
||||||
"eslint": "9.39.2",
|
"eslint": "9.39.2",
|
||||||
"eslint-config-prettier": "9.1.0",
|
"eslint-config-prettier": "9.1.0",
|
||||||
"eslint-plugin-import": "2.31.0",
|
"eslint-plugin-import": "2.31.0",
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
"postcss": "8.5.6",
|
"postcss": "8.5.6",
|
||||||
"prettier": "3.5.3",
|
"prettier": "3.5.3",
|
||||||
"typescript": "5.6.3",
|
"typescript": "5.6.3",
|
||||||
"vite": "5.4.21",
|
"vite": "^7.3.1",
|
||||||
"vite-tsconfig-paths": "4.3.2"
|
"vite-tsconfig-paths": "^6.0.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,9 +148,7 @@ function App() {
|
|||||||
if (cachedAppName && cachedAppName !== document.title) {
|
if (cachedAppName && cachedAppName !== document.title) {
|
||||||
document.title = cachedAppName;
|
document.title = cachedAppName;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {}
|
||||||
console.warn("检查标题更新失败:", error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 延迟检查,避免阻塞初始渲染
|
// 延迟检查,避免阻塞初始渲染
|
||||||
|
|||||||
@@ -98,8 +98,6 @@ const Network = {
|
|||||||
resolve(response.data);
|
resolve(response.data);
|
||||||
})
|
})
|
||||||
.catch(function (error: any) {
|
.catch(function (error: any) {
|
||||||
console.error("GET请求错误:", error);
|
|
||||||
|
|
||||||
// 检查是否是401错误(token失效)
|
// 检查是否是401错误(token失效)
|
||||||
if (error.response && error.response.status === 401) {
|
if (error.response && error.response.status === 401) {
|
||||||
handleTokenExpired();
|
handleTokenExpired();
|
||||||
@@ -146,8 +144,6 @@ const Network = {
|
|||||||
resolve(response.data);
|
resolve(response.data);
|
||||||
})
|
})
|
||||||
.catch(function (error: any) {
|
.catch(function (error: any) {
|
||||||
console.error("POST请求错误:", error);
|
|
||||||
|
|
||||||
// 检查是否是401错误(token失效)
|
// 检查是否是401错误(token失效)
|
||||||
if (error.response && error.response.status === 401) {
|
if (error.response && error.response.status === 401) {
|
||||||
handleTokenExpired();
|
handleTokenExpired();
|
||||||
|
|||||||
@@ -33,9 +33,7 @@ export const Navbar = () => {
|
|||||||
// 同步更新siteConfig
|
// 同步更新siteConfig
|
||||||
siteConfig.name = cachedAppName;
|
siteConfig.name = cachedAppName;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {}
|
||||||
console.warn("检查配置更新失败:", error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 延迟执行,避免阻塞初始渲染
|
// 延迟执行,避免阻塞初始渲染
|
||||||
@@ -50,9 +48,7 @@ export const Navbar = () => {
|
|||||||
setAppName(cachedAppName);
|
setAppName(cachedAppName);
|
||||||
siteConfig.name = cachedAppName;
|
siteConfig.name = cachedAppName;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {}
|
||||||
console.warn("更新配置失败:", error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener("configUpdated", handleConfigUpdate);
|
window.addEventListener("configUpdated", handleConfigUpdate);
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ export const getCachedConfigs = async (): Promise<Record<string, string>> => {
|
|||||||
|
|
||||||
return configs;
|
return configs;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
// API失败时返回缓存的数据
|
// API失败时返回缓存的数据
|
||||||
if (hasCachedData) {
|
if (hasCachedData) {
|
||||||
return cachedConfigs;
|
return cachedConfigs;
|
||||||
|
|||||||
@@ -260,9 +260,8 @@ export default function AdminLayout({
|
|||||||
} else {
|
} else {
|
||||||
toast.error(response.msg || "密码修改失败");
|
toast.error(response.msg || "密码修改失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("修改密码时发生错误");
|
toast.error("修改密码时发生错误");
|
||||||
console.error("修改密码错误:", error);
|
|
||||||
} finally {
|
} finally {
|
||||||
setPasswordLoading(false);
|
setPasswordLoading(false);
|
||||||
}
|
}
|
||||||
@@ -289,8 +288,10 @@ export default function AdminLayout({
|
|||||||
>
|
>
|
||||||
{/* 移动端遮罩层 */}
|
{/* 移动端遮罩层 */}
|
||||||
{isMobile && mobileMenuVisible && (
|
{isMobile && mobileMenuVisible && (
|
||||||
<div
|
<button
|
||||||
|
aria-label="关闭菜单"
|
||||||
className="fixed inset-0 backdrop-blur-sm bg-white/50 dark:bg-black/30 z-40"
|
className="fixed inset-0 backdrop-blur-sm bg-white/50 dark:bg-black/30 z-40"
|
||||||
|
type="button"
|
||||||
onClick={hideMobileMenu}
|
onClick={hideMobileMenu}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export default function H5SimpleLayout({
|
|||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
try {
|
try {
|
||||||
window.scrollTo({ top: 0, left: 0, behavior: "auto" });
|
window.scrollTo({ top: 0, left: 0, behavior: "auto" });
|
||||||
} catch (e) {
|
} catch {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
}
|
}
|
||||||
document.body.scrollTop = 0;
|
document.body.scrollTop = 0;
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export default function H5Layout({ children }: { children: React.ReactNode }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
window.scrollTo({ top: 0, left: 0, behavior: "auto" });
|
window.scrollTo({ top: 0, left: 0, behavior: "auto" });
|
||||||
} catch (e) {
|
} catch {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
}
|
}
|
||||||
document.body.scrollTop = 0;
|
document.body.scrollTop = 0;
|
||||||
|
|||||||
@@ -89,8 +89,7 @@ export default function ChangePasswordPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(response.msg || "账号密码修改失败");
|
toast.error(response.msg || "账号密码修改失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("修改账号密码错误:", error);
|
|
||||||
toast.error("修改账号密码时发生错误");
|
toast.error("修改账号密码时发生错误");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ const getInitialConfigs = (): Record<string, string> => {
|
|||||||
initialConfigs[key] = cachedValue;
|
initialConfigs[key] = cachedValue;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (error) {}
|
} catch {}
|
||||||
|
|
||||||
return initialConfigs;
|
return initialConfigs;
|
||||||
};
|
};
|
||||||
@@ -172,7 +172,7 @@ export default function ConfigPage() {
|
|||||||
setHasChanges(false);
|
setHasChanges(false);
|
||||||
} else {
|
} else {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
// 只有在没有缓存数据时才显示错误
|
// 只有在没有缓存数据时才显示错误
|
||||||
if (!hasInitialData) {
|
if (!hasInitialData) {
|
||||||
toast.error("加载配置出错,请重试");
|
toast.error("加载配置出错,请重试");
|
||||||
@@ -250,7 +250,7 @@ export default function ConfigPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error("保存配置失败: " + response.msg);
|
toast.error("保存配置失败: " + response.msg);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("保存配置出错,请重试");
|
toast.error("保存配置出错,请重试");
|
||||||
} finally {
|
} finally {
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
|
|||||||
@@ -207,8 +207,7 @@ export default function DashboardPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(res.msg || "获取套餐信息失败");
|
toast.error(res.msg || "获取套餐信息失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("获取套餐信息失败:", error);
|
|
||||||
toast.error("获取套餐信息失败");
|
toast.error("获取套餐信息失败");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -648,7 +647,7 @@ export default function DashboardPage() {
|
|||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
toast.success(`已复制`);
|
toast.success(`已复制`);
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("复制失败");
|
toast.error("复制失败");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -661,7 +660,7 @@ export default function DashboardPage() {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
await copyToClipboard(addressItem.address);
|
await copyToClipboard(addressItem.address);
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("复制失败");
|
toast.error("复制失败");
|
||||||
} finally {
|
} finally {
|
||||||
setAddressList((prev) =>
|
setAddressList((prev) =>
|
||||||
@@ -1189,14 +1188,15 @@ export default function DashboardPage() {
|
|||||||
{forward.name}
|
{forward.name}
|
||||||
</h4>
|
</h4>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<code
|
<button
|
||||||
className={`block px-2 py-1 bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-300 rounded font-mono text-xs truncate ${hasMultipleIps(forward.inIp) ? "cursor-pointer hover:bg-green-200 dark:hover:bg-green-500/30" : ""}`}
|
className={`block px-2 py-1 bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-300 rounded font-mono text-xs truncate ${hasMultipleIps(forward.inIp) ? "cursor-pointer hover:bg-green-200 dark:hover:bg-green-500/30" : ""}`}
|
||||||
|
disabled={!hasMultipleIps(forward.inIp)}
|
||||||
title={formatInAddress(
|
title={formatInAddress(
|
||||||
forward.inIp,
|
forward.inIp,
|
||||||
forward.inPort,
|
forward.inPort,
|
||||||
)}
|
)}
|
||||||
|
type="button"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
hasMultipleIps(forward.inIp) &&
|
|
||||||
showAddressModal(
|
showAddressModal(
|
||||||
forward.inIp,
|
forward.inIp,
|
||||||
forward.inPort,
|
forward.inPort,
|
||||||
@@ -1205,17 +1205,20 @@ export default function DashboardPage() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{formatInAddress(forward.inIp, forward.inPort)}
|
{formatInAddress(forward.inIp, forward.inPort)}
|
||||||
</code>
|
</button>
|
||||||
<div className="text-center text-default-400 text-xs">
|
<div className="text-center text-default-400 text-xs">
|
||||||
↓
|
↓
|
||||||
</div>
|
</div>
|
||||||
<code
|
<button
|
||||||
className={`block px-2 py-1 bg-blue-100 dark:bg-blue-500/20 text-blue-700 dark:text-blue-300 rounded font-mono text-xs truncate ${hasMultipleRemoteAddresses(forward.remoteAddr) ? "cursor-pointer hover:bg-blue-200 dark:hover:bg-blue-500/30" : ""}`}
|
className={`block px-2 py-1 bg-blue-100 dark:bg-blue-500/20 text-blue-700 dark:text-blue-300 rounded font-mono text-xs truncate ${hasMultipleRemoteAddresses(forward.remoteAddr) ? "cursor-pointer hover:bg-blue-200 dark:hover:bg-blue-500/30" : ""}`}
|
||||||
title={formatRemoteAddress(forward.remoteAddr)}
|
disabled={
|
||||||
onClick={() =>
|
!hasMultipleRemoteAddresses(
|
||||||
hasMultipleRemoteAddresses(
|
|
||||||
forward.remoteAddr,
|
forward.remoteAddr,
|
||||||
) &&
|
)
|
||||||
|
}
|
||||||
|
title={formatRemoteAddress(forward.remoteAddr)}
|
||||||
|
type="button"
|
||||||
|
onClick={() =>
|
||||||
showRemoteAddressModal(
|
showRemoteAddressModal(
|
||||||
forward.remoteAddr,
|
forward.remoteAddr,
|
||||||
"出口地址",
|
"出口地址",
|
||||||
@@ -1223,7 +1226,7 @@ export default function DashboardPage() {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{formatRemoteAddress(forward.remoteAddr)}
|
{formatRemoteAddress(forward.remoteAddr)}
|
||||||
</code>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+333
-258
File diff suppressed because it is too large
Load Diff
@@ -156,8 +156,7 @@ export default function IndexPage() {
|
|||||||
|
|
||||||
tacInstanceRef.current = new window.TAC(config, style);
|
tacInstanceRef.current = new window.TAC(config, style);
|
||||||
tacInstanceRef.current.init();
|
tacInstanceRef.current.init();
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("初始化验证码失败:", error);
|
|
||||||
toast.error("验证码初始化失败,请刷新页面重试");
|
toast.error("验证码初始化失败,请刷新页面重试");
|
||||||
setShowCaptcha(false);
|
setShowCaptcha(false);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -202,8 +201,7 @@ export default function IndexPage() {
|
|||||||
// 登录成功
|
// 登录成功
|
||||||
toast.success("登录成功");
|
toast.success("登录成功");
|
||||||
navigate("/dashboard");
|
navigate("/dashboard");
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("登录错误:", error);
|
|
||||||
toast.error("网络错误,请稍后重试");
|
toast.error("网络错误,请稍后重试");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -239,7 +237,6 @@ export default function IndexPage() {
|
|||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("检查验证码状态错误:", error);
|
|
||||||
toast.error("网络错误,请稍后重试" + error);
|
toast.error("网络错误,请稍后重试" + error);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,10 +94,8 @@ export default function LimitPage() {
|
|||||||
if (tunnelsRes.code === 0) {
|
if (tunnelsRes.code === 0) {
|
||||||
setTunnels(tunnelsRes.data || []);
|
setTunnels(tunnelsRes.data || []);
|
||||||
} else {
|
} else {
|
||||||
console.warn("获取隧道列表失败:", tunnelsRes.msg);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("加载数据失败:", error);
|
|
||||||
toast.error("加载数据失败");
|
toast.error("加载数据失败");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -177,8 +175,7 @@ export default function LimitPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(res.msg || "删除失败");
|
toast.error(res.msg || "删除失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("删除失败:", error);
|
|
||||||
toast.error("删除失败");
|
toast.error("删除失败");
|
||||||
} finally {
|
} finally {
|
||||||
setDeleteLoading(false);
|
setDeleteLoading(false);
|
||||||
@@ -196,7 +193,9 @@ export default function LimitPage() {
|
|||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
res = await updateSpeedLimit(form);
|
res = await updateSpeedLimit(form);
|
||||||
} else {
|
} else {
|
||||||
const { id, ...createData } = form;
|
const createData = { ...form };
|
||||||
|
|
||||||
|
delete createData.id;
|
||||||
|
|
||||||
res = await createSpeedLimit(createData);
|
res = await createSpeedLimit(createData);
|
||||||
}
|
}
|
||||||
@@ -208,8 +207,7 @@ export default function LimitPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(res.msg || "操作失败");
|
toast.error(res.msg || "操作失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("提交失败:", error);
|
|
||||||
toast.error("操作失败");
|
toast.error("操作失败");
|
||||||
} finally {
|
} finally {
|
||||||
setSubmitLoading(false);
|
setSubmitLoading(false);
|
||||||
@@ -233,7 +231,7 @@ export default function LimitPage() {
|
|||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<div className="flex-1" />
|
<div className="flex-1" />
|
||||||
|
|
||||||
<Button size="sm" variant="flat" color="primary" onPress={handleAdd}>
|
<Button color="primary" size="sm" variant="flat" onPress={handleAdd}>
|
||||||
新增
|
新增
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@@ -255,8 +253,8 @@ export default function LimitPage() {
|
|||||||
</div>
|
</div>
|
||||||
<Chip
|
<Chip
|
||||||
color={rule.status === 1 ? "success" : "danger"}
|
color={rule.status === 1 ? "success" : "danger"}
|
||||||
variant="flat"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
|
variant="flat"
|
||||||
>
|
>
|
||||||
{rule.status === 1 ? "运行" : "异常"}
|
{rule.status === 1 ? "运行" : "异常"}
|
||||||
</Chip>
|
</Chip>
|
||||||
@@ -291,24 +289,26 @@ export default function LimitPage() {
|
|||||||
<div className="flex gap-2 mt-4">
|
<div className="flex gap-2 mt-4">
|
||||||
<Button
|
<Button
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
color="primary"
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
startContent={
|
startContent={
|
||||||
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 20 20">
|
<svg
|
||||||
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
|
className="w-4 h-4"
|
||||||
</svg>
|
fill="currentColor"
|
||||||
}
|
viewBox="0 0 20 20"
|
||||||
variant="flat"
|
>
|
||||||
onPress={() => handleEdit(rule)}
|
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
|
||||||
|
</svg>
|
||||||
|
}
|
||||||
|
variant="flat"
|
||||||
|
onPress={() => handleEdit(rule)}
|
||||||
>
|
>
|
||||||
编辑
|
编辑
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
|
||||||
variant="flat"
|
|
||||||
color="danger"
|
|
||||||
onPress={() => handleDelete(rule)}
|
|
||||||
className="flex-1"
|
className="flex-1"
|
||||||
|
color="danger"
|
||||||
|
size="sm"
|
||||||
startContent={
|
startContent={
|
||||||
<svg
|
<svg
|
||||||
className="w-4 h-4"
|
className="w-4 h-4"
|
||||||
@@ -316,17 +316,19 @@ export default function LimitPage() {
|
|||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fillRule="evenodd"
|
|
||||||
d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"
|
|
||||||
clipRule="evenodd"
|
clipRule="evenodd"
|
||||||
|
d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"
|
||||||
|
fillRule="evenodd"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
fillRule="evenodd"
|
|
||||||
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8 7a1 1 0 012 0v4a1 1 0 11-2 0V7zM12 7a1 1 0 012 0v4a1 1 0 11-2 0V7z"
|
|
||||||
clipRule="evenodd"
|
clipRule="evenodd"
|
||||||
|
d="M10 18a8 8 0 100-16 8 8 0 000 16zM8 7a1 1 0 012 0v4a1 1 0 11-2 0V7zM12 7a1 1 0 012 0v4a1 1 0 11-2 0V7z"
|
||||||
|
fillRule="evenodd"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
}
|
}
|
||||||
|
variant="flat"
|
||||||
|
onPress={() => handleDelete(rule)}
|
||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
</Button>
|
</Button>
|
||||||
@@ -348,10 +350,10 @@ export default function LimitPage() {
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
|
d="M12 6v6l4 2m6-6a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
d="M12 6v6l4 2m6-6a9 9 0 11-18 0 9 9 0 0118 0z"
|
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
@@ -370,12 +372,12 @@ export default function LimitPage() {
|
|||||||
|
|
||||||
{/* 新增/编辑模态框 */}
|
{/* 新增/编辑模态框 */}
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={modalOpen}
|
|
||||||
onOpenChange={setModalOpen}
|
|
||||||
size="2xl"
|
|
||||||
scrollBehavior="outside"
|
|
||||||
backdrop="blur"
|
backdrop="blur"
|
||||||
|
isOpen={modalOpen}
|
||||||
placement="center"
|
placement="center"
|
||||||
|
scrollBehavior="outside"
|
||||||
|
size="2xl"
|
||||||
|
onOpenChange={setModalOpen}
|
||||||
>
|
>
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
{(onClose) => (
|
{(onClose) => (
|
||||||
@@ -394,36 +396,50 @@ export default function LimitPage() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<Input
|
<Input
|
||||||
errorMessage={errors.name}
|
errorMessage={errors.name}
|
||||||
isInvalid={!!errors.name}
|
isInvalid={!!errors.name}
|
||||||
label="规则名称"
|
label="规则名称"
|
||||||
placeholder="请输入限速规则名称"
|
placeholder="请输入限速规则名称"
|
||||||
value={form.name}
|
value={form.name}
|
||||||
variant="bordered"
|
variant="bordered"
|
||||||
onChange={(e) => setForm(prev => ({ ...prev, name: e.target.value }))}
|
onChange={(e) =>
|
||||||
|
setForm((prev) => ({ ...prev, name: e.target.value }))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Input
|
<Input
|
||||||
endContent={
|
endContent={
|
||||||
<div className="pointer-events-none flex items-center">
|
<div className="pointer-events-none flex items-center">
|
||||||
<span className="text-default-400 text-small">Mbps</span>
|
<span className="text-default-400 text-small">
|
||||||
</div>
|
Mbps
|
||||||
}
|
</span>
|
||||||
errorMessage={errors.speed}
|
</div>
|
||||||
isInvalid={!!errors.speed}
|
}
|
||||||
label="速度限制"
|
errorMessage={errors.speed}
|
||||||
placeholder="请输入速度限制"
|
isInvalid={!!errors.speed}
|
||||||
type="number"
|
label="速度限制"
|
||||||
value={form.speed.toString()}
|
placeholder="请输入速度限制"
|
||||||
variant="bordered"
|
type="number"
|
||||||
onChange={(e) => setForm(prev => ({ ...prev, speed: parseInt(e.target.value) || 0 }))}
|
value={form.speed.toString()}
|
||||||
|
variant="bordered"
|
||||||
|
onChange={(e) =>
|
||||||
|
setForm((prev) => ({
|
||||||
|
...prev,
|
||||||
|
speed: parseInt(e.target.value) || 0,
|
||||||
|
}))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
|
description={isEdit ? "编辑时无法修改绑定隧道" : undefined}
|
||||||
|
errorMessage={errors.tunnelId}
|
||||||
|
isDisabled={isEdit}
|
||||||
|
isInvalid={!!errors.tunnelId}
|
||||||
label="绑定隧道"
|
label="绑定隧道"
|
||||||
placeholder="请选择要绑定的隧道"
|
placeholder="请选择要绑定的隧道"
|
||||||
selectedKeys={
|
selectedKeys={
|
||||||
form.tunnelId ? [form.tunnelId.toString()] : []
|
form.tunnelId ? [form.tunnelId.toString()] : []
|
||||||
}
|
}
|
||||||
|
variant="bordered"
|
||||||
onSelectionChange={(keys) => {
|
onSelectionChange={(keys) => {
|
||||||
const selectedKey = Array.from(keys)[0] as string;
|
const selectedKey = Array.from(keys)[0] as string;
|
||||||
|
|
||||||
@@ -431,6 +447,7 @@ export default function LimitPage() {
|
|||||||
const selectedTunnel = tunnels.find(
|
const selectedTunnel = tunnels.find(
|
||||||
(tunnel) => tunnel.id === parseInt(selectedKey),
|
(tunnel) => tunnel.id === parseInt(selectedKey),
|
||||||
);
|
);
|
||||||
|
|
||||||
setForm((prev) => ({
|
setForm((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
tunnelId: parseInt(selectedKey),
|
tunnelId: parseInt(selectedKey),
|
||||||
@@ -444,11 +461,6 @@ export default function LimitPage() {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
isInvalid={!!errors.tunnelId}
|
|
||||||
errorMessage={errors.tunnelId}
|
|
||||||
variant="bordered"
|
|
||||||
isDisabled={isEdit}
|
|
||||||
description={isEdit ? "编辑时无法修改绑定隧道" : undefined}
|
|
||||||
>
|
>
|
||||||
{tunnels.map((tunnel) => (
|
{tunnels.map((tunnel) => (
|
||||||
<SelectItem key={tunnel.id}>{tunnel.name}</SelectItem>
|
<SelectItem key={tunnel.id}>{tunnel.name}</SelectItem>
|
||||||
@@ -462,8 +474,8 @@ export default function LimitPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
onPress={handleSubmit}
|
|
||||||
isLoading={submitLoading}
|
isLoading={submitLoading}
|
||||||
|
onPress={handleSubmit}
|
||||||
>
|
>
|
||||||
{isEdit ? "保存修改" : "创建规则"}
|
{isEdit ? "保存修改" : "创建规则"}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -475,12 +487,12 @@ export default function LimitPage() {
|
|||||||
|
|
||||||
{/* 删除确认模态框 */}
|
{/* 删除确认模态框 */}
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={deleteModalOpen}
|
|
||||||
onOpenChange={setDeleteModalOpen}
|
|
||||||
size="2xl"
|
|
||||||
scrollBehavior="outside"
|
|
||||||
backdrop="blur"
|
backdrop="blur"
|
||||||
|
isOpen={deleteModalOpen}
|
||||||
placement="center"
|
placement="center"
|
||||||
|
scrollBehavior="outside"
|
||||||
|
size="2xl"
|
||||||
|
onOpenChange={setDeleteModalOpen}
|
||||||
>
|
>
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
{(onClose) => (
|
{(onClose) => (
|
||||||
@@ -492,7 +504,7 @@ export default function LimitPage() {
|
|||||||
<p className="text-default-600">
|
<p className="text-default-600">
|
||||||
确定要删除限速规则{" "}
|
确定要删除限速规则{" "}
|
||||||
<span className="font-semibold text-foreground">
|
<span className="font-semibold text-foreground">
|
||||||
"{ruleToDelete?.name}"
|
"{ruleToDelete?.name}"
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
吗?
|
吗?
|
||||||
</p>
|
</p>
|
||||||
@@ -506,8 +518,8 @@ export default function LimitPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="danger"
|
||||||
onPress={confirmDelete}
|
|
||||||
isLoading={deleteLoading}
|
isLoading={deleteLoading}
|
||||||
|
onPress={confirmDelete}
|
||||||
>
|
>
|
||||||
确认删除
|
确认删除
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -266,7 +266,7 @@ export default function NodePage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(res.msg || "加载节点列表失败");
|
toast.error(res.msg || "加载节点列表失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("网络错误,请重试");
|
toast.error("网络错误,请重试");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -312,7 +312,7 @@ export default function NodePage() {
|
|||||||
const data = JSON.parse(event.data);
|
const data = JSON.parse(event.data);
|
||||||
|
|
||||||
handleWebSocketMessage(data);
|
handleWebSocketMessage(data);
|
||||||
} catch (error) {
|
} catch {
|
||||||
// 解析失败时不输出错误信息
|
// 解析失败时不输出错误信息
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -327,7 +327,7 @@ export default function NodePage() {
|
|||||||
setWsConnecting(false);
|
setWsConnecting(false);
|
||||||
attemptReconnect();
|
attemptReconnect();
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch {
|
||||||
setWsConnected(false);
|
setWsConnected(false);
|
||||||
setWsConnecting(false);
|
setWsConnecting(false);
|
||||||
attemptReconnect();
|
attemptReconnect();
|
||||||
@@ -413,7 +413,7 @@ export default function NodePage() {
|
|||||||
uptime: currentUptime,
|
uptime: currentUptime,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch {
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -717,7 +717,7 @@ export default function NodePage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(res.msg || "删除失败");
|
toast.error(res.msg || "删除失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("网络错误,请重试");
|
toast.error("网络错误,请重试");
|
||||||
} finally {
|
} finally {
|
||||||
setDeleteLoading(false);
|
setDeleteLoading(false);
|
||||||
@@ -737,7 +737,7 @@ export default function NodePage() {
|
|||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(res.data);
|
await navigator.clipboard.writeText(res.data);
|
||||||
toast.success("安装命令已复制到剪贴板");
|
toast.success("安装命令已复制到剪贴板");
|
||||||
} catch (copyError) {
|
} catch {
|
||||||
// 复制失败,显示安装命令模态框
|
// 复制失败,显示安装命令模态框
|
||||||
setInstallCommand(res.data);
|
setInstallCommand(res.data);
|
||||||
setCurrentNodeName(node.name);
|
setCurrentNodeName(node.name);
|
||||||
@@ -746,7 +746,7 @@ export default function NodePage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(res.msg || "获取安装命令失败");
|
toast.error(res.msg || "获取安装命令失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("获取安装命令失败");
|
toast.error("获取安装命令失败");
|
||||||
} finally {
|
} finally {
|
||||||
setNodeList((prev) =>
|
setNodeList((prev) =>
|
||||||
@@ -761,7 +761,7 @@ export default function NodePage() {
|
|||||||
await navigator.clipboard.writeText(installCommand);
|
await navigator.clipboard.writeText(installCommand);
|
||||||
toast.success("安装命令已复制到剪贴板");
|
toast.success("安装命令已复制到剪贴板");
|
||||||
setInstallCommandModal(false);
|
setInstallCommandModal(false);
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("复制失败,请手动选择文本复制");
|
toast.error("复制失败,请手动选择文本复制");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -774,13 +774,13 @@ export default function NodePage() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const apiCall = isEdit ? updateNode : createNode;
|
const apiCall = isEdit ? updateNode : createNode;
|
||||||
const { serverHost: _serverHost, ...rest } = form;
|
const { serverHost, ...rest } = form;
|
||||||
const data = {
|
const data = {
|
||||||
...rest,
|
...rest,
|
||||||
serverIp:
|
serverIp:
|
||||||
form.serverIpV4?.trim() ||
|
form.serverIpV4?.trim() ||
|
||||||
form.serverIpV6?.trim() ||
|
form.serverIpV6?.trim() ||
|
||||||
form.serverHost?.trim() ||
|
serverHost?.trim() ||
|
||||||
"",
|
"",
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -821,7 +821,7 @@ export default function NodePage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(res.msg || (isEdit ? "更新失败" : "创建失败"));
|
toast.error(res.msg || (isEdit ? "更新失败" : "创建失败"));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("网络错误,请重试");
|
toast.error("网络错误,请重试");
|
||||||
} finally {
|
} finally {
|
||||||
setSubmitLoading(false);
|
setSubmitLoading(false);
|
||||||
@@ -871,9 +871,7 @@ export default function NodePage() {
|
|||||||
// 保存到 localStorage
|
// 保存到 localStorage
|
||||||
try {
|
try {
|
||||||
localStorage.setItem("node-order", JSON.stringify(newOrder));
|
localStorage.setItem("node-order", JSON.stringify(newOrder));
|
||||||
} catch (error) {
|
} catch {}
|
||||||
console.warn("无法保存节点排序到localStorage:", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 持久化到数据库
|
// 持久化到数据库
|
||||||
try {
|
try {
|
||||||
@@ -891,8 +889,7 @@ export default function NodePage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error("保存排序失败:" + (response.msg || "未知错误"));
|
toast.error("保存排序失败:" + (response.msg || "未知错误"));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("保存节点排序到数据库失败:", error);
|
|
||||||
toast.error("保存排序失败,请重试");
|
toast.error("保存排序失败,请重试");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -903,6 +900,7 @@ export default function NodePage() {
|
|||||||
if (prev) {
|
if (prev) {
|
||||||
setSelectedIds(new Set());
|
setSelectedIds(new Set());
|
||||||
}
|
}
|
||||||
|
|
||||||
return !prev;
|
return !prev;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -910,11 +908,13 @@ export default function NodePage() {
|
|||||||
const toggleSelect = (id: number) => {
|
const toggleSelect = (id: number) => {
|
||||||
setSelectedIds((prev) => {
|
setSelectedIds((prev) => {
|
||||||
const next = new Set(prev);
|
const next = new Set(prev);
|
||||||
|
|
||||||
if (next.has(id)) {
|
if (next.has(id)) {
|
||||||
next.delete(id);
|
next.delete(id);
|
||||||
} else {
|
} else {
|
||||||
next.add(id);
|
next.add(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return next;
|
return next;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -932,6 +932,7 @@ export default function NodePage() {
|
|||||||
setBatchLoading(true);
|
setBatchLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await batchDeleteNodes(Array.from(selectedIds));
|
const res = await batchDeleteNodes(Array.from(selectedIds));
|
||||||
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
toast.success(`成功删除 ${selectedIds.size} 个节点`);
|
toast.success(`成功删除 ${selectedIds.size} 个节点`);
|
||||||
setNodeList((prev) => prev.filter((n) => !selectedIds.has(n.id)));
|
setNodeList((prev) => prev.filter((n) => !selectedIds.has(n.id)));
|
||||||
@@ -941,7 +942,7 @@ export default function NodePage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(res.msg || "删除失败");
|
toast.error(res.msg || "删除失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("网络错误,请重试");
|
toast.error("网络错误,请重试");
|
||||||
} finally {
|
} finally {
|
||||||
setBatchLoading(false);
|
setBatchLoading(false);
|
||||||
@@ -1724,7 +1725,8 @@ export default function NodePage() {
|
|||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<p>
|
<p>
|
||||||
确定要删除节点 <strong>"{nodeToDelete?.name}"</strong> 吗?
|
确定要删除节点{" "}
|
||||||
|
<strong>"{nodeToDelete?.name}"</strong> 吗?
|
||||||
</p>
|
</p>
|
||||||
<p className="text-small text-default-500">
|
<p className="text-small text-default-500">
|
||||||
此操作不可恢复,请谨慎操作。
|
此操作不可恢复,请谨慎操作。
|
||||||
@@ -1818,7 +1820,8 @@ export default function NodePage() {
|
|||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<p>
|
<p>
|
||||||
确定要删除选中的 <strong>{selectedIds.size}</strong> 个节点吗?
|
确定要删除选中的 <strong>{selectedIds.size}</strong>{" "}
|
||||||
|
个节点吗?
|
||||||
</p>
|
</p>
|
||||||
<p className="text-small text-default-500">
|
<p className="text-small text-default-500">
|
||||||
此操作不可恢复,请谨慎操作。
|
此操作不可恢复,请谨慎操作。
|
||||||
|
|||||||
@@ -168,9 +168,8 @@ export default function ProfilePage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(response.msg || "密码修改失败");
|
toast.error(response.msg || "密码修改失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("修改密码时发生错误");
|
toast.error("修改密码时发生错误");
|
||||||
console.error("修改密码错误:", error);
|
|
||||||
} finally {
|
} finally {
|
||||||
setPasswordLoading(false);
|
setPasswordLoading(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -220,10 +220,8 @@ export default function TunnelPage() {
|
|||||||
if (nodesRes.code === 0) {
|
if (nodesRes.code === 0) {
|
||||||
setNodes(nodesRes.data || []);
|
setNodes(nodesRes.data || []);
|
||||||
} else {
|
} else {
|
||||||
console.warn("获取节点列表失败:", nodesRes.msg);
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("加载数据失败:", error);
|
|
||||||
toast.error("加载数据失败");
|
toast.error("加载数据失败");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -356,8 +354,7 @@ export default function TunnelPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(response.msg || "删除失败");
|
toast.error(response.msg || "删除失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("删除失败:", error);
|
|
||||||
toast.error("删除失败");
|
toast.error("删除失败");
|
||||||
} finally {
|
} finally {
|
||||||
setDeleteLoading(false);
|
setDeleteLoading(false);
|
||||||
@@ -498,8 +495,7 @@ export default function TunnelPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(response.msg || (isEdit ? "更新失败" : "创建失败"));
|
toast.error(response.msg || (isEdit ? "更新失败" : "创建失败"));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("提交失败:", error);
|
|
||||||
toast.error("网络错误,请重试");
|
toast.error("网络错误,请重试");
|
||||||
} finally {
|
} finally {
|
||||||
setSubmitLoading(false);
|
setSubmitLoading(false);
|
||||||
@@ -537,8 +533,7 @@ export default function TunnelPage() {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("诊断失败:", error);
|
|
||||||
toast.error("网络错误,请重试");
|
toast.error("网络错误,请重试");
|
||||||
setDiagnosisResult({
|
setDiagnosisResult({
|
||||||
tunnelName: tunnel.name,
|
tunnelName: tunnel.name,
|
||||||
@@ -627,9 +622,7 @@ export default function TunnelPage() {
|
|||||||
// 保存到 localStorage
|
// 保存到 localStorage
|
||||||
try {
|
try {
|
||||||
localStorage.setItem("tunnel-order", JSON.stringify(newOrder));
|
localStorage.setItem("tunnel-order", JSON.stringify(newOrder));
|
||||||
} catch (error) {
|
} catch {}
|
||||||
console.warn("无法保存隧道排序到localStorage:", error);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 持久化到数据库
|
// 持久化到数据库
|
||||||
try {
|
try {
|
||||||
@@ -647,8 +640,7 @@ export default function TunnelPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error("保存排序失败:" + (response.msg || "未知错误"));
|
toast.error("保存排序失败:" + (response.msg || "未知错误"));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
console.error("保存隧道排序到数据库失败:", error);
|
|
||||||
toast.error("保存排序失败,请重试");
|
toast.error("保存排序失败,请重试");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -662,6 +654,7 @@ export default function TunnelPage() {
|
|||||||
|
|
||||||
const toggleSelect = (id: number) => {
|
const toggleSelect = (id: number) => {
|
||||||
const newSet = new Set(selectedIds);
|
const newSet = new Set(selectedIds);
|
||||||
|
|
||||||
if (newSet.has(id)) {
|
if (newSet.has(id)) {
|
||||||
newSet.delete(id);
|
newSet.delete(id);
|
||||||
} else {
|
} else {
|
||||||
@@ -672,6 +665,7 @@ export default function TunnelPage() {
|
|||||||
|
|
||||||
const selectAll = () => {
|
const selectAll = () => {
|
||||||
const allIds = sortedTunnels.map((t) => t.id);
|
const allIds = sortedTunnels.map((t) => t.id);
|
||||||
|
|
||||||
setSelectedIds(new Set(allIds));
|
setSelectedIds(new Set(allIds));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -684,12 +678,16 @@ export default function TunnelPage() {
|
|||||||
setBatchLoading(true);
|
setBatchLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await batchDeleteTunnels(Array.from(selectedIds));
|
const res = await batchDeleteTunnels(Array.from(selectedIds));
|
||||||
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const result = res.data;
|
const result = res.data;
|
||||||
|
|
||||||
if (result.failCount === 0) {
|
if (result.failCount === 0) {
|
||||||
toast.success(`成功删除 ${result.successCount} 项`);
|
toast.success(`成功删除 ${result.successCount} 项`);
|
||||||
} else {
|
} else {
|
||||||
toast.error(`成功 ${result.successCount} 项,失败 ${result.failCount} 项`);
|
toast.error(
|
||||||
|
`成功 ${result.successCount} 项,失败 ${result.failCount} 项`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
setSelectedIds(new Set());
|
setSelectedIds(new Set());
|
||||||
setSelectMode(false);
|
setSelectMode(false);
|
||||||
@@ -710,12 +708,16 @@ export default function TunnelPage() {
|
|||||||
setBatchLoading(true);
|
setBatchLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await batchRedeployTunnels(Array.from(selectedIds));
|
const res = await batchRedeployTunnels(Array.from(selectedIds));
|
||||||
|
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
const result = res.data;
|
const result = res.data;
|
||||||
|
|
||||||
if (result.failCount === 0) {
|
if (result.failCount === 0) {
|
||||||
toast.success(`成功重新下发 ${result.successCount} 项`);
|
toast.success(`成功重新下发 ${result.successCount} 项`);
|
||||||
} else {
|
} else {
|
||||||
toast.error(`成功 ${result.successCount} 项,失败 ${result.failCount} 项`);
|
toast.error(
|
||||||
|
`成功 ${result.successCount} 项,失败 ${result.failCount} 项`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
setSelectedIds(new Set());
|
setSelectedIds(new Set());
|
||||||
setSelectMode(false);
|
setSelectMode(false);
|
||||||
@@ -840,9 +842,9 @@ export default function TunnelPage() {
|
|||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button
|
<Button
|
||||||
|
color={selectMode ? "warning" : "default"}
|
||||||
size="sm"
|
size="sm"
|
||||||
variant={selectMode ? "solid" : "flat"}
|
variant={selectMode ? "solid" : "flat"}
|
||||||
color={selectMode ? "warning" : "default"}
|
|
||||||
onPress={toggleSelectMode}
|
onPress={toggleSelectMode}
|
||||||
>
|
>
|
||||||
{selectMode ? "退出" : "批量"}
|
{selectMode ? "退出" : "批量"}
|
||||||
@@ -856,7 +858,9 @@ export default function TunnelPage() {
|
|||||||
{selectMode && selectedIds.size > 0 && (
|
{selectMode && selectedIds.size > 0 && (
|
||||||
<div className="fixed bottom-7 left-1/2 z-50 w-[calc(100vw-1rem)] max-w-max -translate-x-1/2 overflow-x-auto rounded-lg border border-divider bg-content1 p-2 shadow-lg">
|
<div className="fixed bottom-7 left-1/2 z-50 w-[calc(100vw-1rem)] max-w-max -translate-x-1/2 overflow-x-auto rounded-lg border border-divider bg-content1 p-2 shadow-lg">
|
||||||
<div className="flex min-w-max items-center gap-2">
|
<div className="flex min-w-max items-center gap-2">
|
||||||
<span className="text-sm text-default-600 shrink-0">已选择 {selectedIds.size} 项</span>
|
<span className="text-sm text-default-600 shrink-0">
|
||||||
|
已选择 {selectedIds.size} 项
|
||||||
|
</span>
|
||||||
<Button size="sm" variant="flat" onPress={selectAll}>
|
<Button size="sm" variant="flat" onPress={selectAll}>
|
||||||
全选
|
全选
|
||||||
</Button>
|
</Button>
|
||||||
@@ -864,19 +868,19 @@ export default function TunnelPage() {
|
|||||||
清空
|
清空
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
|
||||||
color="danger"
|
color="danger"
|
||||||
|
size="sm"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
onPress={() => setBatchDeleteModalOpen(true)}
|
onPress={() => setBatchDeleteModalOpen(true)}
|
||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
|
||||||
color="primary"
|
color="primary"
|
||||||
|
isLoading={batchLoading}
|
||||||
|
size="sm"
|
||||||
variant="flat"
|
variant="flat"
|
||||||
onPress={handleBatchRedeploy}
|
onPress={handleBatchRedeploy}
|
||||||
isLoading={batchLoading}
|
|
||||||
>
|
>
|
||||||
下发
|
下发
|
||||||
</Button>
|
</Button>
|
||||||
@@ -906,9 +910,9 @@ export default function TunnelPage() {
|
|||||||
<div className="flex justify-between items-start w-full">
|
<div className="flex justify-between items-start w-full">
|
||||||
{selectMode && (
|
{selectMode && (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
className="mr-2"
|
||||||
isSelected={selectedIds.has(tunnel.id)}
|
isSelected={selectedIds.has(tunnel.id)}
|
||||||
onValueChange={() => toggleSelect(tunnel.id)}
|
onValueChange={() => toggleSelect(tunnel.id)}
|
||||||
className="mr-2"
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
@@ -1666,7 +1670,7 @@ export default function TunnelPage() {
|
|||||||
{getChainGroups().length === 0 && (
|
{getChainGroups().length === 0 && (
|
||||||
<div className="text-center py-8 bg-default-50 dark:bg-default-100/50 rounded border border-dashed border-default-300">
|
<div className="text-center py-8 bg-default-50 dark:bg-default-100/50 rounded border border-dashed border-default-300">
|
||||||
<p className="text-sm text-default-500">
|
<p className="text-sm text-default-500">
|
||||||
还没有添加转发链,点击上方"添加一跳"按钮开始添加
|
还没有添加转发链,点击上方"添加一跳"按钮开始添加
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -1972,7 +1976,8 @@ export default function TunnelPage() {
|
|||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<p>
|
<p>
|
||||||
确定要删除隧道 <strong>"{tunnelToDelete?.name}"</strong> 吗?
|
确定要删除隧道{" "}
|
||||||
|
<strong>"{tunnelToDelete?.name}"</strong> 吗?
|
||||||
</p>
|
</p>
|
||||||
<p className="text-small text-default-500">
|
<p className="text-small text-default-500">
|
||||||
此操作不可恢复,请谨慎操作。
|
此操作不可恢复,请谨慎操作。
|
||||||
@@ -2512,13 +2517,19 @@ export default function TunnelPage() {
|
|||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal isOpen={batchDeleteModalOpen} onOpenChange={setBatchDeleteModalOpen}>
|
<Modal
|
||||||
|
isOpen={batchDeleteModalOpen}
|
||||||
|
onOpenChange={setBatchDeleteModalOpen}
|
||||||
|
>
|
||||||
<ModalContent>
|
<ModalContent>
|
||||||
{(onClose) => (
|
{(onClose) => (
|
||||||
<>
|
<>
|
||||||
<ModalHeader>确认删除</ModalHeader>
|
<ModalHeader>确认删除</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<p>确定要删除选中的 {selectedIds.size} 项隧道吗?此操作不可撤销,相关转发也将被删除。</p>
|
<p>
|
||||||
|
确定要删除选中的 {selectedIds.size}{" "}
|
||||||
|
项隧道吗?此操作不可撤销,相关转发也将被删除。
|
||||||
|
</p>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button variant="light" onPress={onClose}>
|
<Button variant="light" onPress={onClose}>
|
||||||
@@ -2526,8 +2537,8 @@ export default function TunnelPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="danger"
|
||||||
onPress={handleBatchDelete}
|
|
||||||
isLoading={batchLoading}
|
isLoading={batchLoading}
|
||||||
|
onPress={handleBatchDelete}
|
||||||
>
|
>
|
||||||
确认删除
|
确认删除
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ export default function UserPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(response.msg || "获取用户列表失败");
|
toast.error(response.msg || "获取用户列表失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("获取用户列表失败");
|
toast.error("获取用户列表失败");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -243,9 +243,7 @@ export default function UserPage() {
|
|||||||
if (response.code === 0) {
|
if (response.code === 0) {
|
||||||
setTunnels(response.data || []);
|
setTunnels(response.data || []);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {}
|
||||||
console.error("获取隧道列表失败:", error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadSpeedLimits = async () => {
|
const loadSpeedLimits = async () => {
|
||||||
@@ -255,9 +253,7 @@ export default function UserPage() {
|
|||||||
if (response.code === 0) {
|
if (response.code === 0) {
|
||||||
setSpeedLimits(response.data || []);
|
setSpeedLimits(response.data || []);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {}
|
||||||
console.error("获取限速规则列表失败:", error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadUserTunnels = async (userId: number) => {
|
const loadUserTunnels = async (userId: number) => {
|
||||||
@@ -270,7 +266,7 @@ export default function UserPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(response.msg || "获取隧道权限列表失败");
|
toast.error(response.msg || "获取隧道权限列表失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("获取隧道权限列表失败");
|
toast.error("获取隧道权限列表失败");
|
||||||
} finally {
|
} finally {
|
||||||
setTunnelListLoading(false);
|
setTunnelListLoading(false);
|
||||||
@@ -332,7 +328,7 @@ export default function UserPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(response.msg || "删除失败");
|
toast.error(response.msg || "删除失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("删除失败");
|
toast.error("删除失败");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -366,7 +362,7 @@ export default function UserPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(response.msg || (isEdit ? "更新失败" : "创建失败"));
|
toast.error(response.msg || (isEdit ? "更新失败" : "创建失败"));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error(isEdit ? "更新失败" : "创建失败");
|
toast.error(isEdit ? "更新失败" : "创建失败");
|
||||||
} finally {
|
} finally {
|
||||||
setUserFormLoading(false);
|
setUserFormLoading(false);
|
||||||
@@ -406,7 +402,7 @@ export default function UserPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(response.msg || "分配失败");
|
toast.error(response.msg || "分配失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("分配失败");
|
toast.error("分配失败");
|
||||||
} finally {
|
} finally {
|
||||||
setAssignLoading(false);
|
setAssignLoading(false);
|
||||||
@@ -445,7 +441,7 @@ export default function UserPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(response.msg || "更新失败");
|
toast.error(response.msg || "更新失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("更新失败");
|
toast.error("更新失败");
|
||||||
} finally {
|
} finally {
|
||||||
setEditTunnelLoading(false);
|
setEditTunnelLoading(false);
|
||||||
@@ -473,7 +469,7 @@ export default function UserPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(response.msg || "删除失败");
|
toast.error(response.msg || "删除失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("删除失败");
|
toast.error("删除失败");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -502,7 +498,7 @@ export default function UserPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(response.msg || "重置失败");
|
toast.error(response.msg || "重置失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("重置失败");
|
toast.error("重置失败");
|
||||||
} finally {
|
} finally {
|
||||||
setResetFlowLoading(false);
|
setResetFlowLoading(false);
|
||||||
@@ -535,7 +531,7 @@ export default function UserPage() {
|
|||||||
} else {
|
} else {
|
||||||
toast.error(response.msg || "重置失败");
|
toast.error(response.msg || "重置失败");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
toast.error("重置失败");
|
toast.error("重置失败");
|
||||||
} finally {
|
} finally {
|
||||||
setResetTunnelFlowLoading(false);
|
setResetTunnelFlowLoading(false);
|
||||||
@@ -1005,6 +1001,7 @@ export default function UserPage() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={tunnel.id}
|
key={tunnel.id}
|
||||||
|
aria-disabled={isAssigned}
|
||||||
className={`
|
className={`
|
||||||
px-4 py-3 rounded-lg border transition-all duration-200 cursor-pointer
|
px-4 py-3 rounded-lg border transition-all duration-200 cursor-pointer
|
||||||
${
|
${
|
||||||
@@ -1015,9 +1012,21 @@ export default function UserPage() {
|
|||||||
: "bg-white dark:bg-default-50 border-default-200 dark:border-default-100/30 hover:border-primary-200 dark:hover:border-primary-500/30 hover:shadow-sm"
|
: "bg-white dark:bg-default-50 border-default-200 dark:border-default-100/30 hover:border-primary-200 dark:hover:border-primary-500/30 hover:shadow-sm"
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
|
role="button"
|
||||||
|
tabIndex={isAssigned ? -1 : 0}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
!isAssigned && toggleTunnelSelection(tunnel.id)
|
!isAssigned && toggleTunnelSelection(tunnel.id)
|
||||||
}
|
}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (isAssigned) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
|
event.preventDefault();
|
||||||
|
toggleTunnelSelection(tunnel.id);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<div className="flex items-center gap-3 flex-1 min-w-0">
|
<div className="flex items-center gap-3 flex-1 min-w-0">
|
||||||
@@ -1051,7 +1060,7 @@ export default function UserPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isSelected && !isAssigned && (
|
{isSelected && !isAssigned && (
|
||||||
<div onClick={(e) => e.stopPropagation()}>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
className="w-36"
|
className="w-36"
|
||||||
classNames={{
|
classNames={{
|
||||||
@@ -1071,6 +1080,7 @@ export default function UserPage() {
|
|||||||
: ["null"]
|
: ["null"]
|
||||||
}
|
}
|
||||||
size="sm"
|
size="sm"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
onSelectionChange={(keys) => {
|
onSelectionChange={(keys) => {
|
||||||
const value = Array.from(keys)[0] as string;
|
const value = Array.from(keys)[0] as string;
|
||||||
|
|
||||||
@@ -1455,7 +1465,7 @@ export default function UserPage() {
|
|||||||
<p className="text-foreground">
|
<p className="text-foreground">
|
||||||
确定要删除用户{" "}
|
确定要删除用户{" "}
|
||||||
<span className="font-semibold text-danger">
|
<span className="font-semibold text-danger">
|
||||||
"{userToDelete?.user}"
|
"{userToDelete?.user}"
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
吗?
|
吗?
|
||||||
</p>
|
</p>
|
||||||
@@ -1500,7 +1510,7 @@ export default function UserPage() {
|
|||||||
<span className="font-semibold">{currentUser?.user}</span>{" "}
|
<span className="font-semibold">{currentUser?.user}</span>{" "}
|
||||||
对隧道{" "}
|
对隧道{" "}
|
||||||
<span className="font-semibold text-danger">
|
<span className="font-semibold text-danger">
|
||||||
"{tunnelToDelete?.tunnelName}"
|
"{tunnelToDelete?.tunnelName}"
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
的权限吗?
|
的权限吗?
|
||||||
</p>
|
</p>
|
||||||
@@ -1553,7 +1563,7 @@ export default function UserPage() {
|
|||||||
<p className="text-foreground">
|
<p className="text-foreground">
|
||||||
确定要重置用户{" "}
|
确定要重置用户{" "}
|
||||||
<span className="font-semibold text-warning">
|
<span className="font-semibold text-warning">
|
||||||
"{userToReset?.user}"
|
"{userToReset?.user}"
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
的流量吗?
|
的流量吗?
|
||||||
</p>
|
</p>
|
||||||
@@ -1643,7 +1653,7 @@ export default function UserPage() {
|
|||||||
<span className="font-semibold">{currentUser?.user}</span>{" "}
|
<span className="font-semibold">{currentUser?.user}</span>{" "}
|
||||||
对隧道{" "}
|
对隧道{" "}
|
||||||
<span className="font-semibold text-warning">
|
<span className="font-semibold text-warning">
|
||||||
"{tunnelToReset?.tunnelName}"
|
"{tunnelToReset?.tunnelName}"
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
的流量吗?
|
的流量吗?
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
Vendored
+8
-5
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
TAC: typeof TAC;
|
TAC: TACConstructor;
|
||||||
currentCaptcha?: any;
|
currentCaptcha?: any;
|
||||||
currentCaptchaRes?: any;
|
currentCaptchaRes?: any;
|
||||||
}
|
}
|
||||||
@@ -42,13 +42,16 @@ interface CaptchaStyle {
|
|||||||
moveTrackMaskBorderColor?: string;
|
moveTrackMaskBorderColor?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
declare class TAC {
|
interface TACInstance {
|
||||||
constructor(config: CaptchaConfig, style?: CaptchaStyle);
|
init(): TACInstance;
|
||||||
|
|
||||||
init(): TAC;
|
|
||||||
reloadCaptcha(): void;
|
reloadCaptcha(): void;
|
||||||
destroyWindow(): void;
|
destroyWindow(): void;
|
||||||
openCaptcha(): void;
|
openCaptcha(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TACConstructor = new (
|
||||||
|
config: CaptchaConfig,
|
||||||
|
style?: CaptchaStyle,
|
||||||
|
) => TACInstance;
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
|
|||||||
@@ -60,17 +60,15 @@ export function isLoggedIn(): boolean {
|
|||||||
/**
|
/**
|
||||||
* 权限检查装饰器函数
|
* 权限检查装饰器函数
|
||||||
* @param fn 要执行的函数
|
* @param fn 要执行的函数
|
||||||
* @param errorMsg 权限不足时的错误提示
|
* @param _errorMsg 权限不足时的错误提示
|
||||||
* @returns 包装后的函数
|
* @returns 包装后的函数
|
||||||
*/
|
*/
|
||||||
export function requireAdmin<T extends (...args: any[]) => any>(
|
export function requireAdmin<T extends (...args: any[]) => any>(
|
||||||
fn: T,
|
fn: T,
|
||||||
errorMsg: string = "权限不足,仅管理员可操作",
|
_errorMsg: string = "权限不足,仅管理员可操作",
|
||||||
): T {
|
): T {
|
||||||
return ((...args: Parameters<T>) => {
|
return ((...args: Parameters<T>) => {
|
||||||
if (!isAdmin()) {
|
if (!isAdmin()) {
|
||||||
console.warn(errorMsg);
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ function getPayloadFromToken(token: string): JWTPayload | null {
|
|||||||
const decodedPayload = atob(encodedPayload);
|
const decodedPayload = atob(encodedPayload);
|
||||||
|
|
||||||
return JSON.parse(decodedPayload) as JWTPayload;
|
return JSON.parse(decodedPayload) as JWTPayload;
|
||||||
} catch (error) {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export function validatePanelAddress(address: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
} catch (error) {
|
} catch {
|
||||||
// URL构造函数失败说明格式不正确
|
// URL构造函数失败说明格式不正确
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user