mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
refactor(plugin): add support for plugin feature detection and routing
This commit is contained in:
@@ -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: <PluginSidebarIcon src={pluginLogo} />,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
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: <PluginSidebarIcon src={pluginLogo} />,
|
||||
children: group.entries.map((resource) => ({
|
||||
path: resource.route,
|
||||
label: resource.label,
|
||||
meta: resource.description,
|
||||
icon: <span className="nav-sub-dot" aria-hidden="true" />,
|
||||
})),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
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: <PluginSidebarIcon src={pluginLogo} />,
|
||||
children: group.entries.map((resource) => ({
|
||||
path: resource.route,
|
||||
label: resource.label,
|
||||
meta: resource.description,
|
||||
icon: <span className="nav-sub-dot" aria-hidden="true" />,
|
||||
})),
|
||||
},
|
||||
];
|
||||
}
|
||||
);
|
||||
})
|
||||
: [];
|
||||
|
||||
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',
|
||||
|
||||
@@ -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: <DashboardPage /> },
|
||||
{ path: '/dashboard', element: <DashboardPage /> },
|
||||
{ path: '/settings', element: <Navigate to="/config" replace /> },
|
||||
@@ -25,10 +26,18 @@ const mainRoutes = [
|
||||
{ path: '/auth-files/oauth-model-alias', element: <AuthFilesOAuthModelAliasEditPage /> },
|
||||
{ path: '/oauth', element: <OAuthPage /> },
|
||||
{ path: '/quota', element: <QuotaPage /> },
|
||||
{ path: '/plugin-pages/:pluginId/:menuIndex', element: <PluginResourcePage /> },
|
||||
{ path: '/plugins', element: <PluginsPage /> },
|
||||
{ path: '/plugin-store', element: <PluginStorePage /> },
|
||||
{ path: '/plugins/*', element: <Navigate to="/plugins" replace /> },
|
||||
...(supportsPlugin
|
||||
? [
|
||||
{ path: '/plugin-pages/:pluginId/:menuIndex', element: <PluginResourcePage /> },
|
||||
{ path: '/plugins', element: <PluginsPage /> },
|
||||
{ path: '/plugin-store', element: <PluginStorePage /> },
|
||||
{ path: '/plugins/*', element: <Navigate to="/plugins" replace /> },
|
||||
]
|
||||
: [
|
||||
{ path: '/plugin-pages/*', element: <Navigate to="/" replace /> },
|
||||
{ path: '/plugins/*', element: <Navigate to="/" replace /> },
|
||||
{ path: '/plugin-store', element: <Navigate to="/" replace /> },
|
||||
]),
|
||||
{ path: '/config', element: <ConfigPage /> },
|
||||
{ path: '/logs', element: <LogsPage /> },
|
||||
{ path: '/system', element: <SystemPage /> },
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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<string, unknown> | 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;
|
||||
},
|
||||
|
||||
@@ -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<AuthStoreState>()(
|
||||
serverVersion: null,
|
||||
serverBuildDate: null,
|
||||
serverRuntimeKind: 'unknown',
|
||||
supportsPlugin: false,
|
||||
connectionStatus: 'disconnected',
|
||||
connectionError: null,
|
||||
|
||||
@@ -113,7 +115,8 @@ export const useAuthStore = create<AuthStoreState>()(
|
||||
connectionStatus: 'connecting',
|
||||
serverVersion: null,
|
||||
serverBuildDate: null,
|
||||
serverRuntimeKind: 'unknown'
|
||||
serverRuntimeKind: 'unknown',
|
||||
supportsPlugin: false
|
||||
});
|
||||
useModelsStore.getState().clearCache();
|
||||
|
||||
@@ -169,6 +172,7 @@ export const useAuthStore = create<AuthStoreState>()(
|
||||
serverVersion: null,
|
||||
serverBuildDate: null,
|
||||
serverRuntimeKind: 'unknown',
|
||||
supportsPlugin: false,
|
||||
connectionStatus: 'disconnected',
|
||||
connectionError: null
|
||||
});
|
||||
@@ -186,6 +190,7 @@ export const useAuthStore = create<AuthStoreState>()(
|
||||
try {
|
||||
// 重新配置客户端
|
||||
apiClient.setConfig({ apiBase, managementKey });
|
||||
set({ supportsPlugin: false });
|
||||
|
||||
// 验证连接
|
||||
await useConfigStore.getState().fetchConfig();
|
||||
@@ -201,7 +206,8 @@ export const useAuthStore = create<AuthStoreState>()(
|
||||
} catch {
|
||||
set({
|
||||
isAuthenticated: false,
|
||||
connectionStatus: 'error'
|
||||
connectionStatus: 'error',
|
||||
supportsPlugin: false
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@@ -220,6 +226,10 @@ export const useAuthStore = create<AuthStoreState>()(
|
||||
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
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ export interface AuthState {
|
||||
serverVersion: string | null;
|
||||
serverBuildDate: string | null;
|
||||
serverRuntimeKind: ServerRuntimeKind;
|
||||
supportsPlugin: boolean;
|
||||
}
|
||||
|
||||
// 连接状态
|
||||
|
||||
@@ -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 = [
|
||||
|
||||
Reference in New Issue
Block a user