mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-03-02 00:30:25 +00:00
Add plugin config management to backend and frontend
Introduces a unified plugin configuration schema and API in the backend, with endpoints for getting and setting plugin config. Updates the frontend to support plugin config modals, including a UI for editing plugin settings. Also adds support for uninstalling plugins with optional data cleanup, and updates dependencies to use napcat-types@0.0.5.
This commit is contained in:
@@ -2,11 +2,13 @@ import { Button } from '@heroui/button';
|
||||
import { useEffect, useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { IoMdRefresh } from 'react-icons/io';
|
||||
import { useDisclosure } from '@heroui/modal';
|
||||
|
||||
import PageLoading from '@/components/page_loading';
|
||||
import PluginDisplayCard from '@/components/display_card/plugin_card';
|
||||
import PluginManager, { PluginItem } from '@/controllers/plugin_manager';
|
||||
import useDialog from '@/hooks/use-dialog';
|
||||
import PluginConfigModal from '@/pages/dashboard/plugin_config_modal';
|
||||
|
||||
export default function PluginPage () {
|
||||
const [plugins, setPlugins] = useState<PluginItem[]>([]);
|
||||
@@ -14,16 +16,20 @@ export default function PluginPage () {
|
||||
const [pluginManagerNotFound, setPluginManagerNotFound] = useState(false);
|
||||
const dialog = useDialog();
|
||||
|
||||
const { isOpen, onOpen, onOpenChange } = useDisclosure();
|
||||
const [currentPluginName, setCurrentPluginName] = useState<string>('');
|
||||
|
||||
const loadPlugins = async () => {
|
||||
setLoading(true);
|
||||
setPluginManagerNotFound(false);
|
||||
try {
|
||||
const result = await PluginManager.getPluginList();
|
||||
if (result.pluginManagerNotFound) {
|
||||
const listResult = await PluginManager.getPluginList();
|
||||
|
||||
if (listResult.pluginManagerNotFound) {
|
||||
setPluginManagerNotFound(true);
|
||||
setPlugins([]);
|
||||
} else {
|
||||
setPlugins(result.plugins);
|
||||
setPlugins(listResult.plugins);
|
||||
}
|
||||
} catch (e: any) {
|
||||
toast.error(e.message);
|
||||
@@ -64,11 +70,31 @@ export default function PluginPage () {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
dialog.confirm({
|
||||
title: '卸载插件',
|
||||
content: `确定要卸载插件「${plugin.name}」吗? 此操作不可恢复。`,
|
||||
content: (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p>确定要卸载插件「{plugin.name}」吗? 此操作不可恢复。</p>
|
||||
<p className="text-small text-default-500">如果插件创建了数据文件,是否一并删除?</p>
|
||||
</div>
|
||||
),
|
||||
// This 'dialog' utility might not support returning a value from UI interacting.
|
||||
// We might need to implement a custom confirmation flow if we want a checkbox.
|
||||
// Alternatively, use two buttons? "Uninstall & Clean", "Uninstall Only"?
|
||||
// Standard dialog usually has Confirm/Cancel.
|
||||
// Let's stick to a simpler "Uninstall" and then maybe a second prompt? Or just clean data?
|
||||
// User requested: "Uninstall prompts whether to clean data".
|
||||
// Let's use `window.confirm` for the second step or assume `dialog.confirm` is flexible enough?
|
||||
// I will implement a two-step confirmation or try to modify the dialog hook if visible (not visible here).
|
||||
// Let's use a standard `window.confirm` for the data cleanup question if the custom dialog doesn't support complex return.
|
||||
// Better: Inside onConfirm, ask again?
|
||||
onConfirm: async () => {
|
||||
// Ask for data cleanup
|
||||
// Since we are in an async callback, we can use another dialog or confirm.
|
||||
// Native confirm is ugly but works reliably for logic:
|
||||
const cleanData = window.confirm(`是否同时清理插件「${plugin.name}」的数据文件?\n点击“确定”清理数据,点击“取消”仅卸载插件。`);
|
||||
|
||||
const loadingToast = toast.loading('卸载中...');
|
||||
try {
|
||||
await PluginManager.uninstallPlugin(plugin.name, plugin.filename);
|
||||
await PluginManager.uninstallPlugin(plugin.name, plugin.filename, cleanData);
|
||||
toast.success('卸载成功', { id: loadingToast });
|
||||
loadPlugins();
|
||||
resolve();
|
||||
@@ -84,11 +110,22 @@ export default function PluginPage () {
|
||||
});
|
||||
};
|
||||
|
||||
const handleConfig = (plugin: PluginItem) => {
|
||||
setCurrentPluginName(plugin.name); // Use Loaded Name for config lookup
|
||||
onOpen();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<title>插件管理 - NapCat WebUI</title>
|
||||
<div className='p-2 md:p-4 relative'>
|
||||
<PageLoading loading={loading} />
|
||||
<PluginConfigModal
|
||||
isOpen={isOpen}
|
||||
onOpenChange={onOpenChange}
|
||||
pluginName={currentPluginName}
|
||||
/>
|
||||
|
||||
<div className='flex mb-6 items-center gap-4'>
|
||||
<h1 className="text-2xl font-bold">插件管理</h1>
|
||||
<Button
|
||||
@@ -122,6 +159,14 @@ export default function PluginPage () {
|
||||
onReload={() => handleReload(plugin.name)}
|
||||
onToggleStatus={() => handleToggle(plugin)}
|
||||
onUninstall={() => handleUninstall(plugin)}
|
||||
onConfig={() => {
|
||||
if (plugin.hasConfig) {
|
||||
handleConfig(plugin);
|
||||
} else {
|
||||
toast.error('此插件没有配置哦');
|
||||
}
|
||||
}}
|
||||
hasConfig={true}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user