diff --git a/src/features/authFiles/components/AuthFileCard.tsx b/src/features/authFiles/components/AuthFileCard.tsx index 9e7b9108..5cd01fab 100644 --- a/src/features/authFiles/components/AuthFileCard.tsx +++ b/src/features/authFiles/components/AuthFileCard.tsx @@ -22,6 +22,7 @@ import { getTypeColor, getTypeLabel, isRuntimeOnlyAuthFile, + parsePriorityValue, resolveAuthFileStats, type QuotaProviderType, type ResolvedTheme, @@ -110,6 +111,9 @@ export function AuthFileCard(props: AuthFileCardProps) { const hasStatusWarning = Boolean(rawStatusMessage) && !HEALTHY_STATUS_MESSAGES.has(rawStatusMessage.toLowerCase()); + const priorityValue = parsePriorityValue(file.priority ?? file['priority']); + const noteValue = typeof file.note === 'string' ? file.note.trim() : ''; + return (
{t('auth_files.file_modified')}: {formatModified(file)} + {priorityValue !== undefined && ( + + {t('auth_files.priority_display')}: {priorityValue} + + )}
+ {noteValue && ( +
+ {t('auth_files.note_display')}: + {noteValue} +
+ )} + {rawStatusMessage && hasStatusWarning && (
{rawStatusMessage} diff --git a/src/features/authFiles/components/AuthFilesPrefixProxyEditorModal.tsx b/src/features/authFiles/components/AuthFilesPrefixProxyEditorModal.tsx index 1cc6b03f..4e7b25a1 100644 --- a/src/features/authFiles/components/AuthFilesPrefixProxyEditorModal.tsx +++ b/src/features/authFiles/components/AuthFilesPrefixProxyEditorModal.tsx @@ -114,6 +114,14 @@ export function AuthFilesPrefixProxyEditorModal(props: AuthFilesPrefixProxyEdito disabled={disableControls || editor.saving || !editor.json} onChange={(e) => onChange('disableCooling', e.target.value)} /> + onChange('note', e.target.value)} + /> {editor.isCodexFile && (
diff --git a/src/features/authFiles/hooks/useAuthFilesPrefixProxyEditor.ts b/src/features/authFiles/hooks/useAuthFilesPrefixProxyEditor.ts index e99c5349..75914836 100644 --- a/src/features/authFiles/hooks/useAuthFilesPrefixProxyEditor.ts +++ b/src/features/authFiles/hooks/useAuthFilesPrefixProxyEditor.ts @@ -18,7 +18,8 @@ export type PrefixProxyEditorField = | 'priority' | 'excludedModelsText' | 'disableCooling' - | 'websocket'; + | 'websocket' + | 'note'; export type PrefixProxyEditorFieldValue = string | boolean; @@ -37,6 +38,7 @@ export type PrefixProxyEditorState = { excludedModelsText: string; disableCooling: string; websocket: boolean; + note: string; }; export type UseAuthFilesPrefixProxyEditorOptions = { @@ -93,6 +95,13 @@ const buildPrefixProxyUpdatedText = (editor: PrefixProxyEditorState | null): str next.websocket = editor.websocket; } + const noteValue = editor.note.trim(); + if (noteValue) { + next.note = noteValue; + } else if ('note' in next) { + delete next.note; + } + return JSON.stringify(next); }; @@ -146,6 +155,7 @@ export function useAuthFilesPrefixProxyEditor( excludedModelsText: '', disableCooling: '', websocket: false, + note: '', }); try { @@ -195,6 +205,7 @@ export function useAuthFilesPrefixProxyEditor( const excludedModels = normalizeExcludedModels(json.excluded_models); const disableCoolingValue = parseDisableCoolingValue(json.disable_cooling); const websocketValue = parseDisableCoolingValue(json.websocket); + const note = typeof json.note === 'string' ? json.note : ''; setPrefixProxyEditor((prev) => { if (!prev || prev.fileName !== name) return prev; @@ -211,6 +222,7 @@ export function useAuthFilesPrefixProxyEditor( disableCooling: disableCoolingValue === undefined ? '' : disableCoolingValue ? 'true' : 'false', websocket: websocketValue ?? false, + note, error: null, }; }); @@ -235,6 +247,7 @@ export function useAuthFilesPrefixProxyEditor( 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) }; return { ...prev, websocket: Boolean(value) }; }); }; diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index c6cc4eb7..9449cef4 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -495,6 +495,11 @@ "search_placeholder": "Filter by name, type, or provider", "problem_filter_label": "Problem Filter", "problem_filter_only": "Only show problematic credentials", + "sort_label": "Sort", + "sort_default": "Default", + "sort_az": "A-Z Name", + "sort_priority": "Priority", + "priority_display": "Priority", "page_size_label": "Per page", "page_size_unit": "items", "view_mode_paged": "Paged", @@ -564,6 +569,10 @@ "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.", + "note_label": "Note", + "note_placeholder": "Enter a note, e.g.: John's account", + "note_hint": "Optional. Used to describe the purpose or owner of this credential; leave empty to omit.", + "note_display": "Note", "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}}\"", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index bc7111e1..7a3ff741 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -495,6 +495,11 @@ "search_placeholder": "Фильтр по имени, типу или провайдеру", "problem_filter_label": "Фильтр проблем", "problem_filter_only": "Показывать только проблемные учётные данные", + "sort_label": "Сортировка", + "sort_default": "По умолчанию", + "sort_az": "A-Z Имя", + "sort_priority": "Приоритет", + "priority_display": "Приоритет", "page_size_label": "На странице", "page_size_unit": "элементов", "view_mode_paged": "Постранично", @@ -564,6 +569,10 @@ "disable_cooling_label": "Отключение охлаждения (disable_cooling)", "disable_cooling_placeholder": "например: true / false / 1 / 0", "disable_cooling_hint": "Поддерживает boolean, числа 0/не 0 и строки true/false/1/0; непарсируемые значения игнорируются.", + "note_label": "Заметка (note)", + "note_placeholder": "Введите заметку, например: аккаунт Ивана", + "note_hint": "Необязательно. Используется для описания назначения или владельца учётных данных; оставьте пустым, чтобы не записывать.", + "note_display": "Заметка", "prefix_proxy_invalid_json": "Этот файл авторизации не является JSON-объектом, поэтому поля нельзя редактировать.", "prefix_proxy_saved_success": "Файл авторизации \"{{name}}\" успешно обновлён", "card_tools_title": "Инструменты", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index e01a95b8..0b3c0b1f 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -495,6 +495,11 @@ "search_placeholder": "输入名称、类型或提供方关键字", "problem_filter_label": "问题筛选", "problem_filter_only": "仅显示有问题凭证", + "sort_label": "排序", + "sort_default": "默认", + "sort_az": "A-Z 名称", + "sort_priority": "优先级", + "priority_display": "优先级", "page_size_label": "单页数量", "page_size_unit": "个/页", "view_mode_paged": "按页显示", @@ -564,6 +569,10 @@ "disable_cooling_label": "禁用冷却(disable_cooling)", "disable_cooling_placeholder": "例如: true / false / 1 / 0", "disable_cooling_hint": "支持布尔值、0/非0 数字或字符串 true/false/1/0;无法解析时忽略。", + "note_label": "备注(note)", + "note_placeholder": "输入备注信息,例如:张三的账号", + "note_hint": "可选,用于标记凭证用途或归属;留空则不写入。", + "note_display": "备注", "prefix_proxy_invalid_json": "该认证文件不是 JSON 对象,无法编辑字段。", "prefix_proxy_saved_success": "已更新认证文件 \"{{name}}\"", "quota_refresh_success": "已刷新 \"{{name}}\" 的额度", diff --git a/src/pages/AuthFilesPage.module.scss b/src/pages/AuthFilesPage.module.scss index b2848932..2c484873 100644 --- a/src/pages/AuthFilesPage.module.scss +++ b/src/pages/AuthFilesPage.module.scss @@ -632,6 +632,53 @@ border-bottom: 1px solid var(--border-color); } +.priorityBadge { + display: inline-flex; + align-items: center; + gap: 4px; + font-size: 12px; + color: var(--text-secondary); + + .priorityValue { + font-weight: 600; + color: var(--text-primary); + font-variant-numeric: tabular-nums; + } +} + +.noteText { + font-size: 12px; + color: var(--text-secondary); + line-height: 1.4; + overflow: hidden; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + word-break: break-word; + + .noteLabel { + color: var(--text-tertiary); + } +} + +.sortSelect { + padding: 8px 12px; + border: 1px solid var(--border-color); + border-radius: $radius-md; + background-color: var(--bg-primary); + color: var(--text-primary); + font-size: 14px; + cursor: pointer; + height: 38px; + box-sizing: border-box; + min-width: 140px; + + &:focus { + outline: none; + border-color: var(--primary-color); + } +} + .healthStatusMessage { font-size: 12px; color: var(--warning-text); diff --git a/src/pages/AuthFilesPage.tsx b/src/pages/AuthFilesPage.tsx index cda317ba..30ec0a2e 100644 --- a/src/pages/AuthFilesPage.tsx +++ b/src/pages/AuthFilesPage.tsx @@ -31,6 +31,7 @@ import { hasAuthFileStatusMessage, isRuntimeOnlyAuthFile, normalizeProviderKey, + parsePriorityValue, type QuotaProviderType, type ResolvedTheme, } from '@/features/authFiles/constants'; @@ -74,6 +75,7 @@ export function AuthFilesPage() { const [detailModalOpen, setDetailModalOpen] = useState(false); const [selectedFile, setSelectedFile] = useState(null); const [viewMode, setViewMode] = useState<'diagram' | 'list'>('list'); + const [sortMode, setSortMode] = useState<'default' | 'az' | 'priority'>('default'); const [batchActionBarVisible, setBatchActionBarVisible] = useState(false); const floatingBatchActionsRef = useRef(null); const batchActionAnimationRef = useRef(null); @@ -281,10 +283,25 @@ export function AuthFilesPage() { }); }, [filesMatchingProblemFilter, filter, search]); - const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize)); + const sorted = useMemo(() => { + if (sortMode === 'default') return filtered; + const copy = [...filtered]; + if (sortMode === 'az') { + copy.sort((a, b) => a.name.localeCompare(b.name)); + } else if (sortMode === 'priority') { + copy.sort((a, b) => { + const pa = parsePriorityValue(a.priority ?? a['priority']) ?? 0; + const pb = parsePriorityValue(b.priority ?? b['priority']) ?? 0; + return pb - pa; // 高优先级排前面 + }); + } + return copy; + }, [filtered, sortMode]); + + const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize)); const currentPage = Math.min(page, totalPages); const start = (currentPage - 1) * pageSize; - const pageItems = filtered.slice(start, start + pageSize); + const pageItems = sorted.slice(start, start + pageSize); const selectablePageItems = useMemo( () => pageItems.filter((file) => !isRuntimeOnlyAuthFile(file)), [pageItems] @@ -559,6 +576,21 @@ export function AuthFilesPage() { }} />
+
+ + +
@@ -615,7 +647,7 @@ export function AuthFilesPage() {
)} - {!loading && filtered.length > pageSize && ( + {!loading && sorted.length > pageSize && (