Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/features/authFiles/components/AuthFileCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
getTypeColor,
getTypeLabel,
isRuntimeOnlyAuthFile,
parsePriorityValue,
resolveAuthFileStats,
type QuotaProviderType,
type ResolvedTheme,
Expand Down Expand Up @@ -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']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The expression file.priority ?? file['priority'] is redundant. Accessing a property with dot notation (file.priority) is equivalent to using bracket notation (file['priority']) for a valid identifier. You can simplify this for better readability.

Suggested change
const priorityValue = parsePriorityValue(file.priority ?? file['priority']);
const priorityValue = parsePriorityValue(file.priority);

const noteValue = typeof file.note === 'string' ? file.note.trim() : '';

return (
<div
className={`${styles.fileCard} ${providerCardClass} ${selected ? styles.fileCardSelected : ''} ${file.disabled ? styles.fileCardDisabled : ''}`}
Expand Down Expand Up @@ -151,8 +155,20 @@ export function AuthFileCard(props: AuthFileCardProps) {
<span>
{t('auth_files.file_modified')}: {formatModified(file)}
</span>
{priorityValue !== undefined && (
<span className={styles.priorityBadge}>
{t('auth_files.priority_display')}: <span className={styles.priorityValue}>{priorityValue}</span>
</span>
)}
</div>

{noteValue && (
<div className={styles.noteText} title={noteValue}>
<span className={styles.noteLabel}>{t('auth_files.note_display')}: </span>
{noteValue}
</div>
)}

{rawStatusMessage && hasStatusWarning && (
<div className={styles.healthStatusMessage} title={rawStatusMessage}>
{rawStatusMessage}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ export function AuthFilesPrefixProxyEditorModal(props: AuthFilesPrefixProxyEdito
disabled={disableControls || editor.saving || !editor.json}
onChange={(e) => onChange('disableCooling', e.target.value)}
/>
<Input
label={t('auth_files.note_label')}
value={editor.note}
placeholder={t('auth_files.note_placeholder')}
hint={t('auth_files.note_hint')}
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>
Expand Down
15 changes: 14 additions & 1 deletion src/features/authFiles/hooks/useAuthFilesPrefixProxyEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export type PrefixProxyEditorField =
| 'priority'
| 'excludedModelsText'
| 'disableCooling'
| 'websocket';
| 'websocket'
| 'note';

export type PrefixProxyEditorFieldValue = string | boolean;

Expand All @@ -37,6 +38,7 @@ export type PrefixProxyEditorState = {
excludedModelsText: string;
disableCooling: string;
websocket: boolean;
note: string;
};

export type UseAuthFilesPrefixProxyEditorOptions = {
Expand Down Expand Up @@ -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);
};

Expand Down Expand Up @@ -146,6 +155,7 @@ export function useAuthFilesPrefixProxyEditor(
excludedModelsText: '',
disableCooling: '',
websocket: false,
note: '',
});

try {
Expand Down Expand Up @@ -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;
Expand All @@ -211,6 +222,7 @@ export function useAuthFilesPrefixProxyEditor(
disableCooling:
disableCoolingValue === undefined ? '' : disableCoolingValue ? 'true' : 'false',
websocket: websocketValue ?? false,
note,
error: null,
};
});
Expand All @@ -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) };
});
};
Expand Down
9 changes: 9 additions & 0 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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}}\"",
Expand Down
9 changes: 9 additions & 0 deletions src/i18n/locales/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "Постранично",
Expand Down Expand Up @@ -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": "Инструменты",
Expand Down
9 changes: 9 additions & 0 deletions src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "按页显示",
Expand Down Expand Up @@ -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}}\" 的额度",
Expand Down
47 changes: 47 additions & 0 deletions src/pages/AuthFilesPage.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
40 changes: 36 additions & 4 deletions src/pages/AuthFilesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
hasAuthFileStatusMessage,
isRuntimeOnlyAuthFile,
normalizeProviderKey,
parsePriorityValue,
type QuotaProviderType,
type ResolvedTheme,
} from '@/features/authFiles/constants';
Expand Down Expand Up @@ -74,6 +75,7 @@ export function AuthFilesPage() {
const [detailModalOpen, setDetailModalOpen] = useState(false);
const [selectedFile, setSelectedFile] = useState<AuthFileItem | null>(null);
const [viewMode, setViewMode] = useState<'diagram' | 'list'>('list');
const [sortMode, setSortMode] = useState<'default' | 'az' | 'priority'>('default');
const [batchActionBarVisible, setBatchActionBarVisible] = useState(false);
const floatingBatchActionsRef = useRef<HTMLDivElement>(null);
const batchActionAnimationRef = useRef<AnimationPlaybackControlsWithThen | null>(null);
Expand Down Expand Up @@ -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;
Comment on lines +293 to +294
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The expressions a.priority ?? a['priority'] and b.priority ?? b['priority'] are redundant. Accessing a property with dot notation is equivalent to bracket notation for valid identifiers. You can simplify this for better readability.

Suggested change
const pa = parsePriorityValue(a.priority ?? a['priority']) ?? 0;
const pb = parsePriorityValue(b.priority ?? b['priority']) ?? 0;
const pa = parsePriorityValue(a.priority) ?? 0;
const pb = parsePriorityValue(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]
Expand Down Expand Up @@ -559,6 +576,21 @@ export function AuthFilesPage() {
}}
/>
</div>
<div className={styles.filterItem}>
<label>{t('auth_files.sort_label')}</label>
<select
className={styles.sortSelect}
value={sortMode}
onChange={(e) => {
setSortMode(e.target.value as 'default' | 'az' | 'priority');
setPage(1);
}}
>
<option value="default">{t('auth_files.sort_default')}</option>
<option value="az">{t('auth_files.sort_az')}</option>
<option value="priority">{t('auth_files.sort_priority')}</option>
</select>
</div>
<div className={`${styles.filterItem} ${styles.filterToggleItem}`}>
<label>{t('auth_files.problem_filter_label')}</label>
<div className={styles.filterToggle}>
Expand Down Expand Up @@ -615,7 +647,7 @@ export function AuthFilesPage() {
</div>
)}

{!loading && filtered.length > pageSize && (
{!loading && sorted.length > pageSize && (
<div className={styles.pagination}>
<Button
variant="secondary"
Expand All @@ -629,7 +661,7 @@ export function AuthFilesPage() {
{t('auth_files.pagination_info', {
current: currentPage,
total: totalPages,
count: filtered.length,
count: sorted.length,
})}
</div>
<Button
Expand Down