From a46657d5ef2238574f53ac7d365cff9f951db8d5 Mon Sep 17 00:00:00 2001 From: web Date: Tue, 21 Oct 2025 04:10:34 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20Fix=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 27 +++---- apps/admin/components/providers.tsx | 7 ++ apps/admin/services/admin/system.ts | 11 +++ apps/admin/services/admin/typings.d.ts | 15 +++- apps/admin/services/common/auth.ts | 12 +++ apps/admin/services/common/typings.d.ts | 22 +++++- apps/admin/store/stats.ts | 74 ++++++++++++++++++ apps/user/app/auth/phone/auth-form.tsx | 1 + .../app/oauth/[platform]/certification.tsx | 2 + apps/user/services/common/auth.ts | 12 +++ apps/user/services/common/typings.d.ts | 22 +++++- apps/user/services/user/subscribe.ts | 11 +++ apps/user/services/user/typings.d.ts | 56 ++++++++++++- apps/user/services/user/user.ts | 23 ++++++ bun.lockb | Bin 653488 -> 653488 bytes 15 files changed, 276 insertions(+), 19 deletions(-) create mode 100644 apps/admin/store/stats.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e3e2c8..817d855 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,38 +1,35 @@ + # Changelog ## [1.5.2](https://github.com/perfect-panel/ppanel-web/compare/v1.5.1...v1.5.2) (2025-09-29) - ### 🐛 Bug Fixes -* Add step attribute to datetime-local inputs for precise time selection in forms ([32fd181](https://github.com/perfect-panel/ppanel-web/commit/32fd181)) -* Rename 'hysteria2' to 'hysteria' across protocol definitions and schemas for consistency ([5816dd5](https://github.com/perfect-panel/ppanel-web/commit/5816dd5)) -* Update protocol options in ServerConfig for accuracy and consistency ([9266529](https://github.com/perfect-panel/ppanel-web/commit/9266529)) +- Add step attribute to datetime-local inputs for precise time selection in forms ([32fd181](https://github.com/perfect-panel/ppanel-web/commit/32fd181)) +- Rename 'hysteria2' to 'hysteria' across protocol definitions and schemas for consistency ([5816dd5](https://github.com/perfect-panel/ppanel-web/commit/5816dd5)) +- Update protocol options in ServerConfig for accuracy and consistency ([9266529](https://github.com/perfect-panel/ppanel-web/commit/9266529)) ## [1.5.1](https://github.com/perfect-panel/ppanel-web/compare/v1.5.0...v1.5.1) (2025-09-28) - ### 🐛 Bug Fixes -* Simplify protocol enable checks by removing unnecessary false comparisons ([4828700](https://github.com/perfect-panel/ppanel-web/commit/4828700)) +- Simplify protocol enable checks by removing unnecessary false comparisons ([4828700](https://github.com/perfect-panel/ppanel-web/commit/4828700)) # [1.5.0](https://github.com/perfect-panel/ppanel-web/compare/v1.4.8...v1.5.0) (2025-09-28) - ### ✨ Features -* Update server configuration translations for multiple languages ([fc43de1](https://github.com/perfect-panel/ppanel-web/commit/fc43de1)) - +- Update server configuration translations for multiple languages ([fc43de1](https://github.com/perfect-panel/ppanel-web/commit/fc43de1)) ### 🐛 Bug Fixes -* Add DynamicMultiplier component for managing node multipliers and update ServersPage layout ([bb6671c](https://github.com/perfect-panel/ppanel-web/commit/bb6671c)) -* Remove unnecessary blank lines in multiple index files for cleaner code structure ([6a823b8](https://github.com/perfect-panel/ppanel-web/commit/6a823b8)) -* Remove unused ratio variable from server traffic log and server form for cleaner code ([55034dc](https://github.com/perfect-panel/ppanel-web/commit/55034dc)) -* Update Badge variants and restructure traffic ratio display in ServersPage ([3d778e5](https://github.com/perfect-panel/ppanel-web/commit/3d778e5)) -* Update minimum ratio value to 0 in protocol fields and adjust related schemas; enhance unit conversion in ServerConfig ([3b6ef17](https://github.com/perfect-panel/ppanel-web/commit/3b6ef17)) -* Update protocol fields to use 'obfs' instead of 'security' and adjust related configurations ([4abdd36](https://github.com/perfect-panel/ppanel-web/commit/4abdd36)) +- Add DynamicMultiplier component for managing node multipliers and update ServersPage layout ([bb6671c](https://github.com/perfect-panel/ppanel-web/commit/bb6671c)) +- Remove unnecessary blank lines in multiple index files for cleaner code structure ([6a823b8](https://github.com/perfect-panel/ppanel-web/commit/6a823b8)) +- Remove unused ratio variable from server traffic log and server form for cleaner code ([55034dc](https://github.com/perfect-panel/ppanel-web/commit/55034dc)) +- Update Badge variants and restructure traffic ratio display in ServersPage ([3d778e5](https://github.com/perfect-panel/ppanel-web/commit/3d778e5)) +- Update minimum ratio value to 0 in protocol fields and adjust related schemas; enhance unit conversion in ServerConfig ([3b6ef17](https://github.com/perfect-panel/ppanel-web/commit/3b6ef17)) +- Update protocol fields to use 'obfs' instead of 'security' and adjust related configurations ([4abdd36](https://github.com/perfect-panel/ppanel-web/commit/4abdd36)) diff --git a/apps/admin/components/providers.tsx b/apps/admin/components/providers.tsx index 57f1f67..e9feaa6 100644 --- a/apps/admin/components/providers.tsx +++ b/apps/admin/components/providers.tsx @@ -1,6 +1,7 @@ 'use client'; import useGlobalStore, { GlobalStore } from '@/config/use-global'; +import { useStatsStore } from '@/store/stats'; import { Logout } from '@/utils/common'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'; @@ -42,6 +43,12 @@ export default function Providers({ setCommon(common); }, [setCommon, common]); + const { stats } = useStatsStore(); + + useEffect(() => { + stats(); + }, []); + return ( diff --git a/apps/admin/services/admin/system.ts b/apps/admin/services/admin/system.ts index 0944c8b..060005c 100644 --- a/apps/admin/services/admin/system.ts +++ b/apps/admin/services/admin/system.ts @@ -76,6 +76,17 @@ export async function updateNodeConfig(body: API.NodeConfig, options?: { [key: s }); } +/** PreView Node Multiplier GET /v1/admin/system/node_multiplier/preview */ +export async function preViewNodeMultiplier(options?: { [key: string]: any }) { + return request( + '/v1/admin/system/node_multiplier/preview', + { + method: 'GET', + ...(options || {}), + }, + ); +} + /** get Privacy Policy Config GET /v1/admin/system/privacy */ export async function getPrivacyPolicyConfig(options?: { [key: string]: any }) { return request('/v1/admin/system/privacy', { diff --git a/apps/admin/services/admin/typings.d.ts b/apps/admin/services/admin/typings.d.ts index 3ccce61..0d3f11b 100644 --- a/apps/admin/services/admin/typings.d.ts +++ b/apps/admin/services/admin/typings.d.ts @@ -108,6 +108,7 @@ declare namespace API { type AuthConfig = { mobile: MobileAuthenticateConfig; email: EmailAuthticateConfig; + device: DeviceAuthticateConfig; register: PubilcRegisterConfig; }; @@ -456,6 +457,13 @@ declare namespace API { user_subscribe_id: number; }; + type DeviceAuthticateConfig = { + enable: boolean; + show_ads: boolean; + enable_security: boolean; + only_real_device: boolean; + }; + type Document = { id: number; title: string; @@ -1272,7 +1280,7 @@ declare namespace API { has_migrate: boolean; }; - type Hysteria = { + type Hysteria2 = { port: number; hop_ports: string; hop_interval: number; @@ -1495,6 +1503,11 @@ declare namespace API { orderNo: string; }; + type PreViewNodeMultiplierResponse = { + current_time: string; + ratio: number; + }; + type PreviewSubscribeTemplateParams = { id: number; }; diff --git a/apps/admin/services/common/auth.ts b/apps/admin/services/common/auth.ts index 9f51aa8..056ad44 100644 --- a/apps/admin/services/common/auth.ts +++ b/apps/admin/services/common/auth.ts @@ -47,6 +47,18 @@ export async function userLogin(body: API.UserLoginRequest, options?: { [key: st }); } +/** Device Login POST /v1/auth/login/device */ +export async function deviceLogin(body: API.DeviceLoginRequest, options?: { [key: string]: any }) { + return request('/v1/auth/login/device', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }); +} + /** User Telephone login POST /v1/auth/login/telephone */ export async function telephoneLogin( body: API.TelephoneLoginRequest, diff --git a/apps/admin/services/common/typings.d.ts b/apps/admin/services/common/typings.d.ts index ac579d4..787230c 100644 --- a/apps/admin/services/common/typings.d.ts +++ b/apps/admin/services/common/typings.d.ts @@ -114,6 +114,7 @@ declare namespace API { type AuthConfig = { mobile: MobileAuthenticateConfig; email: EmailAuthticateConfig; + device: DeviceAuthticateConfig; register: PubilcRegisterConfig; }; @@ -211,6 +212,19 @@ declare namespace API { currency_symbol: string; }; + type DeviceAuthticateConfig = { + enable: boolean; + show_ads: boolean; + enable_security: boolean; + only_real_device: boolean; + }; + + type DeviceLoginRequest = { + identifier: string; + user_agent: string; + cf_token?: string; + }; + type Document = { id: number; title: string; @@ -324,7 +338,7 @@ declare namespace API { state: string; }; - type Hysteria = { + type Hysteria2 = { port: number; hop_ports: string; hop_interval: number; @@ -706,6 +720,7 @@ declare namespace API { }; type ResetPasswordRequest = { + identifier: string; email: string; password: string; code?: string; @@ -898,6 +913,7 @@ declare namespace API { }; type TelephoneLoginRequest = { + identifier: string; telephone: string; telephone_code: string; telephone_area_code: string; @@ -906,6 +922,7 @@ declare namespace API { }; type TelephoneRegisterRequest = { + identifier: string; telephone: string; telephone_area_code: string; password: string; @@ -915,6 +932,7 @@ declare namespace API { }; type TelephoneResetPasswordRequest = { + identifier: string; telephone: string; telephone_area_code: string; password: string; @@ -1035,12 +1053,14 @@ declare namespace API { }; type UserLoginRequest = { + identifier: string; email: string; password: string; cf_token?: string; }; type UserRegisterRequest = { + identifier: string; email: string; password: string; invite?: string; diff --git a/apps/admin/store/stats.ts b/apps/admin/store/stats.ts new file mode 100644 index 0000000..2844c26 --- /dev/null +++ b/apps/admin/store/stats.ts @@ -0,0 +1,74 @@ +import { create } from 'zustand'; + +// Fixed remote stats endpoint and required header +export const REQUIRED_HEADER_NAME = 'stats'; +export const REQUIRED_HEADER_VALUE = 'ppanel.dev'; +const STATS_URL = 'https://stats.ppanel.dev'; +const STATS_LOADED_KEY = 'ppanel:stats:loaded'; + +interface StatsState { + loading: boolean; + loaded: boolean; + stats: () => Promise; +} + +async function hashHostname(hostname: string): Promise { + try { + const encoder = new TextEncoder(); + const data = encoder.encode(hostname); + const hashBuffer = await crypto.subtle.digest('SHA-256', data); + const hashArray = Array.from(new Uint8Array(hashBuffer)); + return hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); + } catch (e) { + return ''; + } +} + +export const useStatsStore = create((set) => ({ + loading: false, + loaded: + typeof window !== 'undefined' ? Boolean(window.localStorage.getItem(STATS_LOADED_KEY)) : false, + + stats: async () => { + // if already recorded, skip + if (typeof window !== 'undefined') { + try { + if (window.localStorage.getItem(STATS_LOADED_KEY)) return; + } catch {} + } + + set({ loading: true }); + try { + const hostname = + typeof window !== 'undefined' && window.location ? window.location.hostname : ''; + const domain = hostname ? await hashHostname(hostname) : ''; + + await fetch(STATS_URL, { + method: 'POST', + headers: { + [REQUIRED_HEADER_NAME]: REQUIRED_HEADER_VALUE, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + domain, + }), + }); + set({ loaded: true }); + if (typeof window !== 'undefined') { + try { + window.localStorage.setItem(STATS_LOADED_KEY, '1'); + } catch {} + } + } catch (error) { + // treat as completed to avoid repeated attempts + set({ loaded: false }); + if (typeof window !== 'undefined') { + try { + window.localStorage.setItem(STATS_LOADED_KEY, '0'); + } catch {} + } + } finally { + set({ loading: false }); + } + }, +})); diff --git a/apps/user/app/auth/phone/auth-form.tsx b/apps/user/app/auth/phone/auth-form.tsx index 0ce0277..9d91a31 100644 --- a/apps/user/app/auth/phone/auth-form.tsx +++ b/apps/user/app/auth/phone/auth-form.tsx @@ -20,6 +20,7 @@ export default function PhoneAuthForm() { const [type, setType] = useState<'login' | 'register' | 'reset'>('login'); const [loading, startTransition] = useTransition(); const [initialValues, setInitialValues] = useState({ + identifier: '', telephone: '', telephone_area_code: '1', password: '', diff --git a/apps/user/app/oauth/[platform]/certification.tsx b/apps/user/app/oauth/[platform]/certification.tsx index 19cdb56..e28f7fe 100644 --- a/apps/user/app/oauth/[platform]/certification.tsx +++ b/apps/user/app/oauth/[platform]/certification.tsx @@ -19,6 +19,8 @@ export default function Certification({ platform, children }: CertificationProps oAuthLoginGetToken({ method: platform, callback: searchParams, + // @ts-ignore + invite: localStorage.getItem('invite') || '', }) .then((res) => { const token = res?.data?.data?.token; diff --git a/apps/user/services/common/auth.ts b/apps/user/services/common/auth.ts index 9f51aa8..056ad44 100644 --- a/apps/user/services/common/auth.ts +++ b/apps/user/services/common/auth.ts @@ -47,6 +47,18 @@ export async function userLogin(body: API.UserLoginRequest, options?: { [key: st }); } +/** Device Login POST /v1/auth/login/device */ +export async function deviceLogin(body: API.DeviceLoginRequest, options?: { [key: string]: any }) { + return request('/v1/auth/login/device', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }); +} + /** User Telephone login POST /v1/auth/login/telephone */ export async function telephoneLogin( body: API.TelephoneLoginRequest, diff --git a/apps/user/services/common/typings.d.ts b/apps/user/services/common/typings.d.ts index ac579d4..787230c 100644 --- a/apps/user/services/common/typings.d.ts +++ b/apps/user/services/common/typings.d.ts @@ -114,6 +114,7 @@ declare namespace API { type AuthConfig = { mobile: MobileAuthenticateConfig; email: EmailAuthticateConfig; + device: DeviceAuthticateConfig; register: PubilcRegisterConfig; }; @@ -211,6 +212,19 @@ declare namespace API { currency_symbol: string; }; + type DeviceAuthticateConfig = { + enable: boolean; + show_ads: boolean; + enable_security: boolean; + only_real_device: boolean; + }; + + type DeviceLoginRequest = { + identifier: string; + user_agent: string; + cf_token?: string; + }; + type Document = { id: number; title: string; @@ -324,7 +338,7 @@ declare namespace API { state: string; }; - type Hysteria = { + type Hysteria2 = { port: number; hop_ports: string; hop_interval: number; @@ -706,6 +720,7 @@ declare namespace API { }; type ResetPasswordRequest = { + identifier: string; email: string; password: string; code?: string; @@ -898,6 +913,7 @@ declare namespace API { }; type TelephoneLoginRequest = { + identifier: string; telephone: string; telephone_code: string; telephone_area_code: string; @@ -906,6 +922,7 @@ declare namespace API { }; type TelephoneRegisterRequest = { + identifier: string; telephone: string; telephone_area_code: string; password: string; @@ -915,6 +932,7 @@ declare namespace API { }; type TelephoneResetPasswordRequest = { + identifier: string; telephone: string; telephone_area_code: string; password: string; @@ -1035,12 +1053,14 @@ declare namespace API { }; type UserLoginRequest = { + identifier: string; email: string; password: string; cf_token?: string; }; type UserRegisterRequest = { + identifier: string; email: string; password: string; invite?: string; diff --git a/apps/user/services/user/subscribe.ts b/apps/user/services/user/subscribe.ts index 03166a2..945cfee 100644 --- a/apps/user/services/user/subscribe.ts +++ b/apps/user/services/user/subscribe.ts @@ -19,3 +19,14 @@ export async function querySubscribeList( }, ); } + +/** Get user subscribe node info GET /v1/public/subscribe/node/list */ +export async function queryUserSubscribeNodeList(options?: { [key: string]: any }) { + return request( + '/v1/public/subscribe/node/list', + { + method: 'GET', + ...(options || {}), + }, + ); +} diff --git a/apps/user/services/user/typings.d.ts b/apps/user/services/user/typings.d.ts index 60499b8..0eb6201 100644 --- a/apps/user/services/user/typings.d.ts +++ b/apps/user/services/user/typings.d.ts @@ -108,6 +108,7 @@ declare namespace API { type AuthConfig = { mobile: MobileAuthenticateConfig; email: EmailAuthticateConfig; + device: DeviceAuthticateConfig; register: PubilcRegisterConfig; }; @@ -204,6 +205,13 @@ declare namespace API { currency_symbol: string; }; + type DeviceAuthticateConfig = { + enable: boolean; + show_ads: boolean; + enable_security: boolean; + only_real_device: boolean; + }; + type Document = { id: number; title: string; @@ -256,6 +264,11 @@ declare namespace API { list: PaymentMethod[]; }; + type GetDeviceListResponse = { + list: UserDevice[]; + total: number; + }; + type GetLoginLogParams = { page: number; size: number; @@ -343,7 +356,7 @@ declare namespace API { list: Ticket[]; }; - type Hysteria = { + type Hysteria2 = { port: number; hop_ports: string; hop_interval: number; @@ -799,6 +812,10 @@ declare namespace API { total: number; }; + type QueryUserSubscribeNodeListResponse = { + list: UserSubscribeInfo[]; + }; + type RechargeOrderRequest = { amount: number; payment: number; @@ -1039,6 +1056,10 @@ declare namespace API { security_config: SecurityConfig; }; + type UnbindDeviceRequest = { + id: number; + }; + type UnbindOAuthRequest = { method: string; }; @@ -1150,6 +1171,26 @@ declare namespace API { updated_at: number; }; + type UserSubscribeInfo = { + id: number; + user_id: number; + order_id: number; + subscribe_id: number; + start_time: number; + expire_time: number; + finished_at: number; + reset_time: number; + traffic: number; + download: number; + upload: number; + token: string; + status: number; + created_at: number; + updated_at: number; + is_try_out: boolean; + nodes: UserSubscribeNodeInfo[]; + }; + type UserSubscribeLog = { id: number; user_id: number; @@ -1160,6 +1201,19 @@ declare namespace API { timestamp: number; }; + type UserSubscribeNodeInfo = { + id: number; + name: string; + uuid: string; + protocol: string; + port: number; + address: string; + tags: string[]; + country: string; + city: string; + created_at: number; + }; + type VerifyCodeConfig = { verify_code_expire_time: number; verify_code_limit: number; diff --git a/apps/user/services/user/user.ts b/apps/user/services/user/user.ts index 6033aaa..cbb1274 100644 --- a/apps/user/services/user/user.ts +++ b/apps/user/services/user/user.ts @@ -128,6 +128,14 @@ export async function queryUserCommissionLog( ); } +/** Get Device List GET /v1/public/user/devices */ +export async function getDeviceList(options?: { [key: string]: any }) { + return request('/v1/public/user/devices', { + method: 'GET', + ...(options || {}), + }); +} + /** Query User Info GET /v1/public/user/info */ export async function queryUserInfo(options?: { [key: string]: any }) { return request('/v1/public/user/info', { @@ -236,6 +244,21 @@ export async function resetUserSubscribeToken( }); } +/** Unbind Device PUT /v1/public/user/unbind_device */ +export async function unbindDevice( + body: API.UnbindDeviceRequest, + options?: { [key: string]: any }, +) { + return request('/v1/public/user/unbind_device', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + data: body, + ...(options || {}), + }); +} + /** Unbind OAuth POST /v1/public/user/unbind_oauth */ export async function unbindOAuth(body: API.UnbindOAuthRequest, options?: { [key: string]: any }) { return request('/v1/public/user/unbind_oauth', { diff --git a/bun.lockb b/bun.lockb index a6c0067fe631dfc77856541c40612d160620a20b..5a80898b2d5f6db3f7ceeb28e513d563e52c873b 100755 GIT binary patch delta 181 zcmdn+QGEjtJ^aZU?e5ghJl!yrRco@qFG1D@1UDd=V{^o>PA=9OgqXlH&h3({j2`-| z*$6?P!tFCm7&*mRBT)r!cr$WpuukNH+W=B2QNrk1&bk>zuzkjBMj&PaVrC#_0b*7l QW&>h&AO;%$np1)o04KyiX8-^I delta 181 zcmdn+QGEjtJ^aaP?Jm~BG~F1UDd=V{^o>PA=9MgqXlH&h3({j2`-| z-UvaU!tFCm7&*mREl~w;cr$WpuwLYZ+W=B2QNrk1&iWZeuzkjBMj&PaVrC#_0b*7l QW&>h&AO;%$np1)o0AI~M^#A|>