merge: 同步 origin/main (v1.4.0) 到定制版本
Some checks failed
Build and Release / Build (push) Has been cancelled

This commit is contained in:
shanshanzhong 2026-03-19 02:56:42 -07:00
parent 6b92979c7c
commit 830430645c
12 changed files with 209 additions and 30 deletions

View File

@ -19,6 +19,30 @@ This document records all notable changes to ShadCN Admin.
--- ---
## [1.4.0](https://github.com/perfect-panel/frontend/compare/v1.3.15...v1.4.0) (2026-03-13)
### ✨ Features / 新功能
* **servers:** add Reality support for anytls; fix vless flow ([be24ba0](https://github.com/perfect-panel/frontend/commit/be24ba03f564181d2bd6bc611917004d24e21aee))
## [1.3.15](https://github.com/perfect-panel/frontend/compare/v1.3.14...v1.3.15) (2026-03-12)
### 🐛 Bug Fixes / 问题修复
* **ci:** remove deprecated forwardRef usage in turnstile components ([89ece6e](https://github.com/perfect-panel/frontend/commit/89ece6e959892808c3a67ffb43612c9b90f1e38a))
## [1.3.14](https://github.com/perfect-panel/frontend/compare/v1.3.13...v1.3.14) (2026-03-11)
### 🐛 Bug Fixes / 问题修复
* improve renewal button readability on subscription card ([#25](https://github.com/perfect-panel/frontend/issues/25)) ([ced5c1d](https://github.com/perfect-panel/frontend/commit/ced5c1d24e5af26ff8fdf700fd2b9b864706694a))
## [1.3.13](https://github.com/perfect-panel/frontend/compare/v1.3.12...v1.3.13) (2026-03-07)
### 🐛 Bug Fixes / 问题修复
* **user:** show expire time & improve renewal dialog on mobile ([#22](https://github.com/perfect-panel/frontend/issues/22)) ([889dbf9](https://github.com/perfect-panel/frontend/commit/889dbf97736252d54d3e1d7ed0622e91ee4e8a1e))
## [1.3.12](https://github.com/perfect-panel/frontend/compare/v1.3.11...v1.3.12) (2026-02-26) ## [1.3.12](https://github.com/perfect-panel/frontend/compare/v1.3.11...v1.3.12) (2026-02-26)
### 🐛 Bug Fixes / 问题修复 ### 🐛 Bug Fixes / 问题修复

View File

@ -14,6 +14,8 @@
}, },
"dependencies": { "dependencies": {
"@faker-js/faker": "^10.0.0", "@faker-js/faker": "^10.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@lottiefiles/dotlottie-react": "^0.17.7", "@lottiefiles/dotlottie-react": "^0.17.7",
"@noble/curves": "^2.0.1", "@noble/curves": "^2.0.1",
"@stripe/react-stripe-js": "^5.4.0", "@stripe/react-stripe-js": "^5.4.0",

View File

@ -1,5 +1,5 @@
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { forwardRef, useEffect, useImperativeHandle } from "react"; import { type RefObject, useEffect, useImperativeHandle } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Turnstile, { useTurnstile } from "react-turnstile"; import Turnstile, { useTurnstile } from "react-turnstile";
@ -9,14 +9,17 @@ export type TurnstileRef = {
reset: () => void; reset: () => void;
}; };
const CloudFlareTurnstile = forwardRef< const CloudFlareTurnstile = function CloudFlareTurnstile({
TurnstileRef, id,
{ value,
onChange,
ref,
}: {
id?: string; id?: string;
value?: null | string; value?: null | string;
onChange: (value?: string) => void; onChange: (value?: string) => void;
} ref?: RefObject<TurnstileRef | null>;
>(function CloudFlareTurnstile({ id, value, onChange }, ref) { }) {
const { common } = useGlobalStore(); const { common } = useGlobalStore();
const { verify } = common; const { verify } = common;
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
@ -58,6 +61,6 @@ const CloudFlareTurnstile = forwardRef<
/> />
) )
); );
}); };
export default CloudFlareTurnstile; export default CloudFlareTurnstile;

View File

@ -66,18 +66,13 @@ export const SECURITY = {
trojan: ["tls"] as const, trojan: ["tls"] as const,
hysteria: ["tls"] as const, hysteria: ["tls"] as const,
tuic: ["tls"] as const, tuic: ["tls"] as const,
anytls: ["tls"] as const, anytls: ["none", "tls", "reality"] as const,
naive: ["none", "tls"] as const, naive: ["none", "tls"] as const,
http: ["none", "tls"] as const, http: ["none", "tls"] as const,
} as const; } as const;
export const FLOWS = { export const FLOWS = {
vless: [ vless: ["none", "xtls-rprx-vision"] as const,
"none",
"xtls-rprx-direct",
"xtls-rprx-splice",
"xtls-rprx-vision",
] as const,
} as const; } as const;
export const TUIC_UDP_RELAY_MODES = ["native", "quic"] as const; export const TUIC_UDP_RELAY_MODES = ["native", "quic"] as const;

View File

@ -184,6 +184,11 @@ export function getProtocolDefaultConfig(proto: ProtocolType) {
cert_mode: "none", cert_mode: "none",
cert_dns_provider: null, cert_dns_provider: null,
cert_dns_env: null, cert_dns_env: null,
reality_server_addr: null,
reality_server_port: null,
reality_private_key: null,
reality_public_key: null,
reality_short_id: null,
ratio: 1, ratio: 1,
} as any; } as any;
default: default:

View File

@ -156,6 +156,11 @@ const anytls = z.object({
cert_mode: z.enum(CERT_MODES).nullish(), cert_mode: z.enum(CERT_MODES).nullish(),
cert_dns_provider: nullableString, cert_dns_provider: nullableString,
cert_dns_env: nullableString, cert_dns_env: nullableString,
reality_server_addr: nullableString,
reality_server_port: nullablePort,
reality_private_key: nullableString,
reality_public_key: nullableString,
reality_short_id: nullableString,
}); });
const socks = z.object({ const socks = z.object({

View File

@ -279,7 +279,10 @@ export function useProtocolFields() {
options: FLOWS.vless, options: FLOWS.vless,
defaultValue: "none", defaultValue: "none",
group: "transport", group: "transport",
condition: (p) => p.transport === "tcp", condition: (p) =>
p.encryption === "mlkem768x25519plus" ||
(p.transport === "tcp" &&
(p.security === "tls" || p.security === "reality")),
}, },
{ {
name: "security", name: "security",
@ -441,7 +444,7 @@ export function useProtocolFields() {
name: "encryption_ticket", name: "encryption_ticket",
type: "input", type: "input",
label: t("encryption_ticket", "Ticket Time"), label: t("encryption_ticket", "Ticket Time"),
placeholder: "e.g. 600s", placeholder: "e.g. 600",
group: "encryption", group: "encryption",
condition: (p) => condition: (p) =>
p.encryption === "mlkem768x25519plus" && p.encryption === "mlkem768x25519plus" &&
@ -1093,6 +1096,14 @@ export function useProtocolFields() {
placeholder: "1-65535", placeholder: "1-65535",
group: "basic", group: "basic",
}, },
{
name: "security",
type: "select",
label: t("security", "Security"),
options: SECURITY.anytls,
defaultValue: "tls",
group: "security",
},
{ {
name: "padding_scheme", name: "padding_scheme",
type: "textarea", type: "textarea",
@ -1108,12 +1119,14 @@ export function useProtocolFields() {
type: "input", type: "input",
label: t("security_sni", "SNI"), label: t("security_sni", "SNI"),
group: "security", group: "security",
condition: (p) => p.security !== "none",
}, },
{ {
name: "allow_insecure", name: "allow_insecure",
type: "switch", type: "switch",
label: t("security_allow_insecure", "Allow Insecure"), label: t("security_allow_insecure", "Allow Insecure"),
group: "security", group: "security",
condition: (p) => p.security !== "none",
}, },
{ {
name: "fingerprint", name: "fingerprint",
@ -1122,6 +1135,7 @@ export function useProtocolFields() {
options: FINGERPRINTS, options: FINGERPRINTS,
defaultValue: "chrome", defaultValue: "chrome",
group: "security", group: "security",
condition: (p) => p.security !== "none",
}, },
{ {
name: "cert_mode", name: "cert_mode",
@ -1130,6 +1144,7 @@ export function useProtocolFields() {
options: CERT_MODES, options: CERT_MODES,
defaultValue: "none", defaultValue: "none",
group: "security", group: "security",
condition: (p) => p.security === "tls",
}, },
{ {
name: "cert_dns_provider", name: "cert_dns_provider",
@ -1148,6 +1163,63 @@ export function useProtocolFields() {
group: "security", group: "security",
condition: (p) => p.cert_mode === "dns", condition: (p) => p.cert_mode === "dns",
}, },
{
name: "reality_server_addr",
type: "input",
label: t("security_server_address", "Reality Server Address"),
placeholder: t(
"security_server_address_placeholder",
"e.g. 1.2.3.4 or domain"
),
group: "reality",
condition: (p) => p.security === "reality",
},
{
name: "reality_server_port",
type: "number",
label: t("security_server_port", "Reality Server Port"),
min: 1,
max: 65_535,
placeholder: "1-65535",
group: "reality",
condition: (p) => p.security === "reality",
},
{
name: "reality_private_key",
type: "input",
label: t("security_private_key", "Reality Private Key"),
placeholder: t(
"security_private_key_placeholder",
"Enter private key"
),
group: "reality",
generate: {
function: generateRealityKeyPair,
updateFields: {
reality_private_key: "privateKey",
reality_public_key: "publicKey",
} as Record<string, string>,
},
condition: (p) => p.security === "reality",
},
{
name: "reality_public_key",
type: "input",
label: t("security_public_key", "Reality Public Key"),
placeholder: t("security_public_key_placeholder", "Enter public key"),
group: "reality",
condition: (p) => p.security === "reality",
},
{
name: "reality_short_id",
type: "input",
label: t("security_short_id", "Reality Short ID"),
group: "reality",
generate: {
function: generateRealityShortId,
},
condition: (p) => p.security === "reality",
},
], ],
}), }),
[t] [t]

View File

@ -14,6 +14,8 @@
}, },
"dependencies": { "dependencies": {
"@faker-js/faker": "^10.0.0", "@faker-js/faker": "^10.0.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"@lottiefiles/dotlottie-react": "^0.17.7", "@lottiefiles/dotlottie-react": "^0.17.7",
"@stripe/react-stripe-js": "^5.4.0", "@stripe/react-stripe-js": "^5.4.0",
"@stripe/stripe-js": "^8.5.2", "@stripe/stripe-js": "^8.5.2",

View File

@ -1,7 +1,7 @@
"use client"; "use client";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { forwardRef, useEffect, useImperativeHandle } from "react"; import { type RefObject, useEffect, useImperativeHandle } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import Turnstile, { useTurnstile } from "react-turnstile"; import Turnstile, { useTurnstile } from "react-turnstile";
import { useGlobalStore } from "@/stores/global"; import { useGlobalStore } from "@/stores/global";
@ -10,14 +10,17 @@ export type TurnstileRef = {
reset: () => void; reset: () => void;
}; };
const CloudFlareTurnstile = forwardRef< const CloudFlareTurnstile = function CloudFlareTurnstile({
TurnstileRef, id,
{ value,
onChange,
ref,
}: {
id?: string; id?: string;
value?: null | string; value?: null | string;
onChange: (value?: string) => void; onChange: (value?: string) => void;
} ref?: RefObject<TurnstileRef | null>;
>(function CloudFlareTurnstile({ id, value, onChange }, ref) { }) {
const { common } = useGlobalStore(); const { common } = useGlobalStore();
const { verify } = common; const { verify } = common;
const { resolvedTheme } = useTheme(); const { resolvedTheme } = useTheme();
@ -63,6 +66,6 @@ const CloudFlareTurnstile = forwardRef<
/> />
) )
); );
}); };
export default CloudFlareTurnstile; export default CloudFlareTurnstile;

