mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
refactor: remove unused fields and streamline auth file editor logic
This commit is contained in:
@@ -3,7 +3,6 @@ import { Modal } from '@/components/ui/Modal';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { LoadingSpinner } from '@/components/ui/LoadingSpinner';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { ToggleSwitch } from '@/components/ui/ToggleSwitch';
|
||||
import type {
|
||||
PrefixProxyEditorField,
|
||||
PrefixProxyEditorFieldValue,
|
||||
@@ -132,18 +131,6 @@ export function AuthFilesPrefixProxyEditorModal(props: AuthFilesPrefixProxyEdito
|
||||
disabled={disableControls || editor.saving || !editor.json}
|
||||
onChange={(e) => onChange('priority', e.target.value)}
|
||||
/>
|
||||
<div className="form-group">
|
||||
<label>{t('auth_files.excluded_models_label')}</label>
|
||||
<textarea
|
||||
className="input"
|
||||
value={editor.excludedModelsText}
|
||||
placeholder={t('auth_files.excluded_models_placeholder')}
|
||||
rows={4}
|
||||
disabled={disableControls || editor.saving || !editor.json}
|
||||
onChange={(e) => onChange('excludedModelsText', e.target.value)}
|
||||
/>
|
||||
<div className="hint">{t('auth_files.excluded_models_hint')}</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>{t('auth_files.headers_label')}</label>
|
||||
<textarea
|
||||
@@ -158,14 +145,6 @@ export function AuthFilesPrefixProxyEditorModal(props: AuthFilesPrefixProxyEdito
|
||||
{editor.headersError && <div className="error-box">{editor.headersError}</div>}
|
||||
<div className="hint">{t('auth_files.headers_hint')}</div>
|
||||
</div>
|
||||
<Input
|
||||
label={t('auth_files.disable_cooling_label')}
|
||||
value={editor.disableCooling}
|
||||
placeholder={t('auth_files.disable_cooling_placeholder')}
|
||||
hint={t('auth_files.disable_cooling_hint')}
|
||||
disabled={disableControls || editor.saving || !editor.json}
|
||||
onChange={(e) => onChange('disableCooling', e.target.value)}
|
||||
/>
|
||||
<Input
|
||||
label={t('auth_files.note_label')}
|
||||
value={editor.note}
|
||||
@@ -174,18 +153,6 @@ export function AuthFilesPrefixProxyEditorModal(props: AuthFilesPrefixProxyEdito
|
||||
disabled={disableControls || editor.saving || !editor.json}
|
||||
onChange={(e) => onChange('note', e.target.value)}
|
||||
/>
|
||||
{editor.isCodexFile && (
|
||||
<div className="form-group">
|
||||
<label>{t('ai_providers.codex_websockets_label')}</label>
|
||||
<ToggleSwitch
|
||||
checked={Boolean(editor.websockets)}
|
||||
disabled={disableControls || editor.saving || !editor.json}
|
||||
ariaLabel={t('ai_providers.codex_websockets_label')}
|
||||
onChange={(value) => onChange('websockets', value)}
|
||||
/>
|
||||
<div className="hint">{t('ai_providers.codex_websockets_hint')}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { authFilesApi } from '@/services/api';
|
||||
import { authFilesApi, type AuthFileFieldsPatch } from '@/services/api';
|
||||
import type { AuthFileItem } from '@/types';
|
||||
import { useNotificationStore } from '@/stores';
|
||||
import { formatFileSize } from '@/utils/format';
|
||||
import { MAX_AUTH_FILE_SIZE } from '@/utils/constants';
|
||||
import {
|
||||
applyCodexAuthFileWebsockets,
|
||||
normalizeExcludedModels,
|
||||
parseDisableCoolingValue,
|
||||
parseExcludedModelsText,
|
||||
parsePriorityValue,
|
||||
readCodexAuthFileWebsockets,
|
||||
} from '@/features/authFiles/constants';
|
||||
import { parsePriorityValue } from '@/features/authFiles/constants';
|
||||
|
||||
type AuthFileHeaders = Record<string, string>;
|
||||
type AuthFileHeadersErrorKey =
|
||||
@@ -20,22 +11,13 @@ type AuthFileHeadersErrorKey =
|
||||
| 'auth_files.headers_invalid_object'
|
||||
| 'auth_files.headers_invalid_value';
|
||||
|
||||
export type PrefixProxyEditorField =
|
||||
| 'prefix'
|
||||
| 'proxyUrl'
|
||||
| 'priority'
|
||||
| 'excludedModelsText'
|
||||
| 'disableCooling'
|
||||
| 'websockets'
|
||||
| 'note'
|
||||
| 'headersText';
|
||||
export type PrefixProxyEditorField = 'prefix' | 'proxyUrl' | 'priority' | 'note' | 'headersText';
|
||||
|
||||
export type PrefixProxyEditorFieldValue = string | boolean;
|
||||
export type PrefixProxyEditorFieldValue = string;
|
||||
|
||||
export type PrefixProxyEditorState = {
|
||||
fileName: string;
|
||||
fileInfoText: string;
|
||||
isCodexFile: boolean;
|
||||
loading: boolean;
|
||||
saving: boolean;
|
||||
error: string | null;
|
||||
@@ -45,9 +27,6 @@ export type PrefixProxyEditorState = {
|
||||
prefix: string;
|
||||
proxyUrl: string;
|
||||
priority: string;
|
||||
excludedModelsText: string;
|
||||
disableCooling: string;
|
||||
websockets: boolean;
|
||||
note: string;
|
||||
noteTouched: boolean;
|
||||
headersText: string;
|
||||
@@ -108,46 +87,113 @@ const parseHeadersText = (
|
||||
return { value: parsed as AuthFileHeaders, errorKey: null };
|
||||
};
|
||||
|
||||
const buildPrefixProxyUpdatedText = (
|
||||
editor: PrefixProxyEditorState | null,
|
||||
const normalizeTextField = (value: unknown): string =>
|
||||
typeof value === 'string' ? value.trim() : '';
|
||||
|
||||
const hasKeys = (value: Record<string, unknown> | AuthFileFieldsPatch | null): boolean =>
|
||||
Boolean(value && Object.keys(value).length > 0);
|
||||
|
||||
const normalizeHeaders = (value: unknown): AuthFileHeaders => {
|
||||
if (!isRecordObject(value)) return {};
|
||||
|
||||
return Object.entries(value).reduce<AuthFileHeaders>((result, [key, rawValue]) => {
|
||||
if (typeof rawValue !== 'string') return result;
|
||||
const name = key.trim();
|
||||
const headerValue = rawValue.trim();
|
||||
if (!name || !headerValue) return result;
|
||||
result[name] = headerValue;
|
||||
return result;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const buildHeadersPatch = (
|
||||
originalHeaders: AuthFileHeaders,
|
||||
nextHeaders: AuthFileHeaders
|
||||
): AuthFileHeaders | undefined => {
|
||||
const patch: AuthFileHeaders = {};
|
||||
const nextNames = new Set(Object.keys(nextHeaders));
|
||||
|
||||
Object.entries(nextHeaders).forEach(([name, value]) => {
|
||||
if (originalHeaders[name] !== value) {
|
||||
patch[name] = value;
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(originalHeaders).forEach((name) => {
|
||||
if (!nextNames.has(name)) {
|
||||
patch[name] = '';
|
||||
}
|
||||
});
|
||||
|
||||
return Object.keys(patch).length > 0 ? patch : undefined;
|
||||
};
|
||||
|
||||
const applyHeadersPatch = (
|
||||
value: Record<string, unknown>,
|
||||
headersPatch: AuthFileHeaders | undefined
|
||||
) => {
|
||||
if (!headersPatch) return;
|
||||
|
||||
const nextHeaders = normalizeHeaders(value.headers);
|
||||
Object.entries(headersPatch).forEach(([name, rawValue]) => {
|
||||
const headerName = name.trim();
|
||||
if (!headerName) return;
|
||||
const headerValue = rawValue.trim();
|
||||
if (!headerValue) {
|
||||
delete nextHeaders[headerName];
|
||||
return;
|
||||
}
|
||||
nextHeaders[headerName] = headerValue;
|
||||
});
|
||||
|
||||
if (Object.keys(nextHeaders).length > 0) {
|
||||
value.headers = nextHeaders;
|
||||
} else {
|
||||
delete value.headers;
|
||||
}
|
||||
};
|
||||
|
||||
const buildAuthFileFieldsPatch = (
|
||||
editor: PrefixProxyEditorState,
|
||||
resolveHeadersError: (key: AuthFileHeadersErrorKey) => string
|
||||
): string => {
|
||||
if (!editor?.json) return editor?.rawText ?? '';
|
||||
const next: Record<string, unknown> = { ...editor.json };
|
||||
if ('prefix' in next || editor.prefix.trim()) {
|
||||
next.prefix = editor.prefix;
|
||||
}
|
||||
if ('proxy_url' in next || editor.proxyUrl.trim()) {
|
||||
next.proxy_url = editor.proxyUrl;
|
||||
): AuthFileFieldsPatch => {
|
||||
const original = editor.json ?? {};
|
||||
const patch: AuthFileFieldsPatch = {};
|
||||
|
||||
const originalPrefix = normalizeTextField(original.prefix);
|
||||
const nextPrefix = editor.prefix.trim();
|
||||
if (nextPrefix !== originalPrefix) {
|
||||
patch.prefix = nextPrefix;
|
||||
}
|
||||
|
||||
const parsedPriority = parsePriorityValue(editor.priority);
|
||||
if (parsedPriority !== undefined) {
|
||||
next.priority = parsedPriority;
|
||||
} else if ('priority' in next) {
|
||||
delete next.priority;
|
||||
const originalProxyURL = normalizeTextField(original.proxy_url);
|
||||
const nextProxyURL = editor.proxyUrl.trim();
|
||||
if (nextProxyURL !== originalProxyURL) {
|
||||
patch.proxy_url = nextProxyURL;
|
||||
}
|
||||
|
||||
const excludedModels = parseExcludedModelsText(editor.excludedModelsText);
|
||||
if (excludedModels.length > 0) {
|
||||
next.excluded_models = excludedModels;
|
||||
} else if ('excluded_models' in next) {
|
||||
delete next.excluded_models;
|
||||
}
|
||||
|
||||
const parsedDisableCooling = parseDisableCoolingValue(editor.disableCooling);
|
||||
if (parsedDisableCooling !== undefined) {
|
||||
next.disable_cooling = parsedDisableCooling;
|
||||
} else if ('disable_cooling' in next) {
|
||||
delete next.disable_cooling;
|
||||
const originalPriority = parsePriorityValue(original.priority);
|
||||
const priorityText = editor.priority.trim();
|
||||
const nextPriority = parsePriorityValue(priorityText);
|
||||
if (!priorityText) {
|
||||
if (originalPriority !== undefined && originalPriority !== 0) {
|
||||
patch.priority = 0;
|
||||
}
|
||||
} else if (nextPriority !== undefined) {
|
||||
if (nextPriority === 0) {
|
||||
if (originalPriority !== undefined && originalPriority !== 0) {
|
||||
patch.priority = 0;
|
||||
}
|
||||
} else if (nextPriority !== originalPriority) {
|
||||
patch.priority = nextPriority;
|
||||
}
|
||||
}
|
||||
|
||||
if (editor.noteTouched) {
|
||||
const noteValue = editor.note.trim();
|
||||
if (noteValue) {
|
||||
next.note = editor.note;
|
||||
} else if ('note' in next) {
|
||||
delete next.note;
|
||||
const originalNote = normalizeTextField(original.note);
|
||||
const nextNote = editor.note.trim();
|
||||
if (nextNote !== originalNote) {
|
||||
patch.note = nextNote;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,16 +202,59 @@ const buildPrefixProxyUpdatedText = (
|
||||
if (errorKey) {
|
||||
throw new Error(resolveHeadersError(errorKey));
|
||||
}
|
||||
if (parsedHeaders) {
|
||||
next.headers = parsedHeaders;
|
||||
} else {
|
||||
delete next.headers;
|
||||
const headersPatch = buildHeadersPatch(
|
||||
normalizeHeaders(original.headers),
|
||||
normalizeHeaders(parsedHeaders ?? {})
|
||||
);
|
||||
if (headersPatch) {
|
||||
patch.headers = headersPatch;
|
||||
}
|
||||
}
|
||||
|
||||
return JSON.stringify(
|
||||
editor.isCodexFile ? applyCodexAuthFileWebsockets(next, editor.websockets) : next
|
||||
);
|
||||
return patch;
|
||||
};
|
||||
|
||||
const buildPrefixProxyUpdatedText = (
|
||||
editor: PrefixProxyEditorState | null,
|
||||
resolveHeadersError: (key: AuthFileHeadersErrorKey) => string
|
||||
): string => {
|
||||
if (!editor?.json) return editor?.rawText ?? '';
|
||||
const patch = buildAuthFileFieldsPatch(editor, resolveHeadersError);
|
||||
const next: Record<string, unknown> = { ...editor.json };
|
||||
if (patch.prefix !== undefined) {
|
||||
if (patch.prefix) {
|
||||
next.prefix = patch.prefix;
|
||||
} else {
|
||||
delete next.prefix;
|
||||
}
|
||||
}
|
||||
if (patch.proxy_url !== undefined) {
|
||||
if (patch.proxy_url) {
|
||||
next.proxy_url = patch.proxy_url;
|
||||
} else {
|
||||
delete next.proxy_url;
|
||||
}
|
||||
}
|
||||
|
||||
if (patch.priority !== undefined) {
|
||||
if (patch.priority === 0) {
|
||||
delete next.priority;
|
||||
} else {
|
||||
next.priority = patch.priority;
|
||||
}
|
||||
}
|
||||
|
||||
if (patch.note !== undefined) {
|
||||
if (patch.note) {
|
||||
next.note = patch.note;
|
||||
} else if ('note' in next) {
|
||||
delete next.note;
|
||||
}
|
||||
}
|
||||
|
||||
applyHeadersPatch(next, patch.headers);
|
||||
|
||||
return JSON.stringify(next);
|
||||
};
|
||||
|
||||
export function useAuthFilesPrefixProxyEditor(
|
||||
@@ -185,10 +274,12 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
? buildPrefixProxyUpdatedText(prefixProxyEditor, (key) => t(key))
|
||||
: '';
|
||||
|
||||
const prefixProxyDirty =
|
||||
Boolean(prefixProxyEditor?.json) &&
|
||||
Boolean(prefixProxyEditor?.originalText) &&
|
||||
(prefixProxyUpdatedText === '' || prefixProxyUpdatedText !== prefixProxyEditor?.originalText);
|
||||
const prefixProxyPatch =
|
||||
prefixProxyEditor?.json && !hasBlockingValidationError
|
||||
? buildAuthFileFieldsPatch(prefixProxyEditor, (key) => t(key))
|
||||
: null;
|
||||
|
||||
const prefixProxyDirty = hasKeys(prefixProxyPatch);
|
||||
|
||||
const closePrefixProxyEditor = () => {
|
||||
setPrefixProxyEditor(null);
|
||||
@@ -196,13 +287,6 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
|
||||
const openPrefixProxyEditor = async (file: AuthFileItem) => {
|
||||
const name = file.name;
|
||||
const normalizedType = String(file.type ?? '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const normalizedProvider = String(file.provider ?? '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
const isCodexFile = normalizedType === 'codex' || normalizedProvider === 'codex';
|
||||
|
||||
if (disableControls) return;
|
||||
if (prefixProxyEditor?.fileName === name) {
|
||||
@@ -213,7 +297,6 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
setPrefixProxyEditor({
|
||||
fileName: name,
|
||||
fileInfoText: JSON.stringify(file, null, 2),
|
||||
isCodexFile,
|
||||
loading: true,
|
||||
saving: false,
|
||||
error: null,
|
||||
@@ -223,9 +306,6 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
prefix: '',
|
||||
proxyUrl: '',
|
||||
priority: '',
|
||||
excludedModelsText: '',
|
||||
disableCooling: '',
|
||||
websockets: false,
|
||||
note: '',
|
||||
noteTouched: false,
|
||||
headersText: '',
|
||||
@@ -269,18 +349,10 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
}
|
||||
|
||||
const json = { ...(parsed as Record<string, unknown>) };
|
||||
if (isCodexFile) {
|
||||
const normalizedWebsockets = readCodexAuthFileWebsockets(json);
|
||||
delete json.websocket;
|
||||
json.websockets = normalizedWebsockets;
|
||||
}
|
||||
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 disableCoolingValue = parseDisableCoolingValue(json.disable_cooling);
|
||||
const websocketsValue = readCodexAuthFileWebsockets(json);
|
||||
const note = typeof json.note === 'string' ? json.note : '';
|
||||
const headers = json.headers;
|
||||
let headersText = '';
|
||||
@@ -302,10 +374,6 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
prefix,
|
||||
proxyUrl,
|
||||
priority: priority !== undefined ? String(priority) : '',
|
||||
excludedModelsText: excludedModels.join('\n'),
|
||||
disableCooling:
|
||||
disableCoolingValue === undefined ? '' : disableCoolingValue ? 'true' : 'false',
|
||||
websockets: websocketsValue,
|
||||
note,
|
||||
noteTouched: false,
|
||||
headersText,
|
||||
@@ -333,8 +401,6 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
if (field === 'prefix') return { ...prev, prefix: String(value) };
|
||||
if (field === 'proxyUrl') return { ...prev, proxyUrl: String(value) };
|
||||
if (field === 'priority') return { ...prev, priority: String(value) };
|
||||
if (field === 'excludedModelsText') return { ...prev, excludedModelsText: String(value) };
|
||||
if (field === 'disableCooling') return { ...prev, disableCooling: String(value) };
|
||||
if (field === 'note') return { ...prev, note: String(value), noteTouched: true };
|
||||
if (field === 'headersText') {
|
||||
const headersText = String(value);
|
||||
@@ -346,7 +412,7 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
headersError: errorKey ? t(errorKey) : null,
|
||||
};
|
||||
}
|
||||
return { ...prev, websockets: Boolean(value) };
|
||||
return prev;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -355,23 +421,15 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
if (!prefixProxyDirty) return;
|
||||
|
||||
const name = prefixProxyEditor.fileName;
|
||||
let payload = '';
|
||||
let payload: AuthFileFieldsPatch;
|
||||
try {
|
||||
payload = buildPrefixProxyUpdatedText(prefixProxyEditor, (key) => t(key));
|
||||
payload = buildAuthFileFieldsPatch(prefixProxyEditor, (key) => t(key));
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : 'Invalid format';
|
||||
showNotification(errorMessage, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const fileSize = new Blob([payload]).size;
|
||||
if (fileSize > MAX_AUTH_FILE_SIZE) {
|
||||
showNotification(
|
||||
t('auth_files.upload_error_size', { maxSize: formatFileSize(MAX_AUTH_FILE_SIZE) }),
|
||||
'error'
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!hasKeys(payload)) return;
|
||||
|
||||
setPrefixProxyEditor((prev) => {
|
||||
if (!prev || prev.fileName !== name) return prev;
|
||||
@@ -379,13 +437,13 @@ export function useAuthFilesPrefixProxyEditor(
|
||||
});
|
||||
|
||||
try {
|
||||
await authFilesApi.saveText(name, payload);
|
||||
await authFilesApi.patchFields(name, payload);
|
||||
showNotification(t('auth_files.prefix_proxy_saved_success', { name }), 'success');
|
||||
await loadFiles();
|
||||
setPrefixProxyEditor(null);
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : '';
|
||||
showNotification(`${t('notification.upload_failed')}: ${errorMessage}`, 'error');
|
||||
showNotification(`${t('notification.update_failed')}: ${errorMessage}`, 'error');
|
||||
setPrefixProxyEditor((prev) => {
|
||||
if (!prev || prev.fileName !== name) return prev;
|
||||
return { ...prev, saving: false };
|
||||
|
||||
@@ -10,6 +10,13 @@ import { parseTimestampMs } from '@/utils/timestamp';
|
||||
type StatusError = { status?: number };
|
||||
type AuthFileStatusResponse = { status: string; disabled: boolean };
|
||||
type AuthFileEntry = AuthFilesResponse['files'][number];
|
||||
export type AuthFileFieldsPatch = {
|
||||
prefix?: string;
|
||||
proxy_url?: string;
|
||||
headers?: Record<string, string>;
|
||||
priority?: number;
|
||||
note?: string;
|
||||
};
|
||||
type AuthFileBatchFailure = { name: string; error: string };
|
||||
type AuthFileBatchUploadResponse = {
|
||||
status?: string;
|
||||
@@ -401,6 +408,9 @@ export const authFilesApi = {
|
||||
setStatus: (name: string, disabled: boolean) =>
|
||||
apiClient.patch<AuthFileStatusResponse>('/auth-files/status', { name, disabled }),
|
||||
|
||||
patchFields: (name: string, fields: AuthFileFieldsPatch) =>
|
||||
apiClient.patch('/auth-files/fields', { name, ...fields }),
|
||||
|
||||
uploadFiles: async (files: File[]): Promise<AuthFileBatchUploadResult> => {
|
||||
const requestedNames = files.map((file) => file.name);
|
||||
if (requestedNames.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user