Files
lzt404 d29a1af69b feat: add plugin management and redesign dashboard and usage analytics
- Add plugin store, version selection, configuration, and OAuth login
- Support Muse (Meta) OAuth device authorization
- Redesign home dashboard and add batch model health checks
- Support per-credential provider editing, removal, and reordering
- Improve usage charts and track actual response models
- Preserve installed plugins and runtime data during portable rebuilds
- Update translations, documentation, and related tests
2026-10-04 17:31:52 +08:00

149 lines
8.3 KiB
TypeScript

import { createRoot } from 'react-dom/client';
import { mockIPC } from '@tauri-apps/api/mocks';
import { I18nProvider } from '../../src/i18n';
import { PluginsPage } from '../../src/pages/PluginsPage';
import '../../src/styles/index.css';
const params = new URLSearchParams(location.search);
localStorage.setItem('easy-cli-proxy-api.locale', params.get('locale') ?? 'en');
document.documentElement.dataset.theme = params.get('theme') ?? 'light';
type Request = { method: string; path: string; query?: Record<string, string>; body?: unknown };
const state = {
supported: !params.has('unsupported'),
commands: [] as { command: string; args: unknown }[],
requests: [] as Request[],
installs: [] as Request[],
resourceRequests: [] as unknown[],
failNextList: false,
failNextInstall: false,
holdInstall: false,
releaseInstall: null as null | (() => void),
settings: {
enabled: true, dir: 'plugins', 'store-sources': ['https://registry.example.test/plugins.json'],
'store-auth': [{ host: 'registry.example.test', 'token-env': 'PLUGIN_FIXTURE_TOKEN' }],
'auth-revision': 9,
configs: {
'sample..plugin': { enabled: true, priority: 3, endpoint: 'https://example.test/api', untouched: { retained: true } },
dormant: { enabled: false },
kiro: { enabled: true },
unloaded: { enabled: true },
} as Record<string, Record<string, unknown>>,
},
};
Object.assign(window, { pluginsFixture: state });
const pluginEntries: Record<string, unknown>[] = [
{ id: 'sample..plugin', path: '/plugins/sample..plugin.dll', configured: true, registered: true,
supports_oauth: true, oauth_provider: 'sample-sso',
metadata: { name: 'Sample provider', version: '1.0.0', author: 'Fixture author' },
config_fields: [{ name: 'endpoint', type: 'string', description: 'Endpoint URL' }],
menus: [null, { path: '', menu: 'Empty' }, { path: '/v0/resource/plugins/sample..plugin/usage', menu: 'Usage dashboard' }],
},
{ id: 'dormant', path: '/plugins/dormant.dll', configured: true, registered: true,
supports_oauth: true, oauth_provider: 'dormant-sso',
metadata: { name: 'Dormant provider', version: '1.0.0' }, menus: [], config_fields: [],
},
{ id: 'kiro', path: '', configured: true, registered: false,
supports_oauth: false, metadata: null, menus: [], config_fields: [],
},
{ id: 'unloaded', path: '/plugins/unloaded.dll', configured: true, registered: false,
supports_oauth: false, metadata: { name: 'Unloaded provider', version: '1.0.0' }, menus: [], config_fields: [],
},
];
const storeEntries: Record<string, unknown>[] = [
{ id: 'sample..plugin', name: 'Sample provider', source_id: 'official', source_name: 'Official',
source_url: 'https://official.example.test/plugins.json', repository: 'router-for-me/sample',
version: '2.0.0', install_type: 'github-release', installed: true, installed_version: '1.0.0', update_available: true,
description: 'Official fixture provider', platforms: [{ goos: 'windows', goarch: 'amd64' }], tags: ['chat'],
},
{ id: 'analytics', name: 'Community analytics', source_id: 'community', source_name: 'Community',
source_url: 'https://registry.example.test/plugins.json', repository: 'router-for-me/analytics',
version: '1.0.0', install_type: 'github-release', description: 'Third-party registry with official-looking metadata',
},
{ id: 'direct', name: 'Direct artifact', source_id: 'official', source_name: 'Official',
repository: 'router-for-me/direct', version: '1.0.0', install_type: 'direct',
},
{ id: 'private', name: 'Private provider', source_id: 'private', source_name: 'Private',
repository: 'private/provider', auth_required: true, auth_configured: false, install_type: 'github-release',
},
{ id: 'kiro', name: 'Kiro', source_id: 'official', source_name: 'Official',
repository: 'router-for-me/kiro', version: '0.1.1', install_type: 'github-release', installed: false,
description: 'A configured provider whose plugin file has not been installed.',
},
];
mockIPC(async (command, args) => {
state.commands.push({ command, args });
if (command === 'start_oauth_login') return { url: 'https://login.example.test/authorize?state=sample-state', state: 'sample-state' };
if (command === 'get_oauth_status') return { status: 'wait' };
if (command === 'get_plugin_support') return state.supported;
if (command === 'get_plugin_resource_url') {
state.resourceRequests.push(args);
if (args?.pluginId !== 'sample..plugin' || args?.menuIndex !== 2) throw new Error('Wrong original menu index');
return `${location.origin}/tests/fixtures/plugins.html?resource=1`;
}
if (command === 'open_external_url' || command === 'restart_core_process') return null;
if (command !== 'management_request') return null;
const request = args?.request as Request;
state.requests.push(structuredClone(request));
if (request.path === '/oauth/session' && request.method === 'DELETE') return { status: 'ok', cancelled: true };
if (request.path === '/plugins' && request.method === 'GET') {
if (state.failNextList) { state.failNextList = false; throw new Error('Fixture list temporarily unavailable'); }
return {
plugins_enabled: state.settings.enabled, plugins_dir: state.settings.dir,
plugins: pluginEntries.map(entry => ({ ...entry,
enabled: state.settings.configs[String(entry.id)]?.enabled === true,
effective_enabled: entry.registered === true && state.settings.enabled && state.settings.configs[String(entry.id)]?.enabled === true,
})),
};
}
if (request.path === '/plugins/store' && request.method === 'GET') return {
plugins_enabled: state.settings.enabled, plugins_dir: state.settings.dir, plugins: storeEntries,
source_errors: [{ source_id: 'offline', source_name: 'Offline source', message: 'Fixture registry unavailable' }],
};
if (request.path === '/config/plugins') {
if (request.method === 'GET') return structuredClone(state.settings);
if (request.method === 'PATCH') { Object.assign(state.settings, request.body); return { status: 'ok' }; }
}
const configMatch = /^\/config\/plugins\/configs\/([^/]+)(\/enabled)?$/.exec(request.path);
if (configMatch) {
const id = decodeURIComponent(configMatch[1]);
if (request.method === 'GET') return structuredClone(state.settings.configs[id] ?? {});
if (request.method === 'PUT') {
state.settings.configs[id] = configMatch[2]
? { ...state.settings.configs[id], enabled: request.body }
: request.body as Record<string, unknown>;
return { status: 'ok' };
}
}
const installMatch = /^\/plugins\/store\/([^/]+)\/install$/.exec(request.path);
if (installMatch && request.method === 'POST') {
state.installs.push(structuredClone(request));
if (state.holdInstall) await new Promise<void>(resolve => { state.releaseInstall = resolve; });
if (state.failNextInstall) { state.failNextInstall = false; throw new Error('Fixture installation failed'); }
const id = decodeURIComponent(installMatch[1]);
const entry = storeEntries.find(candidate => candidate.id === id && candidate.source_id === request.query?.source);
if (!entry) throw new Error('Incorrect plugin store source');
entry.installed = true;
entry.installed_version = request.query?.version || entry.version;
entry.update_available = false;
state.settings.configs[id] ??= { enabled: false };
return { status: 'ok', id, source_id: entry.source_id, version: entry.installed_version,
install_type: entry.install_type, restart_required: true, plugins_enabled: state.settings.enabled };
}
const deleteMatch = /^\/plugins\/([^/]+)$/.exec(request.path);
if (deleteMatch && request.method === 'DELETE') {
const id = decodeURIComponent(deleteMatch[1]);
const index = pluginEntries.findIndex(entry => entry.id === id);
const removed = pluginEntries[index];
if (index >= 0) pluginEntries.splice(index, 1);
delete state.settings.configs[id];
return { status: 'ok', id, file_deleted: Boolean(removed?.path), configured_removed: true,
restart_required: removed?.registered === true };
}
throw new Error(`Unhandled fixture request: ${request.method} ${request.path}`);
});
createRoot(document.getElementById('root')!).render(params.has('resource')
? <h1>Sample resource preview</h1>
: <I18nProvider><main style={{ padding: '24px', maxWidth: '1280px', margin: '0 auto' }}><PluginsPage /></main></I18nProvider>);