Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion messages/en/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@
"view": "View"
},
"error": {
"loadFailed": "Load Failed"
"loadFailed": "Load Failed",
"loadKeysFailed": "Failed to load keys"
},
"details": {
"title": "Request Details",
Expand Down
3 changes: 2 additions & 1 deletion messages/ja/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@
"view": "表示"
},
"error": {
"loadFailed": "読み込み失敗"
"loadFailed": "読み込み失敗",
"loadKeysFailed": "キーリストの読み込みに失敗しました"
},
"details": {
"title": "リクエスト詳細",
Expand Down
3 changes: 2 additions & 1 deletion messages/ru/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@
"view": "Просмотр"
},
"error": {
"loadFailed": "Ошибка загрузки"
"loadFailed": "Ошибка загрузки",
"loadKeysFailed": "Не удалось загрузить список ключей"
},
"details": {
"title": "Детали запроса",
Expand Down
3 changes: 2 additions & 1 deletion messages/zh-CN/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@
"view": "查看"
},
"error": {
"loadFailed": "加载失败"
"loadFailed": "加载失败",
"loadKeysFailed": "加载密钥列表失败"
},
"details": {
"title": "请求详情",
Expand Down
3 changes: 2 additions & 1 deletion messages/zh-TW/dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@
"view": "檢視"
},
"error": {
"loadFailed": "載入失敗"
"loadFailed": "載入失敗",
"loadKeysFailed": "載入密鑰列表失敗"
},
"details": {
"title": "請求詳情",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,13 @@ export function AddKeyForm({ userId, user, onSuccess }: AddKeyFormProps) {

// Load provider group suggestions
useEffect(() => {
if (user?.id) {
getAvailableProviderGroups(user.id).then(setProviderGroupSuggestions);
} else {
getAvailableProviderGroups().then(setProviderGroupSuggestions);
}
const loadGroups = user?.id
? getAvailableProviderGroups(user.id)
: getAvailableProviderGroups();

loadGroups.then(setProviderGroupSuggestions).catch((err) => {
console.error("[AddKeyForm] Failed to load provider groups:", err);
});
Comment on lines +44 to +46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error handling for loading provider groups only logs to the console. This provides no feedback to the user, who might be confused about why suggestions aren't appearing. It would be better to show a toast notification to inform the user about the failure. You can use a generic error message from your translation files, for example dashboard.error.loadFailed.

    loadGroups.then(setProviderGroupSuggestions).catch((err) => {
      console.error("[AddKeyForm] Failed to load provider groups:", err);
      toast.error("Failed to load provider groups. Please try again.");
    });

}, [user?.id]);

const form = useZodForm({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ export function EditKeyForm({ keyData, user, onSuccess }: EditKeyFormProps) {

// Load provider group suggestions
useEffect(() => {
if (user?.id) {
getAvailableProviderGroups(user.id).then(setProviderGroupSuggestions);
} else {
getAvailableProviderGroups().then(setProviderGroupSuggestions);
}
const loadGroups = user?.id
? getAvailableProviderGroups(user.id)
: getAvailableProviderGroups();

loadGroups.then(setProviderGroupSuggestions).catch((err) => {
console.error("[EditKeyForm] Failed to load provider groups:", err);
});
Comment on lines +59 to +61
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error handling for loading provider groups only logs to the console. This provides no feedback to the user, who might be confused about why suggestions aren't appearing. It would be better to show a toast notification to inform the user about the failure. You can use a generic error message from your translation files, for example dashboard.error.loadFailed.

    loadGroups.then(setProviderGroupSuggestions).catch((err) => {
      console.error("[EditKeyForm] Failed to load provider groups:", err);
      toast.error("Failed to load provider groups. Please try again.");
    });

}, [user?.id]);

const formatExpiresAt = (expiresAt: string) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ export function UserForm({ user, onSuccess, currentUser }: UserFormProps) {

// 加载供应商分组建议
useEffect(() => {
getAvailableProviderGroups().then(setProviderGroupSuggestions);
getAvailableProviderGroups()
.then(setProviderGroupSuggestions)
.catch((err) => {
console.error("[UserForm] Failed to load provider groups:", err);
});
Comment on lines +81 to +83
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error handling for loading provider groups only logs to the console. This provides no feedback to the user, who might be confused about why suggestions aren't appearing. It would be better to show a toast notification to inform the user about the failure. You can use a generic error message from your translation files, for example dashboard.error.loadFailed.

      .catch((err) => {
        console.error("[UserForm] Failed to load provider groups:", err);
        toast.error("Failed to load provider groups. Please try again.");
      });

}, []);

const form = useZodForm({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,14 @@ export function UsageLogsFilters({

// 加载该用户的 keys
if (newUserId) {
const keysResult = await getKeys(newUserId);
if (keysResult.ok && keysResult.data) {
setKeys(keysResult.data);
try {
const keysResult = await getKeys(newUserId);
if (keysResult.ok && keysResult.data) {
setKeys(keysResult.data);
}
Comment on lines +117 to +119
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation handles exceptions when calling getKeys, but it doesn't handle the case where the server action returns a failure ({ ok: false, error: '...' }). This can lead to silent failures where the key list fails to load without any user notification.

        if (keysResult.ok && keysResult.data) {
          setKeys(keysResult.data);
        } else if (!keysResult.ok) {
          console.error("Failed to load keys:", keysResult.error);
          toast.error(t("logs.error.loadKeysFailed"));
        }

} catch (error) {
console.error("Failed to load keys:", error);
toast.error(t("logs.error.loadKeysFailed"));
}
} else {
setKeys([]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,18 +146,26 @@ export function ApiTestButton({
const currentProviderType = apiFormatToProviderType[apiFormat];
if (!currentProviderType) return;

getProviderTestPresets(currentProviderType).then((result) => {
if (result.ok && result.data) {
setPresets(result.data);
// Auto-select first preset if available
if (result.data.length > 0 && !selectedPreset) {
setSelectedPreset(result.data[0].id);
setSuccessContains(result.data[0].defaultSuccessContains);
getProviderTestPresets(currentProviderType)
.then((result) => {
if (result.ok && result.data) {
setPresets(result.data);
// Auto-select first preset if available
if (result.data.length > 0 && !selectedPreset) {
setSelectedPreset(result.data[0].id);
setSuccessContains(result.data[0].defaultSuccessContains);
}
} else {
if (!result.ok) {
console.error("[ApiTestButton] Failed to load presets:", result.error);
}
setPresets([]);
}
} else {
})
.catch((err) => {
console.error("[ApiTestButton] Failed to load presets:", err);
setPresets([]);
}
});
});
}, [apiFormat, apiFormatToProviderType, selectedPreset]);

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,20 @@ export function ProviderRichListItem({
// 处理查看密钥
const handleShowKey = async () => {
setShowKeyDialog(true);
const result = await getUnmaskedProviderKey(provider.id);
if (result.ok) {
setUnmaskedKey(result.data.key);
} else {
try {
const result = await getUnmaskedProviderKey(provider.id);
if (result.ok) {
setUnmaskedKey(result.data.key);
} else {
toast.error(tList("getKeyFailed"), {
description: result.error || tList("unknownError"),
});
setShowKeyDialog(false);
}
} catch (error) {
console.error("Failed to get provider key:", error);
toast.error(tList("getKeyFailed"), {
description: result.error || tList("unknownError"),
description: tList("unknownError"),
});
setShowKeyDialog(false);
}
Expand Down
103 changes: 81 additions & 22 deletions src/app/global-error.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
"use client";

import { isNetworkError } from "@/lib/utils/error-detection";

/**
* 全局错误边界组件
* Global error boundary component
*
* Must be a Client Component with html and body tags
* Displayed when root layout throws an error
*
* 必须是 Client Component,且包含 html 和 body 标签
* 当 root layout 抛出错误时显示
* Note: Most errors should be caught by component-level error boundaries
* or try-catch in event handlers. This is the last resort fallback.
*
* Security: Never display raw error.message to users as it may contain
* sensitive information (database strings, file paths, internal APIs, etc.)
*/
export default function GlobalError({
error,
Expand All @@ -13,6 +21,13 @@ export default function GlobalError({
error: Error & { digest?: string };
reset: () => void;
}) {
// Use shared network error detection
const isNetwork = isNetworkError(error);

const handleGoHome = () => {
window.location.href = "/";
};

return (
<html lang="en">
<body>
Expand All @@ -26,33 +41,77 @@ export default function GlobalError({
fontFamily: "system-ui, sans-serif",
backgroundColor: "#f8f9fa",
padding: "20px",
textAlign: "center",
}}
>
<h2 style={{ fontSize: "24px", marginBottom: "16px", color: "#dc3545" }}>
Something went wrong!
<h2
style={{
fontSize: "24px",
marginBottom: "16px",
color: isNetwork ? "#f59e0b" : "#dc3545",
}}
>
{isNetwork ? "Network Connection Error" : "Something went wrong!"}
</h2>
<p style={{ color: "#6c757d", marginBottom: "24px" }}>
{error.message || "An unexpected error occurred"}
</p>

{isNetwork ? (
<div style={{ color: "#6c757d", marginBottom: "24px", maxWidth: "400px" }}>
<p style={{ marginBottom: "12px" }}>Unable to connect to the server. Please check:</p>
<ul
style={{
textAlign: "left",
margin: "0 auto",
paddingLeft: "20px",
listStyleType: "disc",
}}
>
<li>Your network connection is working</li>
<li>The server is running and accessible</li>
<li>Proxy settings are configured correctly</li>
</ul>
</div>
) : (
<p style={{ color: "#6c757d", marginBottom: "24px", maxWidth: "400px" }}>
An unexpected error occurred. Please try again later.
</p>
)}

{error.digest && (
<p style={{ fontSize: "12px", color: "#adb5bd", marginBottom: "16px" }}>
Error ID: {error.digest}
</p>
)}
<button
onClick={() => reset()}
style={{
padding: "12px 24px",
fontSize: "16px",
backgroundColor: "#0d6efd",
color: "white",
border: "none",
borderRadius: "8px",
cursor: "pointer",
}}
>
Try again
</button>

<div style={{ display: "flex", gap: "12px" }}>
<button
onClick={() => reset()}
style={{
padding: "12px 24px",
fontSize: "16px",
backgroundColor: "#0d6efd",
color: "white",
border: "none",
borderRadius: "8px",
cursor: "pointer",
}}
>
Try again
</button>
<button
onClick={handleGoHome}
style={{
padding: "12px 24px",
fontSize: "16px",
backgroundColor: "#6c757d",
color: "white",
border: "none",
borderRadius: "8px",
cursor: "pointer",
}}
>
Go to Home
</button>
</div>
</div>
</body>
</html>
Expand Down
Loading
Loading