merge: 同步 origin/main (v1.4.0) 到定制版本
Some checks failed
Build and Release / Build (push) Has been cancelled
Some checks failed
Build and Release / Build (push) Has been cancelled
This commit is contained in:
parent
6b92979c7c
commit
830430645c
24
CHANGELOG.md
24
CHANGELOG.md
@ -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 / 问题修复
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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,
|
||||||
id?: string;
|
onChange,
|
||||||
value?: null | string;
|
ref,
|
||||||
onChange: (value?: string) => void;
|
}: {
|
||||||
}
|
id?: string;
|
||||||
>(function CloudFlareTurnstile({ id, value, onChange }, ref) {
|
value?: null | string;
|
||||||
|
onChange: (value?: string) => void;
|
||||||
|
ref?: RefObject<TurnstileRef | null>;
|
||||||
|
}) {
|
||||||
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;
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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({
|
||||||
|
|||||||
@ -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]
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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,
|
||||||
id?: string;
|
onChange,
|
||||||
value?: null | string;
|
ref,
|
||||||
onChange: (value?: string) => void;
|
}: {
|
||||||
}
|
id?: string;
|
||||||
>(function CloudFlareTurnstile({ id, value, onChange }, ref) {
|
value?: null | string;
|
||||||
|
onChange: (value?: string) => void;
|
||||||
|
ref?: RefObject<TurnstileRef | null>;
|
||||||
|
}) {
|
||||||
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
5
bunfig.toml
Normal 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
63
functions/v1/[[path]].ts
Normal 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,
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -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": {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user