mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-03-01 08:10:25 +00:00
Refactor bypass defaults and crash handling
Set bypass defaults to disabled and simplify loading: napcat.json default bypass flags changed to false and code now reads bypass options without merging a prior "all enabled" default. Removed the progressive bypass-disable logic and related environment variable usage, and added a log when Napi2NativeLoader enables bypasses. Web UI/backend adjustments: default NapCat config is now generated from the AJV schema; the bypass settings UI defaults to false, adds an o3HookMode toggle, and submits o3HookMode as 0/1. UX fixes: extension tabs made horizontally scrollable with fixed tab sizing, and plugin uninstall flow updated to a single confirmation dialog with an optional checkbox to remove plugin config. Overall changes aim to use safer defaults, simplify crash/restart behavior, and improve configuration and UI clarity.
This commit is contained in:
12
packages/napcat-core/external/napcat.json
vendored
12
packages/napcat-core/external/napcat.json
vendored
@@ -7,11 +7,11 @@
|
|||||||
"packetServer": "",
|
"packetServer": "",
|
||||||
"o3HookMode": 1,
|
"o3HookMode": 1,
|
||||||
"bypass": {
|
"bypass": {
|
||||||
"hook": true,
|
"hook": false,
|
||||||
"window": true,
|
"window": false,
|
||||||
"module": true,
|
"module": false,
|
||||||
"process": true,
|
"process": false,
|
||||||
"container": true,
|
"container": false,
|
||||||
"js": true
|
"js": false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,28 +47,23 @@ export async function NCoreInitFramework (
|
|||||||
const napi2nativeLoader = new Napi2NativeLoader({ logger }); // 初始化 Napi2NativeLoader 用于后续使用
|
const napi2nativeLoader = new Napi2NativeLoader({ logger }); // 初始化 Napi2NativeLoader 用于后续使用
|
||||||
//console.log('[NapCat] [Napi2NativeLoader]', napi2nativeLoader.nativeExports.enableAllBypasses?.());
|
//console.log('[NapCat] [Napi2NativeLoader]', napi2nativeLoader.nativeExports.enableAllBypasses?.());
|
||||||
if (process.env['NAPCAT_DISABLE_BYPASS'] !== '1') {
|
if (process.env['NAPCAT_DISABLE_BYPASS'] !== '1') {
|
||||||
// 读取 napcat.json 配置
|
let bypassOptions: BypassOptions = {};
|
||||||
let bypassOptions: BypassOptions = {
|
|
||||||
hook: false,
|
|
||||||
window: false,
|
|
||||||
module: false,
|
|
||||||
process: false,
|
|
||||||
container: false,
|
|
||||||
js: false,
|
|
||||||
};
|
|
||||||
try {
|
try {
|
||||||
const configFile = path.join(pathWrapper.configPath, 'napcat.json');
|
const configFile = path.join(pathWrapper.configPath, 'napcat.json');
|
||||||
if (fs.existsSync(configFile)) {
|
if (fs.existsSync(configFile)) {
|
||||||
const content = fs.readFileSync(configFile, 'utf-8');
|
const content = fs.readFileSync(configFile, 'utf-8');
|
||||||
const config = json5.parse(content);
|
const config = json5.parse(content);
|
||||||
if (config.bypass && typeof config.bypass === 'object') {
|
if (config.bypass && typeof config.bypass === 'object') {
|
||||||
bypassOptions = { ...bypassOptions, ...config.bypass };
|
bypassOptions = { ...config.bypass };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.logWarn('[NapCat] 读取 napcat.json bypass 配置失败,已全部禁用:', e);
|
logger.logWarn('[NapCat] 读取 napcat.json bypass 配置失败,已全部禁用:', e);
|
||||||
}
|
}
|
||||||
const bypassEnabled = napi2nativeLoader.nativeExports.enableAllBypasses?.(bypassOptions);
|
const bypassEnabled = napi2nativeLoader.nativeExports.enableAllBypasses?.(bypassOptions);
|
||||||
|
if (bypassEnabled) {
|
||||||
|
logger.log('[NapCat] Napi2NativeLoader: 已启用Bypass');
|
||||||
|
}
|
||||||
logger.log('[NapCat] Napi2NativeLoader: Framework模式Bypass配置:', bypassOptions);
|
logger.log('[NapCat] Napi2NativeLoader: Framework模式Bypass配置:', bypassOptions);
|
||||||
} else {
|
} else {
|
||||||
logger.log('[NapCat] Napi2NativeLoader: Bypass已通过环境变量禁用');
|
logger.log('[NapCat] Napi2NativeLoader: Bypass已通过环境变量禁用');
|
||||||
|
|||||||
@@ -49,47 +49,18 @@ import { connectToNamedPipe } from './pipe';
|
|||||||
* 3: 强制禁用全部 bypass
|
* 3: 强制禁用全部 bypass
|
||||||
*/
|
*/
|
||||||
function loadBypassConfig (configPath: string, logger: LogWrapper): BypassOptions {
|
function loadBypassConfig (configPath: string, logger: LogWrapper): BypassOptions {
|
||||||
const defaultOptions: BypassOptions = {
|
let options: BypassOptions = {};
|
||||||
hook: true,
|
|
||||||
window: true,
|
|
||||||
module: true,
|
|
||||||
process: true,
|
|
||||||
container: true,
|
|
||||||
js: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
let options = { ...defaultOptions };
|
|
||||||
try {
|
try {
|
||||||
const configFile = path.join(configPath, 'napcat.json');
|
const configFile = path.join(configPath, 'napcat.json');
|
||||||
if (fs.existsSync(configFile)) {
|
if (fs.existsSync(configFile)) {
|
||||||
const content = fs.readFileSync(configFile, 'utf-8');
|
const content = fs.readFileSync(configFile, 'utf-8');
|
||||||
const config = json5.parse(content);
|
const config = json5.parse(content);
|
||||||
if (config.bypass && typeof config.bypass === 'object') {
|
if (config.bypass && typeof config.bypass === 'object') {
|
||||||
options = { ...defaultOptions, ...config.bypass };
|
options = { ...config.bypass };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.logWarn('[NapCat] 读取 bypass 配置失败,使用默认值:', e);
|
logger.logWarn('[NapCat] 读取 bypass 配置失败:', e);
|
||||||
}
|
|
||||||
// 根据分步禁用级别覆盖配置
|
|
||||||
const disableLevel = parseInt(process.env['NAPCAT_BYPASS_DISABLE_LEVEL'] || '0', 10);
|
|
||||||
if (disableLevel > 0) {
|
|
||||||
const levelDescriptions = ['全部启用', '禁用 hook', '禁用 hook + module', '全部禁用 bypass'];
|
|
||||||
logger.logWarn(`[NapCat] 崩溃恢复:当前 bypass 禁用级别 ${disableLevel} (${levelDescriptions[disableLevel] ?? '未知'})`);
|
|
||||||
if (disableLevel >= 1) {
|
|
||||||
options.hook = false;
|
|
||||||
}
|
|
||||||
if (disableLevel >= 2) {
|
|
||||||
options.module = false;
|
|
||||||
}
|
|
||||||
if (disableLevel >= 3) {
|
|
||||||
options.hook = false;
|
|
||||||
options.window = false;
|
|
||||||
options.module = false;
|
|
||||||
options.process = false;
|
|
||||||
options.container = false;
|
|
||||||
options.js = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,15 +65,6 @@ const recentCrashTimestamps: number[] = [];
|
|||||||
const CRASH_TIME_WINDOW = 10000; // 10秒时间窗口
|
const CRASH_TIME_WINDOW = 10000; // 10秒时间窗口
|
||||||
const MAX_CRASHES_IN_WINDOW = 3; // 最大崩溃次数
|
const MAX_CRASHES_IN_WINDOW = 3; // 最大崩溃次数
|
||||||
|
|
||||||
// 分步禁用策略:记录当前禁用级别 (0-3)
|
|
||||||
// 0: 全部启用
|
|
||||||
// 1: 禁用 hook
|
|
||||||
// 2: 禁用 hook + module
|
|
||||||
// 3: 全部禁用
|
|
||||||
let bypassDisableLevel = 0;
|
|
||||||
|
|
||||||
// 是否已登录成功(登录后不再使用分步禁用策略)
|
|
||||||
let isLoggedIn = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取进程类型名称(用于日志)
|
* 获取进程类型名称(用于日志)
|
||||||
@@ -164,8 +155,6 @@ async function cleanupOrphanedProcesses (excludePids: number[]): Promise<void> {
|
|||||||
*/
|
*/
|
||||||
export async function restartWorker (secretKey?: string, port?: number): Promise<void> {
|
export async function restartWorker (secretKey?: string, port?: number): Promise<void> {
|
||||||
isRestarting = true;
|
isRestarting = true;
|
||||||
isLoggedIn = false;
|
|
||||||
bypassDisableLevel = 0;
|
|
||||||
|
|
||||||
if (!currentWorker) {
|
if (!currentWorker) {
|
||||||
logger.logWarn('[NapCat] [Process] 没有运行中的Worker进程');
|
logger.logWarn('[NapCat] [Process] 没有运行中的Worker进程');
|
||||||
@@ -258,7 +247,6 @@ async function startWorker (passQuickLogin: boolean = true, secretKey?: string,
|
|||||||
NAPCAT_WORKER_PROCESS: '1',
|
NAPCAT_WORKER_PROCESS: '1',
|
||||||
...(secretKey ? { NAPCAT_WEBUI_JWT_SECRET_KEY: secretKey } : {}),
|
...(secretKey ? { NAPCAT_WEBUI_JWT_SECRET_KEY: secretKey } : {}),
|
||||||
...(preferredPort ? { NAPCAT_WEBUI_PREFERRED_PORT: String(preferredPort) } : {}),
|
...(preferredPort ? { NAPCAT_WEBUI_PREFERRED_PORT: String(preferredPort) } : {}),
|
||||||
...(bypassDisableLevel > 0 ? { NAPCAT_BYPASS_DISABLE_LEVEL: String(bypassDisableLevel) } : {}),
|
|
||||||
},
|
},
|
||||||
stdio: isElectron ? 'pipe' : ['inherit', 'pipe', 'pipe', 'ipc'],
|
stdio: isElectron ? 'pipe' : ['inherit', 'pipe', 'pipe', 'ipc'],
|
||||||
});
|
});
|
||||||
@@ -289,7 +277,6 @@ async function startWorker (passQuickLogin: boolean = true, secretKey?: string,
|
|||||||
logger.logError(`[NapCat] [${processType}] 重启Worker进程失败:`, e);
|
logger.logError(`[NapCat] [${processType}] 重启Worker进程失败:`, e);
|
||||||
});
|
});
|
||||||
} else if (message.type === 'login-success') {
|
} else if (message.type === 'login-success') {
|
||||||
isLoggedIn = true;
|
|
||||||
logger.log(`[NapCat] [${processType}] Worker进程已登录成功,切换到正常重试策略`);
|
logger.log(`[NapCat] [${processType}] Worker进程已登录成功,切换到正常重试策略`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -313,33 +300,12 @@ async function startWorker (passQuickLogin: boolean = true, secretKey?: string,
|
|||||||
// 记录本次崩溃
|
// 记录本次崩溃
|
||||||
recentCrashTimestamps.push(now);
|
recentCrashTimestamps.push(now);
|
||||||
|
|
||||||
// 登录前:使用分步禁用策略
|
|
||||||
if (!isLoggedIn) {
|
|
||||||
// 每次崩溃提升禁用级别
|
|
||||||
bypassDisableLevel = Math.min(bypassDisableLevel + 1, 3);
|
|
||||||
|
|
||||||
const levelDescriptions = [
|
|
||||||
'全部启用',
|
|
||||||
'禁用 hook',
|
|
||||||
'禁用 hook + module',
|
|
||||||
'全部禁用 bypass'
|
|
||||||
];
|
|
||||||
|
|
||||||
if (bypassDisableLevel >= 3 && recentCrashTimestamps.length >= MAX_CRASHES_IN_WINDOW) {
|
|
||||||
logger.logError(`[NapCat] [${processType}] Worker进程在 ${CRASH_TIME_WINDOW / 1000} 秒内异常退出 ${MAX_CRASHES_IN_WINDOW} 次,已尝试全部禁用策略,主进程退出`);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.logWarn(`[NapCat] [${processType}] Worker进程意外退出 (${recentCrashTimestamps.length}/${MAX_CRASHES_IN_WINDOW}),切换到禁用级别 ${bypassDisableLevel}: ${levelDescriptions[bypassDisableLevel]},正在尝试重新拉起...`);
|
|
||||||
} else {
|
|
||||||
// 登录后:使用正常重试策略
|
|
||||||
if (recentCrashTimestamps.length >= MAX_CRASHES_IN_WINDOW) {
|
if (recentCrashTimestamps.length >= MAX_CRASHES_IN_WINDOW) {
|
||||||
logger.logError(`[NapCat] [${processType}] Worker进程在 ${CRASH_TIME_WINDOW / 1000} 秒内异常退出 ${MAX_CRASHES_IN_WINDOW} 次,主进程退出`);
|
logger.logError(`[NapCat] [${processType}] Worker进程在 ${CRASH_TIME_WINDOW / 1000} 秒内异常退出 ${MAX_CRASHES_IN_WINDOW} 次,主进程退出`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.logWarn(`[NapCat] [${processType}] Worker进程意外退出 (${recentCrashTimestamps.length}/${MAX_CRASHES_IN_WINDOW}),正在尝试重新拉起...`);
|
logger.logWarn(`[NapCat] [${processType}] Worker进程意外退出 (${recentCrashTimestamps.length}/${MAX_CRASHES_IN_WINDOW}),正在尝试重新拉起...`);
|
||||||
}
|
|
||||||
|
|
||||||
startWorker(true).catch(e => {
|
startWorker(true).catch(e => {
|
||||||
logger.logError(`[NapCat] [${processType}] 重新拉起Worker进程失败:`, e);
|
logger.logError(`[NapCat] [${processType}] 重新拉起Worker进程失败:`, e);
|
||||||
|
|||||||
@@ -5,24 +5,17 @@ import { webUiPathWrapper } from '@/napcat-webui-backend/index';
|
|||||||
import { sendError, sendSuccess } from '@/napcat-webui-backend/src/utils/response';
|
import { sendError, sendSuccess } from '@/napcat-webui-backend/src/utils/response';
|
||||||
import json5 from 'json5';
|
import json5 from 'json5';
|
||||||
|
|
||||||
// NapCat 配置默认值
|
import Ajv from 'ajv';
|
||||||
const defaultNapcatConfig = {
|
import { NapcatConfigSchema } from '@/napcat-core/helper/config';
|
||||||
fileLog: false,
|
|
||||||
consoleLog: true,
|
// 动态获取 NapCat 配置默认值
|
||||||
fileLogLevel: 'debug',
|
function getDefaultNapcatConfig (): Record<string, unknown> {
|
||||||
consoleLogLevel: 'info',
|
const ajv = new Ajv({ useDefaults: true, coerceTypes: true });
|
||||||
packetBackend: 'auto',
|
const validate = ajv.compile(NapcatConfigSchema);
|
||||||
packetServer: '',
|
const data = {};
|
||||||
o3HookMode: 1,
|
validate(data);
|
||||||
bypass: {
|
return data;
|
||||||
hook: true,
|
}
|
||||||
window: true,
|
|
||||||
module: true,
|
|
||||||
process: true,
|
|
||||||
container: true,
|
|
||||||
js: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取 napcat 配置文件路径
|
* 获取 napcat 配置文件路径
|
||||||
@@ -39,12 +32,12 @@ function readNapcatConfig (): Record<string, unknown> {
|
|||||||
try {
|
try {
|
||||||
if (existsSync(configPath)) {
|
if (existsSync(configPath)) {
|
||||||
const content = readFileSync(configPath, 'utf-8');
|
const content = readFileSync(configPath, 'utf-8');
|
||||||
return { ...defaultNapcatConfig, ...json5.parse(content) };
|
return { ...getDefaultNapcatConfig(), ...json5.parse(content) };
|
||||||
}
|
}
|
||||||
} catch (_e) {
|
} catch (_e) {
|
||||||
// 读取失败,使用默认值
|
// 读取失败,使用默认值
|
||||||
}
|
}
|
||||||
return { ...defaultNapcatConfig };
|
return { ...getDefaultNapcatConfig() };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,16 +15,9 @@ interface BypassFormData {
|
|||||||
process: boolean;
|
process: boolean;
|
||||||
container: boolean;
|
container: boolean;
|
||||||
js: boolean;
|
js: boolean;
|
||||||
|
o3HookMode: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultBypass: BypassFormData = {
|
|
||||||
hook: true,
|
|
||||||
window: true,
|
|
||||||
module: true,
|
|
||||||
process: true,
|
|
||||||
container: true,
|
|
||||||
js: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
const BypassConfigCard = () => {
|
const BypassConfigCard = () => {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
@@ -33,21 +26,20 @@ const BypassConfigCard = () => {
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
formState: { isSubmitting },
|
formState: { isSubmitting },
|
||||||
setValue,
|
setValue,
|
||||||
} = useForm<BypassFormData>({
|
} = useForm<BypassFormData>();
|
||||||
defaultValues: defaultBypass,
|
|
||||||
});
|
|
||||||
|
|
||||||
const loadConfig = async (showTip = false) => {
|
const loadConfig = async (showTip = false) => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const config = await QQManager.getNapCatConfig();
|
const config = await QQManager.getNapCatConfig();
|
||||||
const bypass = config.bypass ?? defaultBypass;
|
const bypass = config.bypass ?? {} as Partial<BypassOptions>;
|
||||||
setValue('hook', bypass.hook ?? true);
|
setValue('hook', bypass.hook ?? false);
|
||||||
setValue('window', bypass.window ?? true);
|
setValue('window', bypass.window ?? false);
|
||||||
setValue('module', bypass.module ?? true);
|
setValue('module', bypass.module ?? false);
|
||||||
setValue('process', bypass.process ?? true);
|
setValue('process', bypass.process ?? false);
|
||||||
setValue('container', bypass.container ?? true);
|
setValue('container', bypass.container ?? false);
|
||||||
setValue('js', bypass.js ?? true);
|
setValue('js', bypass.js ?? false);
|
||||||
|
setValue('o3HookMode', config.o3HookMode === 1);
|
||||||
if (showTip) toast.success('刷新成功');
|
if (showTip) toast.success('刷新成功');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const msg = (error as Error).message;
|
const msg = (error as Error).message;
|
||||||
@@ -59,7 +51,8 @@ const BypassConfigCard = () => {
|
|||||||
|
|
||||||
const onSubmit = handleSubmit(async (data) => {
|
const onSubmit = handleSubmit(async (data) => {
|
||||||
try {
|
try {
|
||||||
await QQManager.setNapCatConfig({ bypass: data });
|
const { o3HookMode, ...bypass } = data;
|
||||||
|
await QQManager.setNapCatConfig({ bypass, o3HookMode: o3HookMode ? 1 : 0 });
|
||||||
toast.success('保存成功,重启后生效');
|
toast.success('保存成功,重启后生效');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const msg = (error as Error).message;
|
const msg = (error as Error).message;
|
||||||
@@ -156,6 +149,17 @@ const BypassConfigCard = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name='o3HookMode'
|
||||||
|
render={({ field }) => (
|
||||||
|
<SwitchCard
|
||||||
|
{...field}
|
||||||
|
label='o3HookMode'
|
||||||
|
description='O3 Hook 模式'
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<SaveButtons
|
<SaveButtons
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
reset={onReset}
|
reset={onReset}
|
||||||
|
|||||||
@@ -115,13 +115,14 @@ export default function ExtensionPage () {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{extensionPages.length > 0 && (
|
{extensionPages.length > 0 && (
|
||||||
|
<div className='max-w-full overflow-x-auto overflow-y-hidden pb-1 -mb-1'>
|
||||||
<Tabs
|
<Tabs
|
||||||
aria-label='Extension Pages'
|
aria-label='Extension Pages'
|
||||||
className='max-w-full'
|
className='min-w-max'
|
||||||
selectedKey={selectedTab}
|
selectedKey={selectedTab}
|
||||||
onSelectionChange={(key) => setSelectedTab(key as string)}
|
onSelectionChange={(key) => setSelectedTab(key as string)}
|
||||||
classNames={{
|
classNames={{
|
||||||
tabList: 'bg-white/40 dark:bg-black/20 backdrop-blur-md',
|
tabList: 'bg-white/40 dark:bg-black/20 backdrop-blur-md flex-nowrap',
|
||||||
cursor: 'bg-white/80 dark:bg-white/10 backdrop-blur-md shadow-sm',
|
cursor: 'bg-white/80 dark:bg-white/10 backdrop-blur-md shadow-sm',
|
||||||
panel: 'hidden',
|
panel: 'hidden',
|
||||||
}}
|
}}
|
||||||
@@ -129,11 +130,12 @@ export default function ExtensionPage () {
|
|||||||
{tabs.map((tab) => (
|
{tabs.map((tab) => (
|
||||||
<Tab
|
<Tab
|
||||||
key={tab.key}
|
key={tab.key}
|
||||||
|
className='shrink-0'
|
||||||
title={
|
title={
|
||||||
<div className='flex items-center gap-2'>
|
<div className='flex items-center gap-2'>
|
||||||
{tab.icon && <span>{tab.icon}</span>}
|
{tab.icon && <span>{tab.icon}</span>}
|
||||||
<span
|
<span
|
||||||
className='cursor-pointer hover:underline truncate max-w-[6rem] md:max-w-none'
|
className='cursor-pointer hover:underline truncate max-w-[6rem] md:max-w-none shrink-0'
|
||||||
title={`插件:${tab.pluginName}\n点击在新窗口打开`}
|
title={`插件:${tab.pluginName}\n点击在新窗口打开`}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -142,12 +144,13 @@ export default function ExtensionPage () {
|
|||||||
>
|
>
|
||||||
{tab.title}
|
{tab.title}
|
||||||
</span>
|
</span>
|
||||||
<span className='text-xs text-default-400 hidden md:inline'>({tab.pluginName})</span>
|
<span className='text-xs text-default-400 hidden md:inline shrink-0'>({tab.pluginName})</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -61,43 +61,28 @@ export default function PluginPage () {
|
|||||||
|
|
||||||
const handleUninstall = async (plugin: PluginItem) => {
|
const handleUninstall = async (plugin: PluginItem) => {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
let cleanData = false;
|
||||||
dialog.confirm({
|
dialog.confirm({
|
||||||
title: '卸载插件',
|
title: '卸载插件',
|
||||||
content: (
|
content: (
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<p>确定要卸载插件「{plugin.name}」吗? 此操作不可恢复。</p>
|
<p className="text-base text-default-800">确定要卸载插件「<span className="font-semibold text-danger">{plugin.name}</span>」吗? 此操作不可恢复。</p>
|
||||||
<p className="text-small text-default-500">如果插件创建了配置文件,是否一并删除?</p>
|
<div className="mt-2 bg-default-100 dark:bg-default-50/10 p-3 rounded-lg flex flex-col gap-1">
|
||||||
</div>
|
<label className="flex items-center gap-2 cursor-pointer w-fit">
|
||||||
),
|
<input
|
||||||
onConfirm: async () => {
|
type="checkbox"
|
||||||
// Ask for data cleanup
|
onChange={(e) => { cleanData = e.target.checked; }}
|
||||||
dialog.confirm({
|
className="w-4 h-4 cursor-pointer accent-danger"
|
||||||
title: '删除配置',
|
/>
|
||||||
content: (
|
<span className="text-small font-medium text-default-700">同时删除其配置文件</span>
|
||||||
<div className="flex flex-col gap-2">
|
</label>
|
||||||
<p>是否同时清理插件「{plugin.name}」的配置文件?</p>
|
<p className="text-xs text-default-500 pl-6 break-all w-full">配置目录: config/plugins/{plugin.id}</p>
|
||||||
<div className="text-small text-default-500">
|
|
||||||
<p>配置目录: config/plugins/{plugin.id}</p>
|
|
||||||
<p>点击"确定"清理配置,点击"取消"仅卸载插件。</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
confirmText: '清理并卸载',
|
confirmText: '确定卸载',
|
||||||
cancelText: '仅卸载',
|
cancelText: '取消',
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
await performUninstall(true);
|
|
||||||
},
|
|
||||||
onCancel: async () => {
|
|
||||||
await performUninstall(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onCancel: () => {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const performUninstall = async (cleanData: boolean) => {
|
|
||||||
const loadingToast = toast.loading('卸载中...');
|
const loadingToast = toast.loading('卸载中...');
|
||||||
try {
|
try {
|
||||||
await PluginManager.uninstallPlugin(plugin.id, cleanData);
|
await PluginManager.uninstallPlugin(plugin.id, cleanData);
|
||||||
@@ -108,7 +93,11 @@ export default function PluginPage () {
|
|||||||
toast.error(e.message, { id: loadingToast });
|
toast.error(e.message, { id: loadingToast });
|
||||||
reject(e);
|
reject(e);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
onCancel: () => {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user