mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-18 02:30:51 +08:00
feat(auth-files): support editing priority/excluded_models/disable_cooling and localize auth field editor
This commit is contained in:
@@ -419,15 +419,25 @@
|
||||
"status_toggle_label": "Enabled",
|
||||
"status_enabled_success": "\"{{name}}\" enabled",
|
||||
"status_disabled_success": "\"{{name}}\" disabled",
|
||||
"prefix_proxy_button": "Edit prefix/proxy_url",
|
||||
"prefix_proxy_loading": "Loading credential...",
|
||||
"prefix_proxy_source_label": "Credential JSON",
|
||||
"prefix_label": "prefix",
|
||||
"proxy_url_label": "proxy_url",
|
||||
"prefix_proxy_button": "Edit Auth Fields",
|
||||
"auth_field_editor_title": "Edit Auth Fields - {{name}}",
|
||||
"prefix_proxy_loading": "Loading auth file...",
|
||||
"prefix_proxy_source_label": "Auth file JSON (preview)",
|
||||
"prefix_label": "Prefix (prefix)",
|
||||
"proxy_url_label": "Proxy URL (proxy_url)",
|
||||
"prefix_placeholder": "",
|
||||
"proxy_url_placeholder": "socks5://username:password@proxy_ip:port/",
|
||||
"prefix_proxy_invalid_json": "This credential is not a JSON object and cannot be edited.",
|
||||
"prefix_proxy_saved_success": "Updated \"{{name}}\" successfully",
|
||||
"priority_label": "Priority (priority)",
|
||||
"priority_placeholder": "e.g. 10 or -1",
|
||||
"priority_hint": "Integers only. Invalid values are ignored. Larger value means higher priority.",
|
||||
"excluded_models_label": "Excluded models (excluded_models)",
|
||||
"excluded_models_placeholder": "Comma or newline separated, e.g. model-a, gpt-5-*, *-preview",
|
||||
"excluded_models_hint": "Saved as an array and normalized by trim/lowercase/dedup/sort.",
|
||||
"disable_cooling_label": "Disable cooling (disable_cooling)",
|
||||
"disable_cooling_placeholder": "e.g. true / false / 1 / 0",
|
||||
"disable_cooling_hint": "Supports booleans, numeric 0/non-0, and strings like true/false/1/0; unparseable values are ignored.",
|
||||
"prefix_proxy_invalid_json": "This auth file is not a JSON object, so fields cannot be edited.",
|
||||
"prefix_proxy_saved_success": "Updated auth file \"{{name}}\" successfully",
|
||||
"quota_refresh_success": "Quota refreshed for \"{{name}}\"",
|
||||
"quota_refresh_failed": "Failed to refresh quota for \"{{name}}\": {{message}}"
|
||||
},
|
||||
|
||||
@@ -419,15 +419,25 @@
|
||||
"status_toggle_label": "Включено",
|
||||
"status_enabled_success": "\"{{name}}\" включён",
|
||||
"status_disabled_success": "\"{{name}}\" отключён",
|
||||
"prefix_proxy_button": "Изменить prefix/proxy_url",
|
||||
"prefix_proxy_loading": "Загрузка учётных данных...",
|
||||
"prefix_proxy_source_label": "JSON учётных данных",
|
||||
"prefix_label": "prefix",
|
||||
"proxy_url_label": "proxy_url",
|
||||
"prefix_proxy_button": "Редактировать поля файла авторизации",
|
||||
"auth_field_editor_title": "Редактировать поля файла авторизации - {{name}}",
|
||||
"prefix_proxy_loading": "Загрузка файла авторизации...",
|
||||
"prefix_proxy_source_label": "JSON файла авторизации (предпросмотр)",
|
||||
"prefix_label": "Префикс (prefix)",
|
||||
"proxy_url_label": "URL прокси (proxy_url)",
|
||||
"prefix_placeholder": "",
|
||||
"proxy_url_placeholder": "socks5://username:password@proxy_ip:port/",
|
||||
"prefix_proxy_invalid_json": "Эти учётные данные не являются JSON-объектом и не могут быть изменены.",
|
||||
"prefix_proxy_saved_success": "\"{{name}}\" успешно обновлён",
|
||||
"priority_label": "Приоритет (priority)",
|
||||
"priority_placeholder": "например: 10 или -1",
|
||||
"priority_hint": "Только целые числа. Некорректные значения игнорируются. Чем больше число, тем выше приоритет.",
|
||||
"excluded_models_label": "Исключённые модели (excluded_models)",
|
||||
"excluded_models_placeholder": "Через запятую или с новой строки, например: model-a, gpt-5-*, *-preview",
|
||||
"excluded_models_hint": "Сохраняется как массив; значения trim/нижний регистр/без дублей/с сортировкой.",
|
||||
"disable_cooling_label": "Отключение охлаждения (disable_cooling)",
|
||||
"disable_cooling_placeholder": "например: true / false / 1 / 0",
|
||||
"disable_cooling_hint": "Поддерживает boolean, числа 0/не 0 и строки true/false/1/0; непарсируемые значения игнорируются.",
|
||||
"prefix_proxy_invalid_json": "Этот файл авторизации не является JSON-объектом, поэтому поля нельзя редактировать.",
|
||||
"prefix_proxy_saved_success": "Файл авторизации \"{{name}}\" успешно обновлён",
|
||||
"card_tools_title": "Инструменты",
|
||||
"quota_refresh_single": "Обновить квоту",
|
||||
"quota_refresh_hint": "Обновить квоту только для этих учётных данных",
|
||||
|
||||
@@ -419,15 +419,25 @@
|
||||
"status_toggle_label": "启用",
|
||||
"status_enabled_success": "已启用 \"{{name}}\"",
|
||||
"status_disabled_success": "已停用 \"{{name}}\"",
|
||||
"prefix_proxy_button": "配置 prefix/proxy_url",
|
||||
"prefix_proxy_loading": "正在加载凭证文件...",
|
||||
"prefix_proxy_source_label": "凭证 JSON",
|
||||
"prefix_label": "prefix",
|
||||
"proxy_url_label": "proxy_url",
|
||||
"prefix_proxy_button": "编辑认证文件字段",
|
||||
"auth_field_editor_title": "编辑认证文件字段 - {{name}}",
|
||||
"prefix_proxy_loading": "正在加载认证文件...",
|
||||
"prefix_proxy_source_label": "认证文件 JSON(预览)",
|
||||
"prefix_label": "前缀(prefix)",
|
||||
"proxy_url_label": "代理 URL(proxy_url)",
|
||||
"prefix_placeholder": "",
|
||||
"proxy_url_placeholder": "socks5://username:password@proxy_ip:port/",
|
||||
"prefix_proxy_invalid_json": "该凭证文件不是 JSON 对象,无法编辑。",
|
||||
"prefix_proxy_saved_success": "已更新 \"{{name}}\"",
|
||||
"priority_label": "优先级(priority)",
|
||||
"priority_placeholder": "例如: 10 或 -1",
|
||||
"priority_hint": "仅支持整数;非法值会被忽略。数值越大优先级越高。",
|
||||
"excluded_models_label": "排除模型(excluded_models)",
|
||||
"excluded_models_placeholder": "用逗号或换行分隔,例如: model-a, gpt-5-*, *-preview",
|
||||
"excluded_models_hint": "保存为数组;会自动 trim、小写、去重并排序。",
|
||||
"disable_cooling_label": "禁用冷却(disable_cooling)",
|
||||
"disable_cooling_placeholder": "例如: true / false / 1 / 0",
|
||||
"disable_cooling_hint": "支持布尔值、0/非0 数字或字符串 true/false/1/0;无法解析时忽略。",
|
||||
"prefix_proxy_invalid_json": "该认证文件不是 JSON 对象,无法编辑字段。",
|
||||
"prefix_proxy_saved_success": "已更新认证文件 \"{{name}}\"",
|
||||
"quota_refresh_success": "已刷新 \"{{name}}\" 的额度",
|
||||
"quota_refresh_failed": "刷新 \"{{name}}\" 的额度失败:{{message}}"
|
||||
},
|
||||
|
||||
@@ -95,6 +95,9 @@ const MIN_CARD_PAGE_SIZE = 3;
|
||||
const MAX_CARD_PAGE_SIZE = 30;
|
||||
const MAX_AUTH_FILE_SIZE = 50 * 1024;
|
||||
const AUTH_FILES_UI_STATE_KEY = 'authFilesPage.uiState';
|
||||
const INTEGER_STRING_PATTERN = /^[+-]?\d+$/;
|
||||
const TRUTHY_TEXT_VALUES = new Set(['true', '1', 'yes', 'y', 'on']);
|
||||
const FALSY_TEXT_VALUES = new Set(['false', '0', 'no', 'n', 'off']);
|
||||
|
||||
const clampCardPageSize = (value: number) =>
|
||||
Math.min(MAX_CARD_PAGE_SIZE, Math.max(MIN_CARD_PAGE_SIZE, Math.round(value)));
|
||||
@@ -208,7 +211,54 @@ interface PrefixProxyEditorState {
|
||||
json: Record<string, unknown> | null;
|
||||
prefix: string;
|
||||
proxyUrl: string;
|
||||
priority: string;
|
||||
excludedModelsText: string;
|
||||
disableCooling: string;
|
||||
}
|
||||
|
||||
const parsePriorityValue = (value: unknown): number | undefined => {
|
||||
if (typeof value === 'number') {
|
||||
return Number.isInteger(value) ? value : undefined;
|
||||
}
|
||||
|
||||
if (typeof value !== 'string') return undefined;
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed || !INTEGER_STRING_PATTERN.test(trimmed)) return undefined;
|
||||
const parsed = Number.parseInt(trimmed, 10);
|
||||
return Number.isSafeInteger(parsed) ? parsed : undefined;
|
||||
};
|
||||
|
||||
const normalizeExcludedModels = (value: unknown): string[] => {
|
||||
if (!Array.isArray(value)) return [];
|
||||
|
||||
const seen = new Set<string>();
|
||||
const normalized: string[] = [];
|
||||
value.forEach((entry) => {
|
||||
const model = String(entry ?? '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
if (!model || seen.has(model)) return;
|
||||
seen.add(model);
|
||||
normalized.push(model);
|
||||
});
|
||||
|
||||
return normalized.sort((a, b) => a.localeCompare(b));
|
||||
};
|
||||
|
||||
const parseExcludedModelsText = (value: string): string[] =>
|
||||
normalizeExcludedModels(value.split(/[\n,]+/));
|
||||
|
||||
const parseDisableCoolingValue = (value: unknown): boolean | undefined => {
|
||||
if (typeof value === 'boolean') return value;
|
||||
if (typeof value === 'number' && Number.isFinite(value)) return value !== 0;
|
||||
if (typeof value !== 'string') return undefined;
|
||||
|
||||
const normalized = value.trim().toLowerCase();
|
||||
if (!normalized) return undefined;
|
||||
if (TRUTHY_TEXT_VALUES.has(normalized)) return true;
|
||||
if (FALSY_TEXT_VALUES.has(normalized)) return false;
|
||||
return undefined;
|
||||
};
|
||||
// 标准化 auth_index 值(与 usage.ts 中的 normalizeAuthIndex 保持一致)
|
||||
function normalizeAuthIndexValue(value: unknown): string | null {
|
||||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||||
@@ -441,11 +491,36 @@ export function AuthFilesPage() {
|
||||
if ('proxy_url' in next || prefixProxyEditor.proxyUrl.trim()) {
|
||||
next.proxy_url = prefixProxyEditor.proxyUrl;
|
||||
}
|
||||
|
||||
const parsedPriority = parsePriorityValue(prefixProxyEditor.priority);
|
||||
if (parsedPriority !== undefined) {
|
||||
next.priority = parsedPriority;
|
||||
} else if ('priority' in next) {
|
||||
delete next.priority;
|
||||
}
|
||||
|
||||
const excludedModels = parseExcludedModelsText(prefixProxyEditor.excludedModelsText);
|
||||
if (excludedModels.length > 0) {
|
||||
next.excluded_models = excludedModels;
|
||||
} else if ('excluded_models' in next) {
|
||||
delete next.excluded_models;
|
||||
}
|
||||
|
||||
const parsedDisableCooling = parseDisableCoolingValue(prefixProxyEditor.disableCooling);
|
||||
if (parsedDisableCooling !== undefined) {
|
||||
next.disable_cooling = parsedDisableCooling;
|
||||
} else if ('disable_cooling' in next) {
|
||||
delete next.disable_cooling;
|
||||
}
|
||||
|
||||
return JSON.stringify(next);
|
||||
}, [
|
||||
prefixProxyEditor?.json,
|
||||
prefixProxyEditor?.prefix,
|
||||
prefixProxyEditor?.proxyUrl,
|
||||
prefixProxyEditor?.priority,
|
||||
prefixProxyEditor?.excludedModelsText,
|
||||
prefixProxyEditor?.disableCooling,
|
||||
prefixProxyEditor?.rawText,
|
||||
]);
|
||||
|
||||
@@ -857,6 +932,9 @@ export function AuthFilesPage() {
|
||||
json: null,
|
||||
prefix: '',
|
||||
proxyUrl: '',
|
||||
priority: '',
|
||||
excludedModelsText: '',
|
||||
disableCooling: '',
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -898,6 +976,9 @@ export function AuthFilesPage() {
|
||||
const originalText = JSON.stringify(json);
|
||||
const prefix = typeof json.prefix === 'string' ? json.prefix : '';
|
||||
const proxyUrl = typeof json.proxy_url === 'string' ? json.proxy_url : '';
|
||||
const priority = parsePriorityValue(json.priority);
|
||||
const excludedModels = normalizeExcludedModels(json.excluded_models);
|
||||
const disableCooling = parseDisableCoolingValue(json.disable_cooling);
|
||||
|
||||
setPrefixProxyEditor((prev) => {
|
||||
if (!prev || prev.fileName !== name) return prev;
|
||||
@@ -909,6 +990,10 @@ export function AuthFilesPage() {
|
||||
json,
|
||||
prefix,
|
||||
proxyUrl,
|
||||
priority: priority !== undefined ? String(priority) : '',
|
||||
excludedModelsText: excludedModels.join('\n'),
|
||||
disableCooling:
|
||||
disableCooling === undefined ? '' : disableCooling ? 'true' : 'false',
|
||||
error: null,
|
||||
};
|
||||
});
|
||||
@@ -922,11 +1007,17 @@ export function AuthFilesPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrefixProxyChange = (field: 'prefix' | 'proxyUrl', value: string) => {
|
||||
const handlePrefixProxyChange = (
|
||||
field: 'prefix' | 'proxyUrl' | 'priority' | 'excludedModelsText' | 'disableCooling',
|
||||
value: string
|
||||
) => {
|
||||
setPrefixProxyEditor((prev) => {
|
||||
if (!prev) return prev;
|
||||
if (field === 'prefix') return { ...prev, prefix: value };
|
||||
return { ...prev, proxyUrl: value };
|
||||
if (field === 'proxyUrl') return { ...prev, proxyUrl: value };
|
||||
if (field === 'priority') return { ...prev, priority: value };
|
||||
if (field === 'excludedModelsText') return { ...prev, excludedModelsText: value };
|
||||
return { ...prev, disableCooling: value };
|
||||
});
|
||||
};
|
||||
|
||||
@@ -2171,7 +2262,7 @@ export function AuthFilesPage() {
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
{/* prefix/proxy_url 编辑弹窗 */}
|
||||
{/* 认证文件字段编辑弹窗 */}
|
||||
<Modal
|
||||
open={Boolean(prefixProxyEditor)}
|
||||
onClose={() => setPrefixProxyEditor(null)}
|
||||
@@ -2179,7 +2270,7 @@ export function AuthFilesPage() {
|
||||
width={720}
|
||||
title={
|
||||
prefixProxyEditor?.fileName
|
||||
? `${t('auth_files.prefix_proxy_button')} - ${prefixProxyEditor.fileName}`
|
||||
? t('auth_files.auth_field_editor_title', { name: prefixProxyEditor.fileName })
|
||||
: t('auth_files.prefix_proxy_button')
|
||||
}
|
||||
footer={
|
||||
@@ -2247,6 +2338,42 @@ export function AuthFilesPage() {
|
||||
}
|
||||
onChange={(e) => handlePrefixProxyChange('proxyUrl', e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
label={t('auth_files.priority_label')}
|
||||
value={prefixProxyEditor.priority}
|
||||
placeholder={t('auth_files.priority_placeholder')}
|
||||
hint={t('auth_files.priority_hint')}
|
||||
disabled={
|
||||
disableControls || prefixProxyEditor.saving || !prefixProxyEditor.json
|
||||
}
|
||||
onChange={(e) => handlePrefixProxyChange('priority', e.target.value)}
|
||||
/>
|
||||
<div className="form-group">
|
||||
<label>{t('auth_files.excluded_models_label')}</label>
|
||||
<textarea
|
||||
className="input"
|
||||
value={prefixProxyEditor.excludedModelsText}
|
||||
placeholder={t('auth_files.excluded_models_placeholder')}
|
||||
rows={4}
|
||||
disabled={
|
||||
disableControls || prefixProxyEditor.saving || !prefixProxyEditor.json
|
||||
}
|
||||
onChange={(e) =>
|
||||
handlePrefixProxyChange('excludedModelsText', e.target.value)
|
||||
}
|
||||
/>
|
||||
<div className="hint">{t('auth_files.excluded_models_hint')}</div>
|
||||
</div>
|
||||
<Input
|
||||
label={t('auth_files.disable_cooling_label')}
|
||||
value={prefixProxyEditor.disableCooling}
|
||||
placeholder={t('auth_files.disable_cooling_placeholder')}
|
||||
hint={t('auth_files.disable_cooling_hint')}
|
||||
disabled={
|
||||
disableControls || prefixProxyEditor.saving || !prefixProxyEditor.json
|
||||
}
|
||||
onChange={(e) => handlePrefixProxyChange('disableCooling', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user