From 1741c6c95707e6b2b834576024ccddef28a217b7 Mon Sep 17 00:00:00 2001 From: gaoxizhi Date: Wed, 29 Jul 2026 00:17:41 +0800 Subject: [PATCH] [dev] support reverse proxy subpaths --- ui/index.html | 2 +- ui/src/domain/provider.ts | 4 +++- ui/src/pages/ConsoleLayout.tsx | 3 ++- ui/src/pages/login/Login.tsx | 3 ++- ui/src/pages/settings/SettingsAppearance.tsx | 3 ++- ui/src/pages/workflows/WorkflowNew.tsx | 5 +++-- ui/src/repository/_pocketbase.ts | 4 +++- ui/src/utils/url.ts | 8 ++++++++ ui/vite.config.ts | 2 ++ 9 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 ui/src/utils/url.ts diff --git a/ui/index.html b/ui/index.html index 41f31472f..f1e471a7e 100644 --- a/ui/index.html +++ b/ui/index.html @@ -2,7 +2,7 @@ - + Certimate - Your Trusted Partner in SSL Automation diff --git a/ui/src/domain/provider.ts b/ui/src/domain/provider.ts index f3b247028..e52827d11 100644 --- a/ui/src/domain/provider.ts +++ b/ui/src/domain/provider.ts @@ -1,3 +1,5 @@ +import { resolveAppPath } from "@/utils/url"; + interface BaseProvider

{ type: P; name: string; @@ -296,7 +298,7 @@ export const accessProvidersMap: Map { const navigate = useNavigate(); @@ -231,7 +232,7 @@ const SiderMenu = memo(({ collapsed, onSelect }: { collapsed?: boolean; onSelect <>

- + Certimate diff --git a/ui/src/pages/login/Login.tsx b/ui/src/pages/login/Login.tsx index def72c382..dc6f42b57 100644 --- a/ui/src/pages/login/Login.tsx +++ b/ui/src/pages/login/Login.tsx @@ -14,6 +14,7 @@ import { useAntdForm, useBrowserTheme } from "@/hooks"; import { authWithPassword } from "@/repository/admin"; import { unwrapErrMsg } from "@/utils/error"; +import { resolveAppPath } from "@/utils/url"; const Login = () => { const navigage = useNavigate(); @@ -74,7 +75,7 @@ const Login = () => {
- +
diff --git a/ui/src/pages/settings/SettingsAppearance.tsx b/ui/src/pages/settings/SettingsAppearance.tsx index eb1176232..5a00b283b 100644 --- a/ui/src/pages/settings/SettingsAppearance.tsx +++ b/ui/src/pages/settings/SettingsAppearance.tsx @@ -6,6 +6,7 @@ import { produce } from "immer"; import { useAppLocaleMenuItems } from "@/components/AppLocale"; import { useAppThemeMenuItems } from "@/components/AppTheme"; import { useAppSettings, useBrowserTheme } from "@/hooks"; +import { resolveAppPath } from "@/utils/url"; const SettingsAppearance = () => { const { t } = useTranslation(); @@ -58,7 +59,7 @@ const SettingsAppearanceTheme = ({ className, style }: { className?: string; sty {themeItems.map((item) => (
- +
{item.label}
diff --git a/ui/src/pages/workflows/WorkflowNew.tsx b/ui/src/pages/workflows/WorkflowNew.tsx index de9d4274b..dd261ff0f 100644 --- a/ui/src/pages/workflows/WorkflowNew.tsx +++ b/ui/src/pages/workflows/WorkflowNew.tsx @@ -16,6 +16,7 @@ import { } from "@/domain/workflow"; import { save as saveWorkflow } from "@/repository/workflow"; import { unwrapErrMsg } from "@/utils/error"; +import { resolveAppPath } from "@/utils/url"; const TEMPLATE_KEY_BLANK = "blank" as const; const TEMPLATE_KEY_STANDARD = "standard" as const; @@ -34,13 +35,13 @@ const WorkflowNew = () => { key: TEMPLATE_KEY_STANDARD, name: t("workflow.new.templates.template.standard.title"), description: t("workflow.new.templates.template.standard.description"), - image: "/imgs/workflow/tpl-standard.png", + image: resolveAppPath("/imgs/workflow/tpl-standard.png"), }, { key: TEMPLATE_KEY_CERTTEST, name: t("workflow.new.templates.template.certtest.title"), description: t("workflow.new.templates.template.certtest.description"), - image: "/imgs/workflow/tpl-certtest.png", + image: resolveAppPath("/imgs/workflow/tpl-certtest.png"), }, ]; const [templateSelectKey, setTemplateSelectKey] = useState(); diff --git a/ui/src/repository/_pocketbase.ts b/ui/src/repository/_pocketbase.ts index e4a2f7b4e..6b4a46f98 100644 --- a/ui/src/repository/_pocketbase.ts +++ b/ui/src/repository/_pocketbase.ts @@ -1,9 +1,11 @@ import PocketBase from "pocketbase"; +import { APP_BASE_PATH } from "@/utils/url"; + let pb: PocketBase; export const getPocketBase = () => { if (pb) return pb; - pb = new PocketBase("/"); + pb = new PocketBase(APP_BASE_PATH); pb.afterSend = (res, data) => { if ((res.status === 401 || res.status === 403) && pb.authStore?.isValid) { pb.authStore.clear(); diff --git a/ui/src/utils/url.ts b/ui/src/utils/url.ts new file mode 100644 index 000000000..69cc4c6a2 --- /dev/null +++ b/ui/src/utils/url.ts @@ -0,0 +1,8 @@ +const appBaseUrl = new URL(import.meta.env.BASE_URL, document.baseURI); + +// PocketBase 会把自身的 /api 路径拼接到这里,因此基础路径必须以斜杠结尾。 +export const APP_BASE_PATH = appBaseUrl.pathname; + +export const resolveAppPath = (path: string) => { + return new URL(path.replace(/^\/+/, ""), appBaseUrl).pathname; +}; diff --git a/ui/vite.config.ts b/ui/vite.config.ts index bec8c3fac..6acf6cbf1 100644 --- a/ui/vite.config.ts +++ b/ui/vite.config.ts @@ -52,6 +52,8 @@ export default defineConfig(({ command }) => { } return { + // 使用相对基础路径,让同一份构建产物可以部署在根路径或任意反向代理子路径下。 + base: "./", define: { __APP_VERSION__: JSON.stringify(appVersion), },