From e44a9fc099e8fdbd7330a80ce2715d43025f41cf Mon Sep 17 00:00:00 2001 From: Supra4E8C Date: Sun, 22 Mar 2026 14:41:57 +0800 Subject: [PATCH] feat(auth-files): improve credential cards, bulk actions, and dedupe auth file list --- src/components/ui/icons.tsx | 40 ++- .../authFiles/components/AuthFileCard.tsx | 30 +- .../authFiles/hooks/useAuthFilesData.ts | 82 ++++- src/features/authFiles/uiState.ts | 1 + src/i18n/locales/en.json | 8 + src/i18n/locales/ru.json | 8 + src/i18n/locales/zh-CN.json | 8 + src/pages/AuthFilesPage.module.scss | 303 +++++++++++++++++- src/pages/AuthFilesPage.tsx | 136 +++++--- src/services/api/authFiles.ts | 119 ++++++- 10 files changed, 670 insertions(+), 65 deletions(-) diff --git a/src/components/ui/icons.tsx b/src/components/ui/icons.tsx index 3a71f97..3f93e14 100644 --- a/src/components/ui/icons.tsx +++ b/src/components/ui/icons.tsx @@ -66,6 +66,36 @@ export function IconBot({ size = 20, ...props }: IconProps) { ); } +export function IconModelCluster({ size = 20, ...props }: IconProps) { + return ( + + + + + + + + + + ); +} + +export function IconFilterAll({ size = 20, ...props }: IconProps) { + return ( + + + + + + + + + + + + ); +} + export function IconFileText({ size = 20, ...props }: IconProps) { return ( @@ -404,7 +434,15 @@ export function IconSidebarUsage({ size = 20, ...props }: IconProps) { - + ); diff --git a/src/features/authFiles/components/AuthFileCard.tsx b/src/features/authFiles/components/AuthFileCard.tsx index 1bdf3f2..ed67182 100644 --- a/src/features/authFiles/components/AuthFileCard.tsx +++ b/src/features/authFiles/components/AuthFileCard.tsx @@ -4,11 +4,10 @@ import { LoadingSpinner } from '@/components/ui/LoadingSpinner'; import { SelectionCheckbox } from '@/components/ui/SelectionCheckbox'; import { ToggleSwitch } from '@/components/ui/ToggleSwitch'; import { - IconBot, IconDownload, IconInfo, + IconModelCluster, IconSettings, - IconShield, IconTrash2, } from '@/components/ui/icons'; import { ProviderStatusBar } from '@/components/providers/ProviderStatusBar'; @@ -37,6 +36,7 @@ const HEALTHY_STATUS_MESSAGES = new Set(['ok', 'healthy', 'ready', 'success', 'a export type AuthFileCardProps = { file: AuthFileItem; + compact: boolean; selected: boolean; resolvedTheme: ResolvedTheme; disableControls: boolean; @@ -63,6 +63,7 @@ export function AuthFileCard(props: AuthFileCardProps) { const { t } = useTranslation(); const { file, + compact, selected, resolvedTheme, disableControls, @@ -90,7 +91,7 @@ export function AuthFileCard(props: AuthFileCardProps) { const quotaType = quotaFilterType && resolveQuotaType(file) === quotaFilterType ? quotaFilterType : null; - const showQuotaLayout = Boolean(quotaType) && !isRuntimeOnly; + const showQuotaLayout = Boolean(quotaType) && !isRuntimeOnly && !compact; const providerCardClass = quotaType === 'antigravity' @@ -134,7 +135,7 @@ export function AuthFileCard(props: AuthFileCardProps) { return (
@@ -180,8 +181,10 @@ export function AuthFileCard(props: AuthFileCardProps) { {stateLabel}
- {file.name} - {noteValue && ( + + {file.name} + + {!compact && noteValue && (
{t('auth_files.note_display')} {noteValue} @@ -190,7 +193,7 @@ export function AuthFileCard(props: AuthFileCardProps) {
-
+
{t('auth_files.file_size')} @@ -218,8 +221,8 @@ export function AuthFileCard(props: AuthFileCardProps) {
)} -
-
+
+
{t('stats.success')} {fileStats.success} @@ -230,9 +233,8 @@ export function AuthFileCard(props: AuthFileCardProps) {
-
+
- {t('auth_files.health_status_label')}
@@ -254,12 +256,14 @@ export function AuthFileCard(props: AuthFileCardProps) { variant="secondary" size="sm" onClick={() => onShowModels(file)} - className={styles.primaryActionButton} + className={`${styles.primaryActionButton} ${styles.modelsActionButton}`} title={t('auth_files.models_button', { defaultValue: '模型' })} disabled={disableControls} > <> - + + + {t('auth_files.models_button', { defaultValue: '模型' })} diff --git a/src/features/authFiles/hooks/useAuthFilesData.ts b/src/features/authFiles/hooks/useAuthFilesData.ts index 1ad410d..571397c 100644 --- a/src/features/authFiles/hooks/useAuthFilesData.ts +++ b/src/features/authFiles/hooks/useAuthFilesData.ts @@ -40,7 +40,9 @@ export type UseAuthFilesDataResult = { handleStatusToggle: (item: AuthFileItem, enabled: boolean) => Promise; toggleSelect: (name: string) => void; selectAllVisible: (visibleFiles: AuthFileItem[]) => void; + invertVisibleSelection: (visibleFiles: AuthFileItem[]) => void; deselectAll: () => void; + batchDownload: (names: string[]) => Promise; batchSetStatus: (names: string[], enabled: boolean) => Promise; batchDelete: (names: string[]) => void; }; @@ -81,7 +83,31 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles const nextSelected = visibleFiles .filter((file) => !isRuntimeOnlyAuthFile(file)) .map((file) => file.name); - setSelectedFiles(new Set(nextSelected)); + if (nextSelected.length === 0) return; + setSelectedFiles((prev) => { + const next = new Set(prev); + nextSelected.forEach((name) => next.add(name)); + return next; + }); + }, []); + + const invertVisibleSelection = useCallback((visibleFiles: AuthFileItem[]) => { + const visibleNames = visibleFiles + .filter((file) => !isRuntimeOnlyAuthFile(file)) + .map((file) => file.name); + if (visibleNames.length === 0) return; + + setSelectedFiles((prev) => { + const next = new Set(prev); + visibleNames.forEach((name) => { + if (next.has(name)) { + next.delete(name); + } else { + next.add(name); + } + }); + return next; + }); }, []); const deselectAll = useCallback(() => { @@ -220,7 +246,7 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles } finally { setDeleting(null); } - } + }, }); }, [showConfirmation, showNotification, t] @@ -348,7 +374,7 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles } finally { setDeletingAll(false); } - } + }, }); }, [deselectAll, files, showConfirmation, showNotification, t] @@ -470,6 +496,43 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles [deselectAll, showNotification, t] ); + const batchDownload = useCallback( + async (names: string[]) => { + const uniqueNames = Array.from(new Set(names)); + if (uniqueNames.length === 0) return; + + let successCount = 0; + let failCount = 0; + + for (const name of uniqueNames) { + try { + const response = await apiClient.getRaw( + `/auth-files/download?name=${encodeURIComponent(name)}`, + { responseType: 'blob' } + ); + const blob = new Blob([response.data]); + downloadBlob({ filename: name, blob }); + successCount++; + } catch { + failCount++; + } + } + + if (failCount === 0) { + showNotification( + t('auth_files.batch_download_success', { count: successCount }), + 'success' + ); + } else { + showNotification( + t('auth_files.batch_download_partial', { success: successCount, failed: failCount }), + 'warning' + ); + } + }, + [showNotification, t] + ); + const batchDelete = useCallback( (names: string[]) => { const uniqueNames = Array.from(new Set(names)); @@ -516,18 +579,21 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles }); if (failCount === 0) { - showNotification(`${t('auth_files.delete_all_success')} (${deleted.length})`, 'success'); + showNotification( + `${t('auth_files.delete_all_success')} (${deleted.length})`, + 'success' + ); } else { showNotification( t('auth_files.delete_filtered_partial', { success: deleted.length, failed: failCount, - type: t('auth_files.filter_all') + type: t('auth_files.filter_all'), }), 'warning' ); } - } + }, }); }, [showConfirmation, showNotification, t] @@ -553,8 +619,10 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles handleStatusToggle, toggleSelect, selectAllVisible, + invertVisibleSelection, deselectAll, + batchDownload, batchSetStatus, - batchDelete + batchDelete, }; } diff --git a/src/features/authFiles/uiState.ts b/src/features/authFiles/uiState.ts index edc4c28..fd9ca4d 100644 --- a/src/features/authFiles/uiState.ts +++ b/src/features/authFiles/uiState.ts @@ -5,6 +5,7 @@ export type AuthFilesSortMode = (typeof AUTH_FILES_SORT_MODES)[number]; export type AuthFilesUiState = { filter?: string; problemOnly?: boolean; + compactMode?: boolean; search?: string; page?: number; pageSize?: number; diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 3714b14..4280366 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -495,6 +495,8 @@ "search_placeholder": "Filter by name, type, or provider", "problem_filter_label": "Problem Filter", "problem_filter_only": "Only show problematic credentials", + "display_options_label": "Display options", + "compact_mode_label": "Compact mode", "sort_label": "Sort", "sort_default": "Default", "sort_az": "A-Z Name", @@ -549,7 +551,13 @@ "batch_delete_confirm": "Are you sure you want to delete {{count}} files?", "batch_selected": "{{count}} selected", "batch_select_all": "Select All", + "batch_select_page": "Select page", + "batch_select_filtered": "Select filtered", + "batch_invert_page": "Invert page", "batch_deselect": "Deselect", + "batch_download": "Download selected", + "batch_download_success": "Started downloading {{count}} files", + "batch_download_partial": "Download finished: {{success}} succeeded, {{failed}} failed", "batch_enable": "Enable", "batch_disable": "Disable", "prefix_proxy_button": "Auth File Details / Edit", diff --git a/src/i18n/locales/ru.json b/src/i18n/locales/ru.json index d1b34bd..bd5a43d 100644 --- a/src/i18n/locales/ru.json +++ b/src/i18n/locales/ru.json @@ -495,6 +495,8 @@ "search_placeholder": "Фильтр по имени, типу или провайдеру", "problem_filter_label": "Фильтр проблем", "problem_filter_only": "Показывать только проблемные учётные данные", + "display_options_label": "Параметры отображения", + "compact_mode_label": "Компактный режим", "sort_label": "Сортировка", "sort_default": "По умолчанию", "sort_az": "A-Z Имя", @@ -549,7 +551,13 @@ "batch_delete_confirm": "Удалить {{count}} файлов?", "batch_selected": "{{count}} выбрано", "batch_select_all": "Выбрать все", + "batch_select_page": "Выбрать страницу", + "batch_select_filtered": "Выбрать по фильтру", + "batch_invert_page": "Инвертировать страницу", "batch_deselect": "Отменить", + "batch_download": "Скачать выбранные", + "batch_download_success": "Запущена загрузка {{count}} файлов", + "batch_download_partial": "Загрузка завершена: успешно {{success}}, ошибок {{failed}}", "batch_enable": "Включить", "batch_disable": "Отключить", "prefix_proxy_button": "Просмотр / редактирование файла авторизации", diff --git a/src/i18n/locales/zh-CN.json b/src/i18n/locales/zh-CN.json index 3428d10..f7af907 100644 --- a/src/i18n/locales/zh-CN.json +++ b/src/i18n/locales/zh-CN.json @@ -495,6 +495,8 @@ "search_placeholder": "输入名称、类型或提供方关键字", "problem_filter_label": "问题筛选", "problem_filter_only": "仅显示有问题凭证", + "display_options_label": "显示选项", + "compact_mode_label": "简略模式", "sort_label": "排序", "sort_default": "默认", "sort_az": "A-Z 名称", @@ -549,7 +551,13 @@ "batch_delete_confirm": "确定要删除 {{count}} 个文件吗?", "batch_selected": "已选 {{count}} 项", "batch_select_all": "全选", + "batch_select_page": "全选当前页", + "batch_select_filtered": "全选筛选结果", + "batch_invert_page": "反选当前页", "batch_deselect": "取消选择", + "batch_download": "下载选中", + "batch_download_success": "已开始下载 {{count}} 个文件", + "batch_download_partial": "下载完成:成功 {{success}} 个,失败 {{failed}} 个", "batch_enable": "启用", "batch_disable": "禁用", "prefix_proxy_button": "认证文件详情 / 编辑", diff --git a/src/pages/AuthFilesPage.module.scss b/src/pages/AuthFilesPage.module.scss index 5953d38..f7debdd 100644 --- a/src/pages/AuthFilesPage.module.scss +++ b/src/pages/AuthFilesPage.module.scss @@ -160,6 +160,45 @@ color: var(--text-primary); } +.filterAllIconWrap { + position: relative; + overflow: hidden; + border-color: color-mix(in srgb, var(--primary-color) 20%, var(--border-color)); + background: + radial-gradient( + circle at 30% 28%, + color-mix(in srgb, var(--primary-color) 22%, #ffffff), + transparent 58% + ), + linear-gradient( + 145deg, + color-mix(in srgb, var(--bg-secondary) 94%, var(--primary-color) 8%), + color-mix(in srgb, var(--bg-primary) 92%, var(--primary-color) 5%) + ); + box-shadow: + inset 0 1px 0 color-mix(in srgb, #fff 54%, transparent), + 0 10px 22px -18px color-mix(in srgb, var(--primary-color) 50%, transparent); +} + +.filterAllIconWrap::after { + content: ''; + position: absolute; + inset: auto 7px 7px auto; + width: 10px; + height: 10px; + border-radius: 999px; + background: color-mix(in srgb, var(--primary-color) 18%, transparent); + filter: blur(1px); + opacity: 0.9; +} + +.filterAllIcon { + position: relative; + z-index: 1; + display: block; + color: color-mix(in srgb, var(--primary-color) 70%, var(--text-primary)); +} + .filterRailHeroText { display: flex; flex-direction: column; @@ -189,9 +228,8 @@ } .filterRailCount { - display: inline-flex; - align-items: center; - justify-content: center; + display: inline-grid; + place-items: center; min-width: 38px; height: 30px; padding: 0 10px; @@ -201,6 +239,8 @@ font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; + line-height: 1; + text-align: center; } .filterRailMode { @@ -333,9 +373,8 @@ } .filterTagCount { - display: inline-flex; - align-items: center; - justify-content: center; + display: inline-grid; + place-items: center; min-width: 34px; height: 28px; padding: 0 10px; @@ -346,6 +385,8 @@ font-weight: 700; font-variant-numeric: tabular-nums; flex: 0 0 auto; + line-height: 1; + text-align: center; } .filterTagActive .filterTagCount { @@ -411,6 +452,23 @@ } } +.filterToggleGroup { + display: grid; + grid-template-columns: 1fr; + gap: 8px; + min-height: 40px; +} + +.filterToggleCard { + display: flex; + align-items: center; + min-height: 40px; + padding: 0 2px; + border-radius: 12px; + border: 1px solid color-mix(in srgb, var(--border-color) 82%, transparent); + background: color-mix(in srgb, var(--bg-secondary) 90%, transparent); +} + .filterToggle { display: flex; align-items: center; @@ -473,6 +531,18 @@ } } +.fileGridCompact { + grid-template-columns: repeat(4, minmax(0, 1fr)); + + @include tablet { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + @include mobile { + grid-template-columns: 1fr; + } +} + .antigravityGrid { display: grid; gap: $spacing-md; @@ -840,6 +910,108 @@ } } +.fileCardCompact { + padding: 14px; + border-radius: 14px; + + .fileCardMain { + gap: 10px; + } + + .cardHeader { + gap: 10px; + } + + .cardSelection { + margin-top: 7px; + } + + .providerAvatar { + width: 38px; + height: 38px; + border-radius: 12px; + } + + .providerAvatarImage { + width: 18px; + height: 18px; + } + + .providerAvatarFallback { + font-size: 16px; + } + + .cardHeaderContent { + gap: 4px; + } + + .cardBadgeRow { + gap: 6px; + } + + .typeBadge, + .stateBadge { + padding: 4px 8px; + } + + .fileName { + font-size: 14px; + line-height: 1.35; + -webkit-line-clamp: 1; + } + + .healthStatusMessage { + padding: 8px 10px; + border-radius: 10px; + font-size: 11px; + } + + .quotaSection { + display: none; + } + + .cardActions { + gap: 8px; + padding-top: 0; + } + + .cardActionsMain { + width: 100%; + gap: 8px; + flex-wrap: nowrap; + } + + .primaryActionButton:global(.btn.btn-sm) { + flex: 1 1 auto; + min-width: 0; + padding-inline: 10px; + } + + .cardUtilityActions { + gap: 4px; + padding: 3px; + border-radius: 10px; + } + + .iconButton:global(.btn.btn-sm) { + width: 34px; + height: 34px; + min-width: 34px; + border-radius: 9px; + } + + .statusToggle { + width: 100%; + justify-content: space-between; + gap: 8px; + padding: 5px 8px; + } + + .statusToggleLabel { + font-size: 11px; + } +} + .fileCardLayout { display: flex; align-items: stretch; @@ -976,6 +1148,32 @@ } } +.cardMetaCompact { + display: flex; + flex-wrap: wrap; + gap: 8px; +} + +.cardMetaCompact .metaItem { + flex-direction: row; + align-items: center; + gap: 6px; + padding: 7px 10px; + border-radius: 999px; + flex: 0 1 auto; +} + +.cardMetaCompact .metaLabel { + font-size: 10px; + white-space: nowrap; +} + +.cardMetaCompact .metaValue { + font-size: 12px; + line-height: 1.2; + white-space: nowrap; +} + .metaItem { display: flex; flex-direction: column; @@ -1066,6 +1264,12 @@ background: color-mix(in srgb, var(--bg-secondary) 88%, transparent); } +.cardInsightsCompact { + gap: 10px; + padding: 10px 12px; + border-radius: 14px; +} + .cardStats { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -1076,6 +1280,26 @@ } } +.cardStatsCompact { + gap: 8px; +} + +.cardStatsCompact .statPill { + flex-direction: row; + align-items: center; + justify-content: space-between; + gap: 10px; + padding: 8px 10px; +} + +.cardStatsCompact .statLabel { + font-size: 11px; +} + +.cardStatsCompact .statValue { + font-size: 18px; +} + .statPill { display: flex; flex-direction: column; @@ -1117,6 +1341,23 @@ gap: 8px; } +.statusPanelCompact { + gap: 6px; +} + +.statusPanelCompact .statusPanelLabel { + display: none; +} + +.statusPanelCompact .statusBar { + gap: 8px; +} + +.statusPanelCompact .statusRate { + padding: 5px 8px; + font-size: 11px; +} + .statusPanelLabel { display: inline-flex; align-items: center; @@ -1394,6 +1635,42 @@ padding-inline: 12px; } +.modelsActionButton:global(.btn.btn-sm) { + background: linear-gradient( + 135deg, + color-mix(in srgb, var(--primary-color) 15%, var(--bg-secondary)), + color-mix(in srgb, var(--primary-color) 7%, var(--bg-primary)) + ); + border-color: color-mix(in srgb, var(--primary-color) 24%, var(--border-color)); +} + +.modelsActionButton:global(.btn.btn-sm):hover { + border-color: color-mix(in srgb, var(--primary-color) 38%, var(--border-color)); + background: linear-gradient( + 135deg, + color-mix(in srgb, var(--primary-color) 18%, var(--bg-secondary)), + color-mix(in srgb, var(--primary-color) 9%, var(--bg-primary)) + ); +} + +.modelsActionButton:global(.btn.btn-sm) > span { + display: inline-flex; + align-items: center; + gap: 8px; + min-width: 0; +} + +.modelsActionIconWrap { + width: 22px; + height: 22px; + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + border-radius: 8px; + background: color-mix(in srgb, var(--primary-color) 16%, transparent); +} + .actionButtonLabel { @include text-ellipsis; } @@ -1461,6 +1738,20 @@ width: 100%; justify-content: space-between; } + + .fileCardCompact { + .cardActionsMain { + flex-wrap: wrap; + } + + .primaryActionButton:global(.btn.btn-sm) { + flex: 1 1 auto; + } + + .cardUtilityActions { + margin-left: auto; + } + } } // 分页 diff --git a/src/pages/AuthFilesPage.tsx b/src/pages/AuthFilesPage.tsx index d0d91b9..15943db 100644 --- a/src/pages/AuthFilesPage.tsx +++ b/src/pages/AuthFilesPage.tsx @@ -20,6 +20,7 @@ import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; import { Input } from '@/components/ui/Input'; import { Select } from '@/components/ui/Select'; +import { IconFilterAll } from '@/components/ui/icons'; import { EmptyState } from '@/components/ui/EmptyState'; import { ToggleSwitch } from '@/components/ui/ToggleSwitch'; import { copyToClipboard } from '@/utils/clipboard'; @@ -74,6 +75,7 @@ export function AuthFilesPage() { const [filter, setFilter] = useState<'all' | string>('all'); const [problemOnly, setProblemOnly] = useState(false); + const [compactMode, setCompactMode] = useState(false); const [search, setSearch] = useState(''); const [page, setPage] = useState(1); const [pageSize, setPageSize] = useState(9); @@ -107,7 +109,9 @@ export function AuthFilesPage() { handleStatusToggle, toggleSelect, selectAllVisible, + invertVisibleSelection, deselectAll, + batchDownload, batchSetStatus, batchDelete, } = useAuthFilesData({ refreshKeyStats }); @@ -174,6 +178,9 @@ export function AuthFilesPage() { if (typeof persisted.problemOnly === 'boolean') { setProblemOnly(persisted.problemOnly); } + if (typeof persisted.compactMode === 'boolean') { + setCompactMode(persisted.compactMode); + } if (typeof persisted.search === 'string') { setSearch(persisted.search); } @@ -189,8 +196,8 @@ export function AuthFilesPage() { }, []); useEffect(() => { - writeAuthFilesUiState({ filter, problemOnly, search, page, pageSize, sortMode }); - }, [filter, problemOnly, search, page, pageSize, sortMode]); + writeAuthFilesUiState({ filter, problemOnly, compactMode, search, page, pageSize, sortMode }); + }, [filter, problemOnly, compactMode, search, page, pageSize, sortMode]); useEffect(() => { setPageSizeInput(String(pageSize)); @@ -344,6 +351,10 @@ export function AuthFilesPage() { () => pageItems.filter((file) => !isRuntimeOnlyAuthFile(file)), [pageItems] ); + const selectableFilteredItems = useMemo( + () => sorted.filter((file) => !isRuntimeOnlyAuthFile(file)), + [sorted] + ); const selectedNames = useMemo(() => Array.from(selectedFiles), [selectedFiles]); const copyTextWithNotification = useCallback( @@ -487,15 +498,21 @@ export function AuthFilesPage() {
-
- {activeFilterIcon ? ( - - ) : ( - - {activeFilterLabel.slice(0, 1).toUpperCase()} - - )} -
+ {filter === 'all' ? ( + + + + ) : ( +
+ {activeFilterIcon ? ( + + ) : ( + + {activeFilterLabel.slice(0, 1).toUpperCase()} + + )} +
+ )}
{activeFilterLabel} {t('auth_files.title_section')} @@ -535,15 +552,21 @@ export function AuthFilesPage() { }} > - - {iconSrc ? ( - - ) : ( - - {getTypeLabel(t, type).slice(0, 1).toUpperCase()} - - )} - + {type === 'all' ? ( + + + + ) : ( + + {iconSrc ? ( + + ) : ( + + {getTypeLabel(t, type).slice(0, 1).toUpperCase()} + + )} + + )} {getTypeLabel(t, type)} {typeCounts[type] ?? 0} @@ -667,21 +690,35 @@ export function AuthFilesPage() { />
- -
- { - setProblemOnly(value); - setPage(1); - }} - ariaLabel={t('auth_files.problem_filter_only')} - label={ - - {t('auth_files.problem_filter_only')} - - } - /> + +
+
+ { + setProblemOnly(value); + setPage(1); + }} + ariaLabel={t('auth_files.problem_filter_only')} + label={ + + {t('auth_files.problem_filter_only')} + + } + /> +
+
+ setCompactMode(value)} + ariaLabel={t('auth_files.compact_mode_label')} + label={ + + {t('auth_files.compact_mode_label')} + + } + /> +
@@ -696,12 +733,13 @@ export function AuthFilesPage() { /> ) : (
{pageItems.map((file) => ( selectAllVisible(pageItems)} disabled={selectablePageItems.length === 0} > - {t('auth_files.batch_select_all')} + {t('auth_files.batch_select_page')} + + +
+