feat(pluginStore): add support for third-party plugin sources with localization and configuration updates

This commit is contained in:
LTbinglingfeng
2026-06-15 17:23:54 +08:00
Unverified
parent cc16be4c7b
commit 1279cc1299
8 changed files with 91 additions and 2 deletions
@@ -37,6 +37,7 @@ import {
ApiKeysCardEditor,
PayloadFilterRulesEditor,
PayloadRulesEditor,
StringListEditor,
} from './VisualConfigEditorBlocks';
import styles from './VisualConfigEditor.module.scss';
@@ -218,6 +219,10 @@ export function VisualConfigEditor({
(apiKeysText: string) => onChange({ apiKeysText }),
[onChange]
);
const handlePluginStoreSourcesChange = useCallback(
(pluginStoreSources: string[]) => onChange({ pluginStoreSources }),
[onChange]
);
const handlePayloadDefaultRulesChange = useCallback(
(payloadDefaultRules: PayloadRule[]) => onChange({ payloadDefaultRules }),
[onChange]
@@ -667,6 +672,33 @@ export function VisualConfigEditor({
/>
</SectionGrid>
<SectionSubsection
title={t('config_management.visual.sections.system.plugin_store_sources')}
description={t(
'config_management.visual.sections.system.plugin_store_sources_desc'
)}
>
<div className={styles.fieldShell}>
<label className={styles.fieldLabel}>
{t('config_management.visual.sections.system.plugin_store_sources_label')}
</label>
<StringListEditor
value={values.pluginStoreSources}
disabled={disabled}
placeholder={t(
'config_management.visual.sections.system.plugin_store_sources_placeholder'
)}
inputAriaLabel={t(
'config_management.visual.sections.system.plugin_store_sources_label'
)}
onChange={handlePluginStoreSourcesChange}
/>
<div className={styles.fieldHint}>
{t('config_management.visual.sections.system.plugin_store_sources_hint')}
</div>
</div>
</SectionSubsection>
<SectionGrid>
<Input
label={t('config_management.visual.sections.system.logs_max_size')}
@@ -389,7 +389,7 @@ export const ApiKeysCardEditor = memo(function ApiKeysCardEditor({
);
});
const StringListEditor = memo(function StringListEditor({
export const StringListEditor = memo(function StringListEditor({
value,
disabled,
placeholder,
+36 -1
View File
@@ -120,6 +120,15 @@ function setStringInDoc(doc: YamlDocument, path: YamlPath, value: unknown): void
}
}
function setStringListInDoc(doc: YamlDocument, path: YamlPath, values: string[]): void {
const nextValues = values.map((value) => value.trim()).filter(Boolean);
if (nextValues.length > 0) {
doc.setIn(path, nextValues);
return;
}
if (docHas(doc, path)) doc.deleteIn(path);
}
function setIntFromStringInDoc(doc: YamlDocument, path: YamlPath, value: unknown): void {
const safe = typeof value === 'string' ? value : '';
const trimmed = safe.trim();
@@ -790,6 +799,13 @@ function getNextDirtyFields(
] as Array<keyof VisualConfigValues>
).forEach(updateScalarDirty);
if (Object.prototype.hasOwnProperty.call(patch, 'pluginStoreSources')) {
updateDirty(
'pluginStoreSources',
areStringArraysEqual(nextValues.pluginStoreSources, baselineValues.pluginStoreSources)
);
}
if (Object.prototype.hasOwnProperty.call(patch, 'payloadDefaultRules')) {
updateDirty(
'payloadDefaultRules',
@@ -957,6 +973,7 @@ export function useVisualConfig() {
authDir: typeof parsed['auth-dir'] === 'string' ? parsed['auth-dir'] : '',
apiKeysText: resolveApiKeysText(parsed),
pluginsEnabled: Boolean(plugins?.enabled),
pluginStoreSources: parseStringList(plugins?.['store-sources']),
debug: Boolean(parsed.debug),
commercialMode: Boolean(parsed['commercial-mode']),
@@ -1126,10 +1143,28 @@ export function useVisualConfig() {
if (
docHas(doc, ['plugins']) ||
values.pluginsEnabled ||
shouldWriteManagedField(doc, ['plugins', 'enabled'], dirtyFields, 'pluginsEnabled')
values.pluginStoreSources.length > 0 ||
shouldWriteManagedField(doc, ['plugins', 'enabled'], dirtyFields, 'pluginsEnabled') ||
shouldWriteManagedField(
doc,
['plugins', 'store-sources'],
dirtyFields,
'pluginStoreSources'
)
) {
ensureMapInDoc(doc, ['plugins']);
setBooleanInDoc(doc, ['plugins', 'enabled'], values.pluginsEnabled);
if (
values.pluginStoreSources.length > 0 ||
shouldWriteManagedField(
doc,
['plugins', 'store-sources'],
dirtyFields,
'pluginStoreSources'
)
) {
setStringListInDoc(doc, ['plugins', 'store-sources'], values.pluginStoreSources);
}
deleteIfMapEmpty(doc, ['plugins']);
}
+5
View File
@@ -920,6 +920,11 @@
"logging_to_file_desc": "Save logs to files",
"plugins_enabled": "Enable Plugin System",
"plugins_enabled_desc": "Enable standard dynamic-library plugin loading; individual plugin instances are still managed on the Plugins page",
"plugin_store_sources": "Third-party Plugin Sources",
"plugin_store_sources_desc": "Append plugin-store registry sources; the built-in official source is always kept",
"plugin_store_sources_label": "Plugin source registry URL (plugins.store-sources)",
"plugin_store_sources_placeholder": "https://example.com/cliproxy-plugins/registry.json",
"plugin_store_sources_hint": "One registry.json URL per row. Empty rows are filtered on save",
"logs_max_size": "Log File Size Limit (MB)",
"error_logs_max_files": "Retained Error Log Files",
"usage_statistics_enabled": "Enable In-memory Usage Statistics",
+5
View File
@@ -907,6 +907,11 @@
"logging_to_file_desc": "Сохранять журналы в файлы",
"plugins_enabled": "Включить систему плагинов",
"plugins_enabled_desc": "Включает загрузку стандартных dynamic-library плагинов; отдельные экземпляры управляются на странице плагинов",
"plugin_store_sources": "Сторонние источники плагинов",
"plugin_store_sources_desc": "Добавляет registry-источники магазина плагинов; встроенный официальный источник всегда сохраняется",
"plugin_store_sources_label": "URL registry источника плагинов (plugins.store-sources)",
"plugin_store_sources_placeholder": "https://example.com/cliproxy-plugins/registry.json",
"plugin_store_sources_hint": "Один URL registry.json в строке. Пустые строки отфильтровываются при сохранении",
"logs_max_size": "Максимальный размер файла журнала (МБ)",
"error_logs_max_files": "Файлов журнала ошибок",
"usage_statistics_enabled": "Включить статистику использования в памяти",
+5
View File
@@ -920,6 +920,11 @@
"logging_to_file_desc": "将日志保存到文件",
"plugins_enabled": "启用插件系统",
"plugins_enabled_desc": "启用标准动态库插件加载;具体插件实例仍在插件管理页启停",
"plugin_store_sources": "第三方插件源",
"plugin_store_sources_desc": "追加插件商店 registry 源;内置官方源始终保留",
"plugin_store_sources_label": "插件源 registry URL (plugins.store-sources)",
"plugin_store_sources_placeholder": "https://example.com/cliproxy-plugins/registry.json",
"plugin_store_sources_hint": "每行一个 registry.json URL,保存时会过滤空行",
"logs_max_size": "日志文件大小限制 (MB)",
"error_logs_max_files": "错误日志保留文件数",
"usage_statistics_enabled": "启用内存用量统计",
+5
View File
@@ -946,6 +946,11 @@
"logging_to_file_desc": "將記錄儲存到檔案",
"plugins_enabled": "啟用插件系統",
"plugins_enabled_desc": "啟用標準動態庫插件載入;具體插件實例仍在插件管理頁啟停",
"plugin_store_sources": "第三方插件源",
"plugin_store_sources_desc": "追加插件商店 registry 源;內建官方源始終保留",
"plugin_store_sources_label": "插件源 registry URLplugins.store-sources",
"plugin_store_sources_placeholder": "https://example.com/cliproxy-plugins/registry.json",
"plugin_store_sources_hint": "每列一個 registry.json URL,儲存時會過濾空列",
"logs_max_size": "記錄檔大小限制(MB",
"error_logs_max_files": "錯誤記錄保留檔案數",
"usage_statistics_enabled": "啟用記憶體用量統計",
+2
View File
@@ -81,6 +81,7 @@ export type VisualConfigValues = {
authDir: string;
apiKeysText: string;
pluginsEnabled: boolean;
pluginStoreSources: string[];
debug: boolean;
commercialMode: boolean;
loggingToFile: boolean;
@@ -145,6 +146,7 @@ export const DEFAULT_VISUAL_VALUES: VisualConfigValues = {
authDir: '',
apiKeysText: '',
pluginsEnabled: false,
pluginStoreSources: [],
debug: false,
commercialMode: false,
loggingToFile: false,