diff --git a/src/components/layout/MainLayout.tsx b/src/components/layout/MainLayout.tsx
index a3e114c..bb002d8 100644
--- a/src/components/layout/MainLayout.tsx
+++ b/src/components/layout/MainLayout.tsx
@@ -300,6 +300,7 @@ export function MainLayout() {
const logout = useAuthStore((state) => state.logout);
const connectionStatus = useAuthStore((state) => state.connectionStatus);
const apiBase = useAuthStore((state) => state.apiBase);
+ const supportsPlugin = useAuthStore((state) => state.supportsPlugin);
const fetchConfig = useConfigStore((state) => state.fetchConfig);
const clearCache = useConfigStore((state) => state.clearCache);
@@ -430,7 +431,7 @@ export function MainLayout() {
}, [fetchConfig]);
const loadPluginResources = useCallback(async () => {
- if (connectionStatus !== 'connected') {
+ if (connectionStatus !== 'connected' || !supportsPlugin) {
setPluginResources([]);
return;
}
@@ -441,7 +442,7 @@ export function MainLayout() {
} catch {
setPluginResources([]);
}
- }, [connectionStatus]);
+ }, [connectionStatus, supportsPlugin]);
useEffect(() => {
const timer = window.setTimeout(() => {
@@ -468,39 +469,39 @@ export function MainLayout() {
return groups;
}, []);
- const pluginPageNavItems: SidebarNavItem[] = pluginResourceGroups.flatMap(
- (group): SidebarNavItem[] => {
- if (group.entries.length === 1) {
- const resource = group.entries[0];
- const pluginLogo = resolvePluginAssetURL(resource.pluginLogo, apiBase);
+ const pluginPageNavItems: SidebarNavItem[] = supportsPlugin
+ ? pluginResourceGroups.flatMap((group): SidebarNavItem[] => {
+ if (group.entries.length === 1) {
+ const resource = group.entries[0];
+ const pluginLogo = resolvePluginAssetURL(resource.pluginLogo, apiBase);
+ return [
+ {
+ path: resource.route,
+ label: resource.label,
+ meta: resource.description,
+ icon: ,
+ },
+ ];
+ }
+
+ const pluginLogo = resolvePluginAssetURL(group.entries[0]?.pluginLogo ?? '', apiBase);
return [
{
- path: resource.route,
- label: resource.label,
- meta: resource.description,
+ kind: 'drawer',
+ id: `plugin-pages-${group.pluginID}`,
+ label: group.pluginTitle,
+ meta: t('plugin_resource.page_count', { count: group.entries.length }),
icon: ,
+ children: group.entries.map((resource) => ({
+ path: resource.route,
+ label: resource.label,
+ meta: resource.description,
+ icon: ,
+ })),
},
];
- }
-
- const pluginLogo = resolvePluginAssetURL(group.entries[0]?.pluginLogo ?? '', apiBase);
- return [
- {
- kind: 'drawer',
- id: `plugin-pages-${group.pluginID}`,
- label: group.pluginTitle,
- meta: t('plugin_resource.page_count', { count: group.entries.length }),
- icon: ,
- children: group.entries.map((resource) => ({
- path: resource.route,
- label: resource.label,
- meta: resource.description,
- icon: ,
- })),
- },
- ];
- }
- );
+ })
+ : [];
const navGroups: SidebarNavGroup[] = [
{
@@ -567,18 +568,22 @@ export function MainLayout() {
metaKey: 'nav_meta.config_management',
icon: sidebarIcons.config,
},
- {
- path: '/plugins',
- labelKey: 'nav.plugins',
- metaKey: 'nav_meta.plugins',
- icon: sidebarIcons.plugins,
- },
- {
- path: '/plugin-store',
- labelKey: 'nav.plugin_store',
- metaKey: 'nav_meta.plugin_store',
- icon: sidebarIcons.pluginStore,
- },
+ ...(supportsPlugin
+ ? [
+ {
+ path: '/plugins',
+ labelKey: 'nav.plugins',
+ metaKey: 'nav_meta.plugins',
+ icon: sidebarIcons.plugins,
+ },
+ {
+ path: '/plugin-store',
+ labelKey: 'nav.plugin_store',
+ metaKey: 'nav_meta.plugin_store',
+ icon: sidebarIcons.pluginStore,
+ },
+ ]
+ : []),
{
path: '/system',
labelKey: 'nav.system_info',
diff --git a/src/router/MainRoutes.tsx b/src/router/MainRoutes.tsx
index f3c6172..629218d 100644
--- a/src/router/MainRoutes.tsx
+++ b/src/router/MainRoutes.tsx
@@ -12,8 +12,9 @@ import { PluginStorePage } from '@/features/plugins/PluginStorePage';
import { ConfigPage } from '@/pages/ConfigPage';
import { LogsPage } from '@/pages/LogsPage';
import { SystemPage } from '@/pages/SystemPage';
+import { useAuthStore } from '@/stores';
-const mainRoutes = [
+const createMainRoutes = (supportsPlugin: boolean) => [
{ path: '/', element: },
{ path: '/dashboard', element: },
{ path: '/settings', element: },
@@ -25,10 +26,18 @@ const mainRoutes = [
{ path: '/auth-files/oauth-model-alias', element: },
{ path: '/oauth', element: },
{ path: '/quota', element: },
- { path: '/plugin-pages/:pluginId/:menuIndex', element: },
- { path: '/plugins', element: },
- { path: '/plugin-store', element: },
- { path: '/plugins/*', element: },
+ ...(supportsPlugin
+ ? [
+ { path: '/plugin-pages/:pluginId/:menuIndex', element: },
+ { path: '/plugins', element: },
+ { path: '/plugin-store', element: },
+ { path: '/plugins/*', element: },
+ ]
+ : [
+ { path: '/plugin-pages/*', element: },
+ { path: '/plugins/*', element: },
+ { path: '/plugin-store', element: },
+ ]),
{ path: '/config', element: },
{ path: '/logs', element: },
{ path: '/system', element: },
@@ -36,5 +45,6 @@ const mainRoutes = [
];
export function MainRoutes({ location }: { location?: Location }) {
- return useRoutes(mainRoutes, location);
+ const supportsPlugin = useAuthStore((state) => state.supportsPlugin);
+ return useRoutes(createMainRoutes(supportsPlugin), location);
}
diff --git a/src/services/api/client.ts b/src/services/api/client.ts
index af3b51c..5d907ba 100644
--- a/src/services/api/client.ts
+++ b/src/services/api/client.ts
@@ -8,6 +8,7 @@ import type { ApiClientConfig, ApiError } from '@/types';
import {
BUILD_DATE_HEADER_KEYS,
CPA_BUILD_DATE_HEADER_KEYS,
+ CPA_SUPPORT_PLUGIN_HEADER_KEYS,
CPA_VERSION_HEADER_KEYS,
HOME_BUILD_DATE_HEADER_KEYS,
HOME_VERSION_HEADER_KEYS,
@@ -87,6 +88,19 @@ class ApiClient {
return null;
}
+ private readBooleanHeader(
+ headers: Record | undefined,
+ keys: string[]
+ ): boolean | null {
+ const value = this.readHeader(headers, keys);
+ if (value === null) return null;
+
+ const normalized = value.trim().toLowerCase();
+ if (['1', 'true', 'yes', 'on'].includes(normalized)) return true;
+ if (['0', 'false', 'no', 'off'].includes(normalized)) return false;
+ return null;
+ }
+
/**
* 设置请求/响应拦截器
*/
@@ -122,6 +136,7 @@ class ApiClient {
const version = homeVersion || cpaVersion || this.readHeader(headers, VERSION_HEADER_KEYS);
const buildDate =
homeBuildDate || cpaBuildDate || this.readHeader(headers, BUILD_DATE_HEADER_KEYS);
+ const supportsPlugin = this.readBooleanHeader(headers, CPA_SUPPORT_PLUGIN_HEADER_KEYS);
const runtimeKind: ServerRuntimeKind | null =
homeVersion || homeBuildDate ? 'home' : cpaVersion || cpaBuildDate ? 'cpa' : null;
@@ -133,6 +148,13 @@ class ApiClient {
})
);
}
+ if (supportsPlugin !== null) {
+ window.dispatchEvent(
+ new CustomEvent('server-plugin-support-update', {
+ detail: { supportsPlugin }
+ })
+ );
+ }
return response;
},
diff --git a/src/stores/useAuthStore.ts b/src/stores/useAuthStore.ts
index 1419a86..96fa315 100644
--- a/src/stores/useAuthStore.ts
+++ b/src/stores/useAuthStore.ts
@@ -29,6 +29,7 @@ interface AuthStoreState extends AuthState {
runtimeKind?: ServerRuntimeKind | null
) => void;
updateServerRuntimeKind: (runtimeKind: ServerRuntimeKind) => void;
+ updateServerPluginSupport: (supportsPlugin: boolean) => void;
updateConnectionStatus: (status: ConnectionStatus, error?: string | null) => void;
}
@@ -54,6 +55,7 @@ export const useAuthStore = create()(
serverVersion: null,
serverBuildDate: null,
serverRuntimeKind: 'unknown',
+ supportsPlugin: false,
connectionStatus: 'disconnected',
connectionError: null,
@@ -113,7 +115,8 @@ export const useAuthStore = create()(
connectionStatus: 'connecting',
serverVersion: null,
serverBuildDate: null,
- serverRuntimeKind: 'unknown'
+ serverRuntimeKind: 'unknown',
+ supportsPlugin: false
});
useModelsStore.getState().clearCache();
@@ -169,6 +172,7 @@ export const useAuthStore = create()(
serverVersion: null,
serverBuildDate: null,
serverRuntimeKind: 'unknown',
+ supportsPlugin: false,
connectionStatus: 'disconnected',
connectionError: null
});
@@ -186,6 +190,7 @@ export const useAuthStore = create()(
try {
// 重新配置客户端
apiClient.setConfig({ apiBase, managementKey });
+ set({ supportsPlugin: false });
// 验证连接
await useConfigStore.getState().fetchConfig();
@@ -201,7 +206,8 @@ export const useAuthStore = create()(
} catch {
set({
isAuthenticated: false,
- connectionStatus: 'error'
+ connectionStatus: 'error',
+ supportsPlugin: false
});
return false;
}
@@ -220,6 +226,10 @@ export const useAuthStore = create()(
set({ serverRuntimeKind: runtimeKind });
},
+ updateServerPluginSupport: (supportsPlugin) => {
+ set({ supportsPlugin });
+ },
+
// 更新连接状态
updateConnectionStatus: (status, error = null) => {
set({
@@ -273,4 +283,11 @@ if (typeof window !== 'undefined') {
.updateServerVersion(detail.version || null, detail.buildDate || null, runtimeKind);
}) as EventListener
);
+
+ window.addEventListener(
+ 'server-plugin-support-update',
+ ((e: CustomEvent) => {
+ useAuthStore.getState().updateServerPluginSupport(e.detail?.supportsPlugin === true);
+ }) as EventListener
+ );
}
diff --git a/src/types/auth.ts b/src/types/auth.ts
index d967c0a..a382b44 100644
--- a/src/types/auth.ts
+++ b/src/types/auth.ts
@@ -19,6 +19,7 @@ export interface AuthState {
serverVersion: string | null;
serverBuildDate: string | null;
serverRuntimeKind: ServerRuntimeKind;
+ supportsPlugin: boolean;
}
// 连接状态
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 23830f8..f19632a 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -18,6 +18,7 @@ export const MANAGEMENT_API_PREFIX = '/v0/management';
export const REQUEST_TIMEOUT_MS = 30 * 1000;
export const CPA_VERSION_HEADER_KEYS = ['x-cpa-version'];
export const CPA_BUILD_DATE_HEADER_KEYS = ['x-cpa-build-date'];
+export const CPA_SUPPORT_PLUGIN_HEADER_KEYS = ['x-cpa-support-plugin'];
export const HOME_VERSION_HEADER_KEYS = ['x-cpa-home-version'];
export const HOME_BUILD_DATE_HEADER_KEYS = ['x-cpa-home-build-date'];
export const VERSION_HEADER_KEYS = [