5
bunfig.toml Normal file
View File

@ -0,0 +1,5 @@
[install]
# Use hoisted (flat) node_modules layout instead of isolated installs.
# This ensures Vite/Rollup can resolve dependencies correctly in CI environments
# such as Cloudflare Pages.
linker = "hoisted"

63
functions/v1/[[path]].ts Normal file
View File

@ -0,0 +1,63 @@
interface Env {
API_BASE_URL: string;
}
export const onRequest: PagesFunction<Env> = async (context) => {
const { request, env } = context;
const apiBase = (env.API_BASE_URL || "https://api.ppanel.dev").replace(
/\/$/,
"",
);
const url = new URL(request.url);
const targetUrl = `${apiBase}${url.pathname}${url.search}`;
const headers = new Headers(request.headers);
headers.set("Host", new URL(apiBase).host);
headers.delete("cf-connecting-ip");
headers.delete("cf-ipcountry");
headers.delete("cf-ray");
headers.delete("cf-visitor");
const init: RequestInit = {
method: request.method,
headers,
redirect: "follow",
};
if (
request.method !== "GET" &&
request.method !== "HEAD" &&
request.body !== null
) {
init.body = request.body;
}
const response = await fetch(targetUrl, init);
const responseHeaders = new Headers(response.headers);
responseHeaders.delete("set-cookie");
responseHeaders.set("Access-Control-Allow-Origin", url.origin);
responseHeaders.set(
"Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, PATCH, OPTIONS",
);
responseHeaders.set(
"Access-Control-Allow-Headers",
"Content-Type, Authorization",
);
if (request.method === "OPTIONS") {
return new Response(null, {
status: 204,
headers: responseHeaders,
});
}
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: responseHeaders,
});
};

View File

@ -1,6 +1,6 @@
{ {
"name": "frontend", "name": "frontend",
"version": "1.3.12", "version": "1.4.0",
"private": true, "private": true,
"homepage": "https://github.com/perfect-panel/frontend", "homepage": "https://github.com/perfect-panel/frontend",
"bugs": { "bugs": {