mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-06-16 21:03:58 +08:00
feat(AuthFiles): add disabled filter option and enhance delete confirmation messages
This commit is contained in:
@@ -16,8 +16,10 @@ import {
|
||||
type DeleteAllOptions = {
|
||||
filter: string;
|
||||
problemOnly: boolean;
|
||||
disabledOnly: boolean;
|
||||
onResetFilterToAll: () => void;
|
||||
onResetProblemOnly: () => void;
|
||||
onResetDisabledOnly: () => void;
|
||||
};
|
||||
|
||||
export type UseAuthFilesDataResult = {
|
||||
@@ -275,17 +277,28 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles
|
||||
|
||||
const handleDeleteAll = useCallback(
|
||||
(deleteAllOptions: DeleteAllOptions) => {
|
||||
const { filter, problemOnly, onResetFilterToAll, onResetProblemOnly } = deleteAllOptions;
|
||||
const {
|
||||
filter,
|
||||
problemOnly,
|
||||
disabledOnly,
|
||||
onResetFilterToAll,
|
||||
onResetProblemOnly,
|
||||
onResetDisabledOnly,
|
||||
} = deleteAllOptions;
|
||||
const isFiltered = filter !== 'all';
|
||||
const isProblemOnly = problemOnly === true;
|
||||
const isDisabledOnly = disabledOnly === true;
|
||||
const typeLabel = isFiltered ? getTypeLabel(t, filter) : t('auth_files.filter_all');
|
||||
const confirmMessage = isProblemOnly
|
||||
? isFiltered
|
||||
let confirmMessage = t('auth_files.delete_all_confirm');
|
||||
if (isDisabledOnly) {
|
||||
confirmMessage = t('auth_files.delete_filtered_result_confirm');
|
||||
} else if (isProblemOnly) {
|
||||
confirmMessage = isFiltered
|
||||
? t('auth_files.delete_problem_filtered_confirm', { type: typeLabel })
|
||||
: t('auth_files.delete_problem_confirm')
|
||||
: isFiltered
|
||||
? t('auth_files.delete_filtered_confirm', { type: typeLabel })
|
||||
: t('auth_files.delete_all_confirm');
|
||||
: t('auth_files.delete_problem_confirm');
|
||||
} else if (isFiltered) {
|
||||
confirmMessage = t('auth_files.delete_filtered_confirm', { type: typeLabel });
|
||||
}
|
||||
|
||||
showConfirmation({
|
||||
title: t('auth_files.delete_all_title', { defaultValue: 'Delete All Files' }),
|
||||
@@ -295,7 +308,7 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles
|
||||
onConfirm: async () => {
|
||||
setDeletingAll(true);
|
||||
try {
|
||||
if (!isFiltered && !isProblemOnly) {
|
||||
if (!isFiltered && !isProblemOnly && !isDisabledOnly) {
|
||||
await authFilesApi.deleteAll();
|
||||
showNotification(t('auth_files.delete_all_success'), 'success');
|
||||
setFiles((prev) => prev.filter((file) => isRuntimeOnlyAuthFile(file)));
|
||||
@@ -305,15 +318,19 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles
|
||||
if (isRuntimeOnlyAuthFile(file)) return false;
|
||||
if (isFiltered && file.type !== filter) return false;
|
||||
if (isProblemOnly && !hasAuthFileStatusMessage(file)) return false;
|
||||
if (isDisabledOnly && file.disabled !== true) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
if (filesToDelete.length === 0) {
|
||||
const emptyMessage = isProblemOnly
|
||||
? isFiltered
|
||||
let emptyMessage = t('auth_files.delete_filtered_none', { type: typeLabel });
|
||||
if (isDisabledOnly) {
|
||||
emptyMessage = t('auth_files.delete_filtered_result_none');
|
||||
} else if (isProblemOnly) {
|
||||
emptyMessage = isFiltered
|
||||
? t('auth_files.delete_problem_filtered_none', { type: typeLabel })
|
||||
: t('auth_files.delete_problem_none')
|
||||
: t('auth_files.delete_filtered_none', { type: typeLabel });
|
||||
: t('auth_files.delete_problem_none');
|
||||
}
|
||||
showNotification(emptyMessage, 'info');
|
||||
setDeletingAll(false);
|
||||
return;
|
||||
@@ -327,7 +344,12 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles
|
||||
|
||||
applyDeletedFiles(result.files);
|
||||
|
||||
if (failed === 0 && isProblemOnly) {
|
||||
if (failed === 0 && isDisabledOnly) {
|
||||
showNotification(
|
||||
t('auth_files.delete_filtered_result_success', { count: success }),
|
||||
'success'
|
||||
);
|
||||
} else if (failed === 0 && isProblemOnly) {
|
||||
showNotification(
|
||||
isFiltered
|
||||
? t('auth_files.delete_problem_filtered_success', {
|
||||
@@ -342,6 +364,11 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles
|
||||
t('auth_files.delete_filtered_success', { count: success, type: typeLabel }),
|
||||
'success'
|
||||
);
|
||||
} else if (isDisabledOnly) {
|
||||
showNotification(
|
||||
t('auth_files.delete_filtered_result_partial', { success, failed }),
|
||||
'warning'
|
||||
);
|
||||
} else if (isProblemOnly) {
|
||||
showNotification(
|
||||
isFiltered
|
||||
@@ -366,6 +393,9 @@ export function useAuthFilesData(options: UseAuthFilesDataOptions): UseAuthFiles
|
||||
if (isProblemOnly) {
|
||||
onResetProblemOnly();
|
||||
}
|
||||
if (isDisabledOnly) {
|
||||
onResetDisabledOnly();
|
||||
}
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
const errorMessage = err instanceof Error ? err.message : '';
|
||||
|
||||
@@ -5,6 +5,7 @@ export type AuthFilesSortMode = (typeof AUTH_FILES_SORT_MODES)[number];
|
||||
export type AuthFilesUiState = {
|
||||
filter?: string;
|
||||
problemOnly?: boolean;
|
||||
disabledOnly?: boolean;
|
||||
compactMode?: boolean;
|
||||
search?: string;
|
||||
page?: number;
|
||||
|
||||
@@ -501,6 +501,8 @@
|
||||
"delete_problem_button_with_type": "Delete Problematic {{type}} Files",
|
||||
"delete_problem_confirm": "Are you sure you want to delete all problematic auth files? This operation cannot be undone!",
|
||||
"delete_problem_filtered_confirm": "Are you sure you want to delete all problematic {{type}} auth files? This operation cannot be undone!",
|
||||
"delete_filtered_result_button": "Delete filtered results",
|
||||
"delete_filtered_result_confirm": "Are you sure you want to delete auth files in the current filtered results? This operation cannot be undone!",
|
||||
"upload_error_json": "Only JSON files are allowed",
|
||||
"upload_error_size": "File size cannot exceed {{maxSize}}",
|
||||
"upload_success": "File uploaded successfully",
|
||||
@@ -516,6 +518,9 @@
|
||||
"delete_problem_filtered_partial": "Problematic {{type}} auth files deletion finished: {{success}} succeeded, {{failed}} failed",
|
||||
"delete_problem_none": "No deletable problematic auth files under the current filter",
|
||||
"delete_problem_filtered_none": "No deletable problematic {{type}} auth files under the current filter",
|
||||
"delete_filtered_result_success": "Deleted {{count}} auth files from the filtered results successfully",
|
||||
"delete_filtered_result_partial": "Filtered result deletion finished: {{success}} succeeded, {{failed}} failed",
|
||||
"delete_filtered_result_none": "No deletable auth files in the current filtered results",
|
||||
"files_count": "files",
|
||||
"pagination_prev": "Previous",
|
||||
"pagination_next": "Next",
|
||||
@@ -524,6 +529,7 @@
|
||||
"search_placeholder": "Filter by name, type, or provider. Use * as a wildcard",
|
||||
"problem_filter_label": "Problem Filter",
|
||||
"problem_filter_only": "Only show problematic credentials",
|
||||
"disabled_filter_only": "Only show disabled credentials",
|
||||
"display_options_label": "Display options",
|
||||
"compact_mode_label": "Compact mode",
|
||||
"sort_label": "Sort",
|
||||
|
||||
@@ -501,6 +501,8 @@
|
||||
"delete_problem_button_with_type": "Удалить проблемные файлы {{type}}",
|
||||
"delete_problem_confirm": "Удалить все проблемные файлы авторизации? Это действие нельзя отменить!",
|
||||
"delete_problem_filtered_confirm": "Удалить все проблемные файлы авторизации {{type}}? Это действие нельзя отменить!",
|
||||
"delete_filtered_result_button": "Удалить результаты фильтра",
|
||||
"delete_filtered_result_confirm": "Удалить файлы авторизации из текущих результатов фильтра? Это действие нельзя отменить!",
|
||||
"upload_error_json": "Допустимы только файлы JSON",
|
||||
"upload_error_size": "Размер файла не может превышать {{maxSize}}",
|
||||
"upload_success": "Файл успешно загружен",
|
||||
@@ -516,6 +518,9 @@
|
||||
"delete_problem_filtered_partial": "Удаление проблемных файлов авторизации {{type}} завершено: успешных {{success}}, ошибок {{failed}}",
|
||||
"delete_problem_none": "Нет проблемных файлов авторизации для удаления при текущем фильтре",
|
||||
"delete_problem_filtered_none": "Нет проблемных файлов авторизации {{type}} для удаления при текущем фильтре",
|
||||
"delete_filtered_result_success": "Удалено файлов авторизации из результатов фильтра: {{count}}",
|
||||
"delete_filtered_result_partial": "Удаление результатов фильтра завершено: успешных {{success}}, ошибок {{failed}}",
|
||||
"delete_filtered_result_none": "Нет файлов авторизации для удаления в текущих результатах фильтра",
|
||||
"files_count": "файлов",
|
||||
"pagination_prev": "Предыдущая",
|
||||
"pagination_next": "Следующая",
|
||||
@@ -524,6 +529,7 @@
|
||||
"search_placeholder": "Фильтр по имени, типу или провайдеру, поддерживается wildcard *",
|
||||
"problem_filter_label": "Фильтр проблем",
|
||||
"problem_filter_only": "Показывать только проблемные учётные данные",
|
||||
"disabled_filter_only": "Показывать только отключённые учётные данные",
|
||||
"display_options_label": "Параметры отображения",
|
||||
"compact_mode_label": "Компактный режим",
|
||||
"sort_label": "Сортировка",
|
||||
|
||||
@@ -501,6 +501,8 @@
|
||||
"delete_problem_button_with_type": "删除 {{type}} 问题凭证",
|
||||
"delete_problem_confirm": "确定要删除所有有问题的认证文件吗?此操作不可恢复!",
|
||||
"delete_problem_filtered_confirm": "确定要删除筛选出的有问题的 {{type}} 认证文件吗?此操作不可恢复!",
|
||||
"delete_filtered_result_button": "删除筛选结果",
|
||||
"delete_filtered_result_confirm": "确定要删除当前筛选结果中的认证文件吗?此操作不可恢复!",
|
||||
"upload_error_json": "只能上传JSON文件",
|
||||
"upload_error_size": "文件大小不能超过 {{maxSize}}",
|
||||
"upload_success": "文件上传成功",
|
||||
@@ -516,6 +518,9 @@
|
||||
"delete_problem_filtered_partial": "有问题的 {{type}} 认证文件删除完成,成功 {{success}} 个,失败 {{failed}} 个",
|
||||
"delete_problem_none": "当前没有可删除的有问题认证文件",
|
||||
"delete_problem_filtered_none": "当前筛选类型 ({{type}}) 下没有可删除的有问题认证文件",
|
||||
"delete_filtered_result_success": "成功删除 {{count}} 个筛选结果中的认证文件",
|
||||
"delete_filtered_result_partial": "筛选结果删除完成,成功 {{success}} 个,失败 {{failed}} 个",
|
||||
"delete_filtered_result_none": "当前筛选结果中没有可删除的认证文件",
|
||||
"files_count": "个文件",
|
||||
"pagination_prev": "上一页",
|
||||
"pagination_next": "下一页",
|
||||
@@ -524,6 +529,7 @@
|
||||
"search_placeholder": "输入名称、类型或提供方关键字,支持 * 通配",
|
||||
"problem_filter_label": "问题筛选",
|
||||
"problem_filter_only": "仅显示有问题凭证",
|
||||
"disabled_filter_only": "仅显示已停用凭证",
|
||||
"display_options_label": "显示选项",
|
||||
"compact_mode_label": "简略模式",
|
||||
"sort_label": "排序",
|
||||
|
||||
@@ -501,6 +501,8 @@
|
||||
"delete_problem_button_with_type": "刪除 {{type}} 問題憑證",
|
||||
"delete_problem_confirm": "確定要刪除所有有問題的驗證檔案嗎?此操作無法還原!",
|
||||
"delete_problem_filtered_confirm": "確定要刪除篩選出的有問題的 {{type}} 驗證檔案嗎?此操作無法還原!",
|
||||
"delete_filtered_result_button": "刪除篩選結果",
|
||||
"delete_filtered_result_confirm": "確定要刪除目前篩選結果中的驗證檔案嗎?此操作無法還原!",
|
||||
"upload_error_json": "只能上傳 JSON 檔案",
|
||||
"upload_error_size": "檔案大小不能超過 {{maxSize}}",
|
||||
"upload_success": "檔案上傳成功",
|
||||
@@ -516,6 +518,9 @@
|
||||
"delete_problem_filtered_partial": "有問題的 {{type}} 驗證檔案刪除完成,成功 {{success}} 個,失敗 {{failed}} 個",
|
||||
"delete_problem_none": "目前沒有可刪除的有問題驗證檔案",
|
||||
"delete_problem_filtered_none": "目前篩選類型({{type}})下沒有可刪除的有問題驗證檔案",
|
||||
"delete_filtered_result_success": "成功刪除 {{count}} 個篩選結果中的驗證檔案",
|
||||
"delete_filtered_result_partial": "篩選結果刪除完成,成功 {{success}} 個,失敗 {{failed}} 個",
|
||||
"delete_filtered_result_none": "目前篩選結果中沒有可刪除的驗證檔案",
|
||||
"files_count": "個檔案",
|
||||
"pagination_prev": "上一頁",
|
||||
"pagination_next": "下一頁",
|
||||
@@ -524,6 +529,7 @@
|
||||
"search_placeholder": "輸入名稱、類型或供應方關鍵字,支援 * 萬用字元",
|
||||
"problem_filter_label": "問題篩選",
|
||||
"problem_filter_only": "僅顯示有問題憑證",
|
||||
"disabled_filter_only": "僅顯示已停用憑證",
|
||||
"display_options_label": "顯示選項",
|
||||
"compact_mode_label": "簡略模式",
|
||||
"sort_label": "排序",
|
||||
|
||||
+47
-13
@@ -88,6 +88,7 @@ export function AuthFilesPage() {
|
||||
|
||||
const [filter, setFilter] = useState<'all' | string>('all');
|
||||
const [problemOnly, setProblemOnly] = useState(false);
|
||||
const [disabledOnly, setDisabledOnly] = useState(false);
|
||||
const [compactMode, setCompactMode] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
const [page, setPage] = useState(1);
|
||||
@@ -201,6 +202,9 @@ export function AuthFilesPage() {
|
||||
if (typeof persisted.problemOnly === 'boolean') {
|
||||
setProblemOnly(persisted.problemOnly);
|
||||
}
|
||||
if (typeof persisted.disabledOnly === 'boolean') {
|
||||
setDisabledOnly(persisted.disabledOnly);
|
||||
}
|
||||
if (
|
||||
typeof persistedCompactMode !== 'boolean' &&
|
||||
typeof persisted.compactMode === 'boolean'
|
||||
@@ -243,6 +247,7 @@ export function AuthFilesPage() {
|
||||
writeAuthFilesUiState({
|
||||
filter,
|
||||
problemOnly,
|
||||
disabledOnly,
|
||||
compactMode,
|
||||
search,
|
||||
page,
|
||||
@@ -254,6 +259,7 @@ export function AuthFilesPage() {
|
||||
writePersistedAuthFilesCompactMode(compactMode);
|
||||
}, [
|
||||
compactMode,
|
||||
disabledOnly,
|
||||
filter,
|
||||
page,
|
||||
pageSize,
|
||||
@@ -354,9 +360,14 @@ export function AuthFilesPage() {
|
||||
return Array.from(types);
|
||||
}, [files]);
|
||||
|
||||
const filesMatchingProblemFilter = useMemo(
|
||||
() => (problemOnly ? files.filter(hasAuthFileStatusMessage) : files),
|
||||
[files, problemOnly]
|
||||
const filesMatchingStatusFilters = useMemo(
|
||||
() =>
|
||||
files.filter((file) => {
|
||||
if (problemOnly && !hasAuthFileStatusMessage(file)) return false;
|
||||
if (disabledOnly && file.disabled !== true) return false;
|
||||
return true;
|
||||
}),
|
||||
[disabledOnly, files, problemOnly]
|
||||
);
|
||||
|
||||
const sortOptions = useMemo(
|
||||
@@ -369,13 +380,13 @@ export function AuthFilesPage() {
|
||||
);
|
||||
|
||||
const typeCounts = useMemo(() => {
|
||||
const counts: Record<string, number> = { all: filesMatchingProblemFilter.length };
|
||||
filesMatchingProblemFilter.forEach((file) => {
|
||||
const counts: Record<string, number> = { all: filesMatchingStatusFilters.length };
|
||||
filesMatchingStatusFilters.forEach((file) => {
|
||||
if (!file.type) return;
|
||||
counts[file.type] = (counts[file.type] || 0) + 1;
|
||||
});
|
||||
return counts;
|
||||
}, [filesMatchingProblemFilter]);
|
||||
}, [filesMatchingStatusFilters]);
|
||||
|
||||
const normalizedSearch = search.trim();
|
||||
const wildcardSearch = useMemo(() => buildWildcardSearch(normalizedSearch), [normalizedSearch]);
|
||||
@@ -383,7 +394,7 @@ export function AuthFilesPage() {
|
||||
const filtered = useMemo(() => {
|
||||
const normalizedTerm = normalizedSearch.toLowerCase();
|
||||
|
||||
return filesMatchingProblemFilter.filter((item) => {
|
||||
return filesMatchingStatusFilters.filter((item) => {
|
||||
const matchType = filter === 'all' || item.type === filter;
|
||||
const matchSearch =
|
||||
!normalizedSearch ||
|
||||
@@ -395,7 +406,7 @@ export function AuthFilesPage() {
|
||||
});
|
||||
return matchType && matchSearch;
|
||||
});
|
||||
}, [filesMatchingProblemFilter, filter, normalizedSearch, wildcardSearch]);
|
||||
}, [filesMatchingStatusFilters, filter, normalizedSearch, wildcardSearch]);
|
||||
|
||||
const sorted = useMemo(() => {
|
||||
const copy = [...filtered];
|
||||
@@ -634,13 +645,19 @@ export function AuthFilesPage() {
|
||||
</div>
|
||||
);
|
||||
|
||||
const deleteAllButtonLabel = problemOnly
|
||||
? filter === 'all'
|
||||
? t('auth_files.delete_problem_button')
|
||||
: t('auth_files.delete_problem_button_with_type', { type: getTypeLabel(t, filter) })
|
||||
: filter === 'all'
|
||||
const deleteAllButtonLabel = (() => {
|
||||
if (disabledOnly) {
|
||||
return t('auth_files.delete_filtered_result_button');
|
||||
}
|
||||
if (problemOnly) {
|
||||
return filter === 'all'
|
||||
? t('auth_files.delete_problem_button')
|
||||
: t('auth_files.delete_problem_button_with_type', { type: getTypeLabel(t, filter) });
|
||||
}
|
||||
return filter === 'all'
|
||||
? t('auth_files.delete_all_button')
|
||||
: `${t('common.delete')} ${getTypeLabel(t, filter)}`;
|
||||
})();
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
@@ -671,8 +688,10 @@ export function AuthFilesPage() {
|
||||
handleDeleteAll({
|
||||
filter,
|
||||
problemOnly,
|
||||
disabledOnly,
|
||||
onResetFilterToAll: () => setFilter('all'),
|
||||
onResetProblemOnly: () => setProblemOnly(false),
|
||||
onResetDisabledOnly: () => setDisabledOnly(false),
|
||||
})
|
||||
}
|
||||
disabled={disableControls || loading || deletingAll}
|
||||
@@ -757,6 +776,21 @@ export function AuthFilesPage() {
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.filterToggleCard}>
|
||||
<ToggleSwitch
|
||||
checked={disabledOnly}
|
||||
onChange={(value) => {
|
||||
setDisabledOnly(value);
|
||||
setPage(1);
|
||||
}}
|
||||
ariaLabel={t('auth_files.disabled_filter_only')}
|
||||
label={
|
||||
<span className={styles.filterToggleLabel}>
|
||||
{t('auth_files.disabled_filter_only')}
|
||||
</span>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.filterToggleCard}>
|
||||
<ToggleSwitch
|
||||
checked={compactMode}
|
||||
|
||||
Reference in New Issue
Block a user