From ece3c71f38635576f6f4cf61f0e8fc27212385d6 Mon Sep 17 00:00:00 2001 From: wonfen Date: Sun, 26 Jul 2026 01:28:11 +0800 Subject: [PATCH] feat(proxies): add empty-state troubleshooting guidance --- .../proxy/proxy-empty-state-model.ts | 88 +++++++++++++ src/components/proxy/proxy-empty-state.tsx | 118 ++++++++++++++++++ src/components/proxy/proxy-groups.tsx | 28 ++++- src/locales/ar/proxies.json | 23 +++- src/locales/de/proxies.json | 23 +++- src/locales/en/proxies.json | 23 +++- src/locales/es/proxies.json | 23 +++- src/locales/fa/proxies.json | 23 +++- src/locales/id/proxies.json | 23 +++- src/locales/jp/proxies.json | 23 +++- src/locales/ko/proxies.json | 23 +++- src/locales/ru/proxies.json | 23 +++- src/locales/tr/proxies.json | 23 +++- src/locales/tt/proxies.json | 23 +++- src/locales/zh/proxies.json | 23 +++- src/locales/zhtw/proxies.json | 23 +++- src/providers/app-data-context.ts | 2 + src/providers/app-data-provider.tsx | 17 ++- src/types/generated/i18n-keys.ts | 11 ++ src/types/generated/i18n-resources.ts | 21 ++++ 20 files changed, 567 insertions(+), 17 deletions(-) create mode 100644 src/components/proxy/proxy-empty-state-model.ts create mode 100644 src/components/proxy/proxy-empty-state.tsx diff --git a/src/components/proxy/proxy-empty-state-model.ts b/src/components/proxy/proxy-empty-state-model.ts new file mode 100644 index 000000000..16c64708c --- /dev/null +++ b/src/components/proxy/proxy-empty-state-model.ts @@ -0,0 +1,88 @@ +import type { ProxyViewV1 } from '@/types/proxy-view' + +export type ProxyEmptyStateReason = + | 'no-subscriptions' + | 'inactive-subscription' + | 'core-unavailable' + | 'no-proxy-info' + +type ProfileSummary = { + current?: string + items?: Array<{ + uid?: string + type?: string + }> +} + +interface ProxyEmptyStateInput { + mode: string + isChainMode: boolean + profiles?: ProfileSummary + isProfilesPending: boolean + proxyView?: ProxyViewV1 + isProxyViewPending: boolean + isProxyViewError: boolean + runningMode?: string + isRunningModePending: boolean +} + +const isSubscriptionProfile = (item: { type?: string }): boolean => + item.type === 'remote' || item.type === 'local' + +export const hasRenderableProxyContent = ( + proxyView: ProxyViewV1 | undefined, + mode: string, + isChainMode: boolean, +) => { + if (!proxyView) return false + + if (isChainMode) { + return proxyView.groups.some( + (group) => group.type === 'Selector' || group.type === 'URLTest', + ) + } + + if (mode === 'rule' || mode === 'script') { + return proxyView.groups.some((group) => !group.hidden) + } + + return proxyView.global !== null +} + +export const resolveProxyEmptyState = ({ + mode, + isChainMode, + profiles, + isProfilesPending, + proxyView, + isProxyViewPending, + isProxyViewError, + runningMode, + isRunningModePending, +}: ProxyEmptyStateInput): ProxyEmptyStateReason | 'loading' | null => { + if (mode === 'direct') return null + + if (isProfilesPending || isProxyViewPending || isRunningModePending) { + return 'loading' + } + + if (profiles) { + const subscriptions = (profiles.items ?? []).filter(isSubscriptionProfile) + if (subscriptions.length === 0) return 'no-subscriptions' + + const hasActiveSubscription = subscriptions.some( + (item) => item.uid === profiles.current, + ) + if (!hasActiveSubscription) return 'inactive-subscription' + } + + if (hasRenderableProxyContent(proxyView, mode, isChainMode)) { + return null + } + + if (runningMode === 'NotRunning' || isProxyViewError) { + return 'core-unavailable' + } + + return 'no-proxy-info' +} diff --git a/src/components/proxy/proxy-empty-state.tsx b/src/components/proxy/proxy-empty-state.tsx new file mode 100644 index 000000000..52bf1f41b --- /dev/null +++ b/src/components/proxy/proxy-empty-state.tsx @@ -0,0 +1,118 @@ +import { Alert, AlertTitle, Box, Button, Stack } from '@mui/material' +import { useLockFn } from 'ahooks' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router' + +import { useAppRefreshers } from '@/providers/app-data-context' +import { openLogsDir, restartCore } from '@/services/cmds' +import { showNotice } from '@/services/notice-service' +import { revalidateQuery } from '@/services/query-client' + +import type { ProxyEmptyStateReason } from './proxy-empty-state-model' + +interface Props { + reason: ProxyEmptyStateReason +} + +export const ProxyEmptyState = ({ reason }: Props) => { + const { t } = useTranslation() + const navigate = useNavigate() + const { refreshProxy } = useAppRefreshers() + const [isRestarting, setIsRestarting] = useState(false) + + const handleRestart = useLockFn(async () => { + setIsRestarting(true) + try { + await restartCore() + await Promise.all([refreshProxy(), revalidateQuery(['getRunningMode'])]) + } catch (error) { + showNotice.error(error) + } finally { + setIsRestarting(false) + } + }) + + const title = + reason === 'no-subscriptions' + ? t('proxies.page.empty.noSubscriptions.title') + : reason === 'inactive-subscription' + ? t('proxies.page.empty.inactiveSubscription.title') + : reason === 'core-unavailable' + ? t('proxies.page.empty.coreUnavailable.title') + : t('proxies.page.empty.noProxyInfo.title') + const description = + reason === 'no-subscriptions' + ? t('proxies.page.empty.noSubscriptions.description') + : reason === 'inactive-subscription' + ? t('proxies.page.empty.inactiveSubscription.description') + : reason === 'core-unavailable' + ? t('proxies.page.empty.coreUnavailable.description') + : t('proxies.page.empty.noProxyInfo.description') + const showProfiles = + reason === 'no-subscriptions' || + reason === 'inactive-subscription' || + reason === 'no-proxy-info' + const showCoreActions = + reason === 'core-unavailable' || reason === 'no-proxy-info' + + return ( + + + {title} + {description} + + + {showProfiles && ( + + )} + + {showCoreActions && ( + <> + + + + )} + + + + ) +} diff --git a/src/components/proxy/proxy-groups.tsx b/src/components/proxy/proxy-groups.tsx index 4b51a1b8b..b1d2bcfd3 100644 --- a/src/components/proxy/proxy-groups.tsx +++ b/src/components/proxy/proxy-groups.tsx @@ -18,9 +18,10 @@ import { StickyVirtualList, type StickyVirtualListHandle, } from '@/components/base' +import { useProfiles } from '@/hooks/use-profiles' import { useProxySelection } from '@/hooks/use-proxy-selection' import { useVerge } from '@/hooks/use-verge' -import { useProxiesData } from '@/providers/app-data-context' +import { useProxiesData, useSystemData } from '@/providers/app-data-context' import delayManager from '@/services/delay' import { isInteractableMember, @@ -30,6 +31,8 @@ import { } from '@/types/proxy-view' import { debugLog } from '@/utils/debug' +import { ProxyEmptyState } from './proxy-empty-state' +import { resolveProxyEmptyState } from './proxy-empty-state-model' import { DEFAULT_HOVER_DELAY, ProxyGroupNavigator, @@ -598,11 +601,34 @@ function NormalProxyGroups(props: { mode: string }) { export const ProxyGroups = (props: Props) => { const { mode, isChainMode = false, chainConfigData } = props + const { profiles, isLoading: isProfilesLoading } = useProfiles() + const { proxyView, isProxyViewPending, isProxyViewError } = useProxiesData() + const { runningMode, isRunningModePending } = useSystemData() + + const emptyState = resolveProxyEmptyState({ + mode, + isChainMode, + profiles, + isProfilesPending: !profiles && isProfilesLoading, + proxyView, + isProxyViewPending, + isProxyViewError, + runningMode, + isRunningModePending, + }) if (mode === 'direct') { return } + if (emptyState === 'loading') { + return + } + + if (emptyState) { + return + } + if (isChainMode) { return } diff --git a/src/locales/ar/proxies.json b/src/locales/ar/proxies.json index ae7eb24c4..cebbe12a8 100644 --- a/src/locales/ar/proxies.json +++ b/src/locales/ar/proxies.json @@ -44,7 +44,28 @@ }, "empty": { "noAvailableGroups": "لا توجد مجموعات وكيل متاحة", - "noProxies": "لا توجد وكلاء" + "noProxies": "لا توجد وكلاء", + "noSubscriptions": { + "title": "لم يتم استيراد أي اشتراك", + "description": "لا يوجد محتوى لعرضه في صفحة الوكلاء. انتقل إلى صفحة «الملفات الشخصية» لاستيراد اشتراك وتفعيله." + }, + "inactiveSubscription": { + "title": "لا يوجد اشتراك مفعّل", + "description": "تم العثور على اشتراكات، لكن لا يوجد أي اشتراك مفعّل حاليًا. انتقل إلى صفحة «الملفات الشخصية» واختر اشتراكًا لتفعيله." + }, + "coreUnavailable": { + "title": "النواة غير متاحة مؤقتًا", + "description": "تعذر الحصول على معلومات الوكلاء من النواة. حاول إعادة تشغيل النواة. إذا استمرت المشكلة، افتح مجلد السجلات وتحقق من سجلات النواة." + }, + "noProxyInfo": { + "title": "معلومات الوكلاء غير متاحة", + "description": "لا توجد مجموعات أو عقد وكلاء لعرضها. تحقق من أن الاشتراك المفعّل يحتوي على مجموعات وعقد صالحة، أو أعد تشغيل النواة وراجع السجلات." + }, + "actions": { + "openProfiles": "الانتقال إلى الملفات الشخصية", + "restartCore": "إعادة تشغيل النواة", + "openLogs": "فتح مجلد السجلات" + } }, "chain": { "header": "Chain Proxy Config", diff --git a/src/locales/de/proxies.json b/src/locales/de/proxies.json index f71289018..cca58c8a9 100644 --- a/src/locales/de/proxies.json +++ b/src/locales/de/proxies.json @@ -44,7 +44,28 @@ }, "empty": { "noAvailableGroups": "Keine Proxy-Gruppen verfügbar", - "noProxies": "Keine Proxys" + "noProxies": "Keine Proxys", + "noSubscriptions": { + "title": "Kein Abonnement importiert", + "description": "Auf der Proxy-Seite gibt es nichts anzuzeigen. Öffnen Sie „Abonnement“, um ein Abonnement zu importieren und zu aktivieren." + }, + "inactiveSubscription": { + "title": "Kein aktives Abonnement", + "description": "Es wurden Abonnements gefunden, aber keines ist derzeit aktiv. Öffnen Sie „Abonnement“, wählen Sie eines aus und aktivieren Sie es." + }, + "coreUnavailable": { + "title": "Kern vorübergehend nicht verfügbar", + "description": "Die Proxy-Informationen konnten nicht vom Kern abgerufen werden. Starten Sie den Kern neu. Falls das Problem weiterhin besteht, öffnen Sie den Protokollordner und prüfen Sie die Kernprotokolle." + }, + "noProxyInfo": { + "title": "Keine Proxy-Informationen verfügbar", + "description": "Es gibt keine Proxy-Gruppen oder -Knoten zum Anzeigen. Prüfen Sie, ob das aktive Abonnement gültige Proxy-Gruppen und -Knoten enthält, oder starten Sie den Kern neu und prüfen Sie die Protokolle." + }, + "actions": { + "openProfiles": "Zum Abonnement", + "restartCore": "Kern neu starten", + "openLogs": "Protokollordner öffnen" + } }, "chain": { "header": "Chain Proxy Config", diff --git a/src/locales/en/proxies.json b/src/locales/en/proxies.json index 0d6a8f36f..f54974713 100644 --- a/src/locales/en/proxies.json +++ b/src/locales/en/proxies.json @@ -44,7 +44,28 @@ }, "empty": { "noAvailableGroups": "No proxy groups are available", - "noProxies": "No proxies" + "noProxies": "No proxies", + "noSubscriptions": { + "title": "No subscription imported", + "description": "There is nothing to display on the Proxies page. Go to Profiles to import and activate a subscription." + }, + "inactiveSubscription": { + "title": "No active subscription", + "description": "Subscriptions were found, but none is currently active. Go to Profiles and select one to activate." + }, + "coreUnavailable": { + "title": "Core temporarily unavailable", + "description": "Proxy information could not be retrieved from the core. Try restarting the core. If the problem persists, open the logs folder and check the core logs." + }, + "noProxyInfo": { + "title": "No proxy information available", + "description": "There are no proxy groups or nodes to display. Check that the active subscription contains valid proxy groups and nodes, or restart the core and review the logs." + }, + "actions": { + "openProfiles": "Go to Profiles", + "restartCore": "Restart Core", + "openLogs": "Open Logs Folder" + } }, "chain": { "header": "Chain Proxy Config", diff --git a/src/locales/es/proxies.json b/src/locales/es/proxies.json index e7eb9bc4b..ea02c26b1 100644 --- a/src/locales/es/proxies.json +++ b/src/locales/es/proxies.json @@ -44,7 +44,28 @@ }, "empty": { "noAvailableGroups": "No hay grupos de proxy disponibles", - "noProxies": "Sin proxies" + "noProxies": "Sin proxies", + "noSubscriptions": { + "title": "No se ha importado ninguna suscripción", + "description": "No hay contenido para mostrar en la página de proxies. Ve a Perfiles para importar y activar una suscripción." + }, + "inactiveSubscription": { + "title": "No hay ninguna suscripción activa", + "description": "Se encontraron suscripciones, pero ninguna está activa. Ve a Perfiles, selecciona una suscripción y actívala." + }, + "coreUnavailable": { + "title": "El núcleo no está disponible temporalmente", + "description": "No se pudo obtener la información de proxies del núcleo. Intenta reiniciar el núcleo. Si el problema continúa, abre la carpeta de registros y revisa los registros del núcleo." + }, + "noProxyInfo": { + "title": "No hay información de proxies disponible", + "description": "No hay grupos ni nodos de proxy para mostrar. Comprueba que la suscripción activa contenga grupos y nodos válidos, o reinicia el núcleo y revisa los registros." + }, + "actions": { + "openProfiles": "Ir a Perfiles", + "restartCore": "Reiniciar núcleo", + "openLogs": "Abrir carpeta de registros" + } }, "chain": { "header": "Chain Proxy Config", diff --git a/src/locales/fa/proxies.json b/src/locales/fa/proxies.json index ee5e4c2f6..3b9185f0f 100644 --- a/src/locales/fa/proxies.json +++ b/src/locales/fa/proxies.json @@ -44,7 +44,28 @@ }, "empty": { "noAvailableGroups": "هیچ گروه پروکسی در دسترس نیست", - "noProxies": "پروکسی‌ای وجود ندارد" + "noProxies": "پروکسی‌ای وجود ندارد", + "noSubscriptions": { + "title": "هیچ اشتراکی وارد نشده است", + "description": "چیزی برای نمایش در صفحه پروکسی وجود ندارد. برای وارد کردن و فعال‌سازی اشتراک به صفحه «پروفایل‌ها» بروید." + }, + "inactiveSubscription": { + "title": "هیچ اشتراک فعالی وجود ندارد", + "description": "اشتراک‌هایی پیدا شدند، اما در حال حاضر هیچ‌کدام فعال نیستند. به صفحه «پروفایل‌ها» بروید، یک اشتراک را انتخاب و فعال کنید." + }, + "coreUnavailable": { + "title": "هسته موقتاً در دسترس نیست", + "description": "دریافت اطلاعات پروکسی از هسته ممکن نشد. هسته را دوباره راه‌اندازی کنید. اگر مشکل ادامه داشت، پوشه گزارش‌ها را باز کرده و گزارش‌های هسته را بررسی کنید." + }, + "noProxyInfo": { + "title": "اطلاعات پروکسی در دسترس نیست", + "description": "هیچ گروه یا گره پروکسی برای نمایش وجود ندارد. بررسی کنید که اشتراک فعال دارای گروه‌ها و گره‌های معتبر باشد، یا هسته را دوباره راه‌اندازی کرده و گزارش‌ها را ببینید." + }, + "actions": { + "openProfiles": "رفتن به پروفایل‌ها", + "restartCore": "راه‌اندازی مجدد هسته", + "openLogs": "باز کردن پوشه گزارش‌ها" + } }, "chain": { "header": "Chain Proxy Config", diff --git a/src/locales/id/proxies.json b/src/locales/id/proxies.json index 473984537..869c0768c 100644 --- a/src/locales/id/proxies.json +++ b/src/locales/id/proxies.json @@ -44,7 +44,28 @@ }, "empty": { "noAvailableGroups": "Tidak ada grup proxy yang tersedia", - "noProxies": "Tidak ada proxy" + "noProxies": "Tidak ada proxy", + "noSubscriptions": { + "title": "Belum ada langganan yang diimpor", + "description": "Tidak ada yang dapat ditampilkan di halaman Proksi. Buka halaman Profil untuk mengimpor dan mengaktifkan langganan." + }, + "inactiveSubscription": { + "title": "Tidak ada langganan aktif", + "description": "Langganan ditemukan, tetapi tidak ada yang sedang aktif. Buka halaman Profil, lalu pilih dan aktifkan langganan." + }, + "coreUnavailable": { + "title": "Inti sementara tidak tersedia", + "description": "Informasi proksi tidak dapat diambil dari inti. Coba mulai ulang inti. Jika masalah berlanjut, buka folder log dan periksa log inti." + }, + "noProxyInfo": { + "title": "Informasi proksi tidak tersedia", + "description": "Tidak ada grup atau node proksi yang dapat ditampilkan. Pastikan langganan aktif berisi grup dan node yang valid, atau mulai ulang inti dan periksa log." + }, + "actions": { + "openProfiles": "Buka Profil", + "restartCore": "Mulai Ulang Inti", + "openLogs": "Buka Folder Log" + } }, "chain": { "header": "Chain Proxy Config", diff --git a/src/locales/jp/proxies.json b/src/locales/jp/proxies.json index 1e345dd49..b3f0b17d5 100644 --- a/src/locales/jp/proxies.json +++ b/src/locales/jp/proxies.json @@ -44,7 +44,28 @@ }, "empty": { "noAvailableGroups": "利用可能なプロキシグループがありません", - "noProxies": "プロキシがありません" + "noProxies": "プロキシがありません", + "noSubscriptions": { + "title": "サブスクリプションがインポートされていません", + "description": "プロキシページに表示する内容がありません。プロファイルページでサブスクリプションをインポートして有効化してください。" + }, + "inactiveSubscription": { + "title": "有効なサブスクリプションがありません", + "description": "サブスクリプションは見つかりましたが、現在有効なものがありません。プロファイルページで選択して有効化してください。" + }, + "coreUnavailable": { + "title": "コアを一時的に利用できません", + "description": "コアからプロキシ情報を取得できませんでした。コアを再起動してください。問題が解決しない場合は、ログフォルダーを開いてコアのログを確認してください。" + }, + "noProxyInfo": { + "title": "プロキシ情報を取得できませんでした", + "description": "表示できるプロキシグループまたはノードがありません。有効なサブスクリプションに正しいプロキシグループとノードが含まれているか確認するか、コアを再起動してログを確認してください。" + }, + "actions": { + "openProfiles": "プロファイルへ移動", + "restartCore": "コアを再起動", + "openLogs": "ログフォルダーを開く" + } }, "chain": { "header": "Chain Proxy Config", diff --git a/src/locales/ko/proxies.json b/src/locales/ko/proxies.json index 43b5b18c2..b534797c5 100644 --- a/src/locales/ko/proxies.json +++ b/src/locales/ko/proxies.json @@ -44,7 +44,28 @@ }, "empty": { "noAvailableGroups": "사용 가능한 프록시 그룹이 없습니다", - "noProxies": "프록시 없음" + "noProxies": "프록시 없음", + "noSubscriptions": { + "title": "가져온 구독이 없습니다", + "description": "프록시 페이지에 표시할 내용이 없습니다. 프로필 페이지에서 구독을 가져와 활성화하세요." + }, + "inactiveSubscription": { + "title": "활성화된 구독이 없습니다", + "description": "구독이 있지만 현재 활성화된 구독이 없습니다. 프로필 페이지에서 구독을 선택하고 활성화하세요." + }, + "coreUnavailable": { + "title": "코어를 일시적으로 사용할 수 없습니다", + "description": "코어에서 프록시 정보를 가져오지 못했습니다. 코어를 재시작해 보세요. 문제가 계속되면 로그 폴더를 열어 코어 로그를 확인하세요." + }, + "noProxyInfo": { + "title": "프록시 정보를 가져오지 못했습니다", + "description": "표시할 프록시 그룹이나 노드가 없습니다. 활성 구독에 유효한 프록시 그룹과 노드가 포함되어 있는지 확인하거나 코어를 재시작하고 로그를 확인하세요." + }, + "actions": { + "openProfiles": "프로필로 이동", + "restartCore": "코어 재시작", + "openLogs": "로그 폴더 열기" + } }, "chain": { "header": "체인 프록시 구성", diff --git a/src/locales/ru/proxies.json b/src/locales/ru/proxies.json index a9e5a1538..fdce686d0 100644 --- a/src/locales/ru/proxies.json +++ b/src/locales/ru/proxies.json @@ -44,7 +44,28 @@ }, "empty": { "noAvailableGroups": "Нет доступных групп прокси", - "noProxies": "Нет прокси" + "noProxies": "Нет прокси", + "noSubscriptions": { + "title": "Нет импортированных подписок", + "description": "На странице прокси нечего отображать. Перейдите в раздел «Профили», чтобы импортировать и активировать подписку." + }, + "inactiveSubscription": { + "title": "Нет активной подписки", + "description": "Подписки найдены, но ни одна из них сейчас не активна. Перейдите в раздел «Профили», выберите подписку и активируйте её." + }, + "coreUnavailable": { + "title": "Ядро временно недоступно", + "description": "Не удалось получить информацию о прокси от ядра. Попробуйте перезапустить ядро. Если проблема сохраняется, откройте папку журналов и проверьте журналы ядра." + }, + "noProxyInfo": { + "title": "Нет информации о прокси", + "description": "Нет групп или узлов прокси для отображения. Убедитесь, что активная подписка содержит допустимые группы и узлы, либо перезапустите ядро и проверьте журналы." + }, + "actions": { + "openProfiles": "Перейти к профилям", + "restartCore": "Перезапустить ядро", + "openLogs": "Открыть папку журналов" + } }, "chain": { "header": "Цепочка прокси", diff --git a/src/locales/tr/proxies.json b/src/locales/tr/proxies.json index 2849f21b6..00ee3b212 100644 --- a/src/locales/tr/proxies.json +++ b/src/locales/tr/proxies.json @@ -44,7 +44,28 @@ }, "empty": { "noAvailableGroups": "Kullanılabilir proxy grubu yok", - "noProxies": "Proxy yok" + "noProxies": "Proxy yok", + "noSubscriptions": { + "title": "İçe aktarılmış abonelik yok", + "description": "Proxy sayfasında görüntülenecek içerik yok. Bir aboneliği içe aktarmak ve etkinleştirmek için Profiller sayfasına gidin." + }, + "inactiveSubscription": { + "title": "Etkin abonelik yok", + "description": "Abonelikler bulundu ancak hiçbiri şu anda etkin değil. Profiller sayfasına gidip bir abonelik seçin ve etkinleştirin." + }, + "coreUnavailable": { + "title": "Çekirdek geçici olarak kullanılamıyor", + "description": "Proxy bilgileri çekirdekten alınamadı. Çekirdeği yeniden başlatmayı deneyin. Sorun devam ederse günlükler klasörünü açıp çekirdek günlüklerini kontrol edin." + }, + "noProxyInfo": { + "title": "Proxy bilgisi yok", + "description": "Görüntülenecek proxy grubu veya düğümü yok. Etkin aboneliğin geçerli proxy grupları ve düğümleri içerdiğini doğrulayın ya da çekirdeği yeniden başlatıp günlükleri inceleyin." + }, + "actions": { + "openProfiles": "Profillere Git", + "restartCore": "Çekirdeği Yeniden Başlat", + "openLogs": "Günlükler Klasörünü Aç" + } }, "chain": { "header": "Chain Proxy Config", diff --git a/src/locales/tt/proxies.json b/src/locales/tt/proxies.json index e20331ffd..53b3aa9ac 100644 --- a/src/locales/tt/proxies.json +++ b/src/locales/tt/proxies.json @@ -44,7 +44,28 @@ }, "empty": { "noAvailableGroups": "Кулланырлык прокси төркемнәре юк", - "noProxies": "Проксилар юк" + "noProxies": "Проксилар юк", + "noSubscriptions": { + "title": "Язылу импортланмаган", + "description": "Прокси битендә күрсәтерлек мәгълүмат юк. Язылуны импортлау һәм активлаштыру өчен «Профильләр» битенә күчегез." + }, + "inactiveSubscription": { + "title": "Актив язылу юк", + "description": "Язылулар табылды, ләкин хәзер берсе дә актив түгел. «Профильләр» битенә күчеп, язылуны сайлагыз һәм активлаштырыгыз." + }, + "coreUnavailable": { + "title": "Ядро вакытлыча кулланылмый", + "description": "Ядродан прокси мәгълүматын алып булмады. Ядроны яңадан эшләтеп карагыз. Мәсьәлә чишелмәсә, журналлар папкасын ачып, ядро журналларын тикшерегез." + }, + "noProxyInfo": { + "title": "Прокси мәгълүматы юк", + "description": "Күрсәтү өчен прокси төркемнәре яки төеннәре юк. Актив язылуда дөрес төркемнәр һәм төеннәр булуын тикшерегез, яки ядроны яңадан эшләтеп журналларны карагыз." + }, + "actions": { + "openProfiles": "Профильләргә күчү", + "restartCore": "Ядроны яңадан эшләтү", + "openLogs": "Журналлар папкасын ачу" + } }, "chain": { "header": "Chain Proxy Config", diff --git a/src/locales/zh/proxies.json b/src/locales/zh/proxies.json index 54c2dd3c1..2713cc1af 100644 --- a/src/locales/zh/proxies.json +++ b/src/locales/zh/proxies.json @@ -44,7 +44,28 @@ }, "empty": { "noAvailableGroups": "暂无可用代理组", - "noProxies": "暂无代理" + "noProxies": "暂无代理", + "noSubscriptions": { + "title": "尚未导入订阅", + "description": "代理页没有可展示的内容。请先前往“订阅”页面导入并启用订阅。" + }, + "inactiveSubscription": { + "title": "尚未激活订阅", + "description": "检测到已有订阅,但当前没有有效订阅被激活。请前往“订阅”页面选择并激活一个订阅。" + }, + "coreUnavailable": { + "title": "内核暂不可用", + "description": "无法从内核获取代理信息。请尝试重启内核;若问题仍然存在,请打开日志目录检查内核日志。" + }, + "noProxyInfo": { + "title": "未获取到代理信息", + "description": "当前没有可展示的代理组或节点。请检查当前订阅是否包含有效的代理组和节点,或尝试重启内核并查看日志。" + }, + "actions": { + "openProfiles": "前往订阅", + "restartCore": "重启内核", + "openLogs": "打开日志目录" + } }, "chain": { "header": "代理链配置", diff --git a/src/locales/zhtw/proxies.json b/src/locales/zhtw/proxies.json index 74b9bdced..c2e805457 100644 --- a/src/locales/zhtw/proxies.json +++ b/src/locales/zhtw/proxies.json @@ -44,7 +44,28 @@ }, "empty": { "noAvailableGroups": "目前沒有可用的代理群組", - "noProxies": "目前沒有代理" + "noProxies": "目前沒有代理", + "noSubscriptions": { + "title": "尚未匯入訂閱", + "description": "代理頁沒有可顯示的內容。請先前往「訂閱」頁面匯入並啟用訂閱。" + }, + "inactiveSubscription": { + "title": "尚未啟用訂閱", + "description": "偵測到已有訂閱,但目前沒有有效訂閱被啟用。請前往「訂閱」頁面選擇並啟用一個訂閱。" + }, + "coreUnavailable": { + "title": "核心暫時無法使用", + "description": "無法從核心取得代理資訊。請嘗試重新啟動核心;若問題仍然存在,請開啟日誌目錄檢查核心日誌。" + }, + "noProxyInfo": { + "title": "未取得代理資訊", + "description": "目前沒有可顯示的代理群組或節點。請檢查目前訂閱是否包含有效的代理群組和節點,或嘗試重新啟動核心並查看日誌。" + }, + "actions": { + "openProfiles": "前往訂閱", + "restartCore": "重新啟動核心", + "openLogs": "開啟日誌目錄" + } }, "chain": { "header": "鏈式代理設定", diff --git a/src/providers/app-data-context.ts b/src/providers/app-data-context.ts index e21882252..001337572 100644 --- a/src/providers/app-data-context.ts +++ b/src/providers/app-data-context.ts @@ -6,6 +6,7 @@ import type { ProxyViewV1 } from '@/types/proxy-view' export interface ProxiesContextType { proxyView: ProxyViewV1 | undefined isProxyViewPending: boolean + isProxyViewError: boolean } export interface RulesContextType { @@ -21,6 +22,7 @@ export interface ClashConfigContextType { export interface SystemContextType { sysproxy: any runningMode?: string + isRunningModePending: boolean systemProxyAddress: string } diff --git a/src/providers/app-data-provider.tsx b/src/providers/app-data-provider.tsx index 8e7dc5afb..f1d7d3a0e 100644 --- a/src/providers/app-data-provider.tsx +++ b/src/providers/app-data-provider.tsx @@ -60,6 +60,7 @@ export const AppDataProvider = ({ const { data: proxyView, + error: proxyViewError, isPending: isProxyViewPending, refetch: _refetchProxyView, } = useQuery({ @@ -99,7 +100,7 @@ export const AppDataProvider = ({ ...TQ_DEFAULTS, }) - const { data: runningMode } = useQuery({ + const { data: runningMode, isPending: isRunningModePending } = useQuery({ queryKey: ['getRunningMode'], queryFn: getRunningMode, ...TQ_DEFAULTS, @@ -216,8 +217,9 @@ export const AppDataProvider = ({ () => ({ proxyView, isProxyViewPending, + isProxyViewError: Boolean(proxyViewError), }), - [proxyView, isProxyViewPending], + [proxyView, isProxyViewPending, proxyViewError], ) const rulesValue = useMemo( @@ -273,9 +275,18 @@ export const AppDataProvider = ({ return { sysproxy, runningMode, + isRunningModePending, systemProxyAddress: calculateSystemProxyAddress(), } - }, [sysproxy, runningMode, verge, clashConfig, runtimeConfig, clashInfo]) + }, [ + sysproxy, + runningMode, + isRunningModePending, + verge, + clashConfig, + runtimeConfig, + clashInfo, + ]) const uptimeValue = useMemo(() => ({ uptime: uptimeData || 0 }), [uptimeData]) diff --git a/src/types/generated/i18n-keys.ts b/src/types/generated/i18n-keys.ts index b555a6afe..222a02d89 100644 --- a/src/types/generated/i18n-keys.ts +++ b/src/types/generated/i18n-keys.ts @@ -288,6 +288,17 @@ export const translationKeys = [ 'proxies.page.placeholders.delayCheckUrl', 'proxies.page.empty.noAvailableGroups', 'proxies.page.empty.noProxies', + 'proxies.page.empty.noSubscriptions.title', + 'proxies.page.empty.noSubscriptions.description', + 'proxies.page.empty.inactiveSubscription.title', + 'proxies.page.empty.inactiveSubscription.description', + 'proxies.page.empty.coreUnavailable.title', + 'proxies.page.empty.coreUnavailable.description', + 'proxies.page.empty.noProxyInfo.title', + 'proxies.page.empty.noProxyInfo.description', + 'proxies.page.empty.actions.openProfiles', + 'proxies.page.empty.actions.restartCore', + 'proxies.page.empty.actions.openLogs', 'proxies.page.chain.header', 'proxies.page.chain.warning', 'proxies.page.chain.empty', diff --git a/src/types/generated/i18n-resources.ts b/src/types/generated/i18n-resources.ts index c7ed9187b..d505d7cae 100644 --- a/src/types/generated/i18n-resources.ts +++ b/src/types/generated/i18n-resources.ts @@ -508,8 +508,29 @@ export interface TranslationResources { warning: string } empty: { + actions: { + openLogs: string + openProfiles: string + restartCore: string + } + coreUnavailable: { + description: string + title: string + } + inactiveSubscription: { + description: string + title: string + } noAvailableGroups: string noProxies: string + noProxyInfo: { + description: string + title: string + } + noSubscriptions: { + description: string + title: string + } } labels: { delayCheckReset: string