mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-04 06:31:13 +00:00
Expose plugin pages at /plugin/:id/page/:path
Add a public route to serve plugin extension pages without auth and update related pieces accordingly. Backend: register GET /plugin/:pluginId/page/:pagePath to locate the plugin router, validate page and HTML file existence, and send the file (returns appropriate 4xx/5xx errors). Frontend: switch iframe and new-window URLs to the new unauthenticated route (remove webui_token usage). Builtin plugin: clarify page registration comment and add a log line for the extension page URL. Minor formatting whitespace tweaks in plugin manager type annotations.
This commit is contained in:
parent
d9297c1e10
commit
a5769b6a62
@ -160,7 +160,7 @@ const plugin_init: PluginModule['plugin_init'] = async (ctx) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 注册扩展页面
|
// 注册扩展页面(无需鉴权,可通过 /plugin/{pluginId}/page/dashboard 访问)
|
||||||
ctx.router.page({
|
ctx.router.page({
|
||||||
path: 'dashboard',
|
path: 'dashboard',
|
||||||
title: '插件仪表盘',
|
title: '插件仪表盘',
|
||||||
@ -171,6 +171,7 @@ const plugin_init: PluginModule['plugin_init'] = async (ctx) => {
|
|||||||
|
|
||||||
logger.info('WebUI 路由已注册:');
|
logger.info('WebUI 路由已注册:');
|
||||||
logger.info(' - API 路由: /api/Plugin/ext/' + ctx.pluginName + '/');
|
logger.info(' - API 路由: /api/Plugin/ext/' + ctx.pluginName + '/');
|
||||||
|
logger.info(' - 扩展页面: /plugin/' + ctx.pluginName + '/page/dashboard');
|
||||||
logger.info(' - 静态资源: /plugin/' + ctx.pluginName + '/files/static/');
|
logger.info(' - 静态资源: /plugin/' + ctx.pluginName + '/files/static/');
|
||||||
logger.info(' - 内存资源: /plugin/' + ctx.pluginName + '/mem/dynamic/');
|
logger.info(' - 内存资源: /plugin/' + ctx.pluginName + '/mem/dynamic/');
|
||||||
};
|
};
|
||||||
|
|||||||
@ -332,6 +332,42 @@ export async function InitWebUi (logger: ILogWrapper, pathWrapper: NapCatPathWra
|
|||||||
return res.status(404).json({ code: -1, message: 'Memory file not found' });
|
return res.status(404).json({ code: -1, message: 'Memory file not found' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 插件页面路由(不需要鉴权)
|
||||||
|
// 路径格式: /plugin/:pluginId/page/:pagePath
|
||||||
|
app.get('/plugin/:pluginId/page/:pagePath', (req, res) => {
|
||||||
|
const { pluginId, pagePath } = req.params;
|
||||||
|
if (!pluginId) return res.status(400).json({ code: -1, message: 'Plugin ID is required' });
|
||||||
|
|
||||||
|
const ob11 = WebUiDataRuntime.getOneBotContext() as NapCatOneBot11Adapter | null;
|
||||||
|
if (!ob11) return res.status(503).json({ code: -1, message: 'OneBot context not available' });
|
||||||
|
|
||||||
|
const pluginManager = ob11.networkManager.findSomeAdapter('plugin_manager') as OB11PluginMangerAdapter | undefined;
|
||||||
|
if (!pluginManager) return res.status(503).json({ code: -1, message: 'Plugin manager not available' });
|
||||||
|
|
||||||
|
const routerRegistry = pluginManager.getPluginRouter(pluginId);
|
||||||
|
if (!routerRegistry || !routerRegistry.hasPages()) {
|
||||||
|
return res.status(404).json({ code: -1, message: `Plugin '${pluginId}' has no registered pages` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const pages = routerRegistry.getPages();
|
||||||
|
const page = pages.find(p => p.path === '/' + pagePath || p.path === pagePath);
|
||||||
|
if (!page) {
|
||||||
|
return res.status(404).json({ code: -1, message: `Page '${pagePath}' not found in plugin '${pluginId}'` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const pluginPath = routerRegistry.getPluginPath();
|
||||||
|
if (!pluginPath) {
|
||||||
|
return res.status(500).json({ code: -1, message: 'Plugin path not available' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const htmlFilePath = join(pluginPath, page.htmlFile);
|
||||||
|
if (!existsSync(htmlFilePath)) {
|
||||||
|
return res.status(404).json({ code: -1, message: `HTML file not found: ${page.htmlFile}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.sendFile(htmlFilePath);
|
||||||
|
});
|
||||||
|
|
||||||
// 插件文件系统静态资源路由(不需要鉴权)
|
// 插件文件系统静态资源路由(不需要鉴权)
|
||||||
// 路径格式: /plugin/:pluginId/files/*
|
// 路径格式: /plugin/:pluginId/files/*
|
||||||
app.use('/plugin/:pluginId/files', (req, res, next) => {
|
app.use('/plugin/:pluginId/files', (req, res, next) => {
|
||||||
|
|||||||
@ -63,13 +63,12 @@ export default function ExtensionPage () {
|
|||||||
}, [extensionPages]);
|
}, [extensionPages]);
|
||||||
|
|
||||||
// 获取当前选中页面的 iframe URL
|
// 获取当前选中页面的 iframe URL
|
||||||
|
// 新路由格式不需要鉴权: /plugin/:pluginId/page/:pagePath
|
||||||
const currentPageUrl = useMemo(() => {
|
const currentPageUrl = useMemo(() => {
|
||||||
if (!selectedTab) return '';
|
if (!selectedTab) return '';
|
||||||
const [pluginId, ...pathParts] = selectedTab.split(':');
|
const [pluginId, ...pathParts] = selectedTab.split(':');
|
||||||
const path = pathParts.join(':').replace(/^\//, '');
|
const path = pathParts.join(':').replace(/^\//, '');
|
||||||
// 获取认证 token
|
return `/plugin/${pluginId}/page/${path}`;
|
||||||
const token = localStorage.getItem('token') || '';
|
|
||||||
return `/api/Plugin/page/${pluginId}/${path}?webui_token=${token}`;
|
|
||||||
}, [selectedTab]);
|
}, [selectedTab]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -86,11 +85,10 @@ export default function ExtensionPage () {
|
|||||||
setIframeLoading(false);
|
setIframeLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 在新窗口打开页面
|
// 在新窗口打开页面(新路由不需要鉴权)
|
||||||
const openInNewWindow = (pluginId: string, path: string) => {
|
const openInNewWindow = (pluginId: string, path: string) => {
|
||||||
const cleanPath = path.replace(/^\//, '');
|
const cleanPath = path.replace(/^\//, '');
|
||||||
const token = localStorage.getItem('token') || '';
|
const url = `/plugin/${pluginId}/page/${cleanPath}`;
|
||||||
const url = `/api/Plugin/page/${pluginId}/${cleanPath}?webui_token=${token}`;
|
|
||||||
window.open(url, '_blank');
|
window.open(url, '_blank');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user