mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
feat: improve error handling and localization for vault operations and import/export processes
This commit is contained in:
+26
-23
@@ -110,24 +110,27 @@ function summarizeImportResult(
|
||||
ciphers: Array<Record<string, unknown>>,
|
||||
folderCount: number
|
||||
): ImportResultSummary {
|
||||
const counter = new Map<string, number>();
|
||||
const typeLabel = (type: number): string => {
|
||||
if (type === 1) return '登录';
|
||||
if (type === 2) return '安全备注';
|
||||
if (type === 3) return '卡片';
|
||||
if (type === 4) return '身份';
|
||||
if (type === 5) return 'SSH 密钥';
|
||||
return '其他';
|
||||
if (type === 1) return t('txt_login');
|
||||
if (type === 2) return t('txt_secure_note');
|
||||
if (type === 3) return t('txt_card');
|
||||
if (type === 4) return t('txt_identity');
|
||||
if (type === 5) return t('txt_ssh_key');
|
||||
return t('txt_other');
|
||||
};
|
||||
const counter = new Map<number, number>();
|
||||
for (const raw of ciphers) {
|
||||
const t = Number(raw?.type || 1) || 1;
|
||||
const label = typeLabel(t);
|
||||
counter.set(label, (counter.get(label) || 0) + 1);
|
||||
const cipherType = Number(raw?.type || 1) || 1;
|
||||
counter.set(cipherType, (counter.get(cipherType) || 0) + 1);
|
||||
}
|
||||
const order = ['登录', '安全备注', '卡片', '身份', 'SSH 密钥', '其他'];
|
||||
const order = [1, 2, 3, 4, 5];
|
||||
const seen = new Set<number>(order);
|
||||
const typeCounts = order
|
||||
.filter((label) => (counter.get(label) || 0) > 0)
|
||||
.map((label) => ({ label, count: counter.get(label) || 0 }));
|
||||
.filter((type) => (counter.get(type) || 0) > 0)
|
||||
.map((type) => ({ label: typeLabel(type), count: counter.get(type) || 0 }));
|
||||
for (const [type, count] of counter.entries()) {
|
||||
if (!seen.has(type) && count > 0) typeCounts.push({ label: typeLabel(type), count });
|
||||
}
|
||||
return {
|
||||
totalItems: ciphers.length,
|
||||
folderCount: Math.max(0, folderCount),
|
||||
@@ -1075,7 +1078,7 @@ export default function App() {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (!session) throw new Error('Vault key unavailable');
|
||||
if (!session) throw new Error(t('txt_vault_key_unavailable'));
|
||||
await createFolder(authedFetch, session, folderName);
|
||||
await foldersQuery.refetch();
|
||||
pushToast('success', t('txt_folder_created'));
|
||||
@@ -1088,15 +1091,15 @@ export default function App() {
|
||||
async function deleteFolderAction(folderId: string) {
|
||||
const id = String(folderId || '').trim();
|
||||
if (!id) {
|
||||
pushToast('error', 'Folder not found');
|
||||
pushToast('error', t('txt_folder_not_found'));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await deleteFolder(authedFetch, id);
|
||||
await Promise.all([ciphersQuery.refetch(), foldersQuery.refetch()]);
|
||||
pushToast('success', 'Folder deleted');
|
||||
pushToast('success', t('txt_folder_deleted'));
|
||||
} catch (error) {
|
||||
pushToast('error', error instanceof Error ? error.message : 'Delete folder failed');
|
||||
pushToast('error', error instanceof Error ? error.message : t('txt_delete_folder_failed'));
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -1120,7 +1123,7 @@ export default function App() {
|
||||
idMaps: { byIndex: Map<number, string>; bySourceId: Map<string, string> }
|
||||
): Promise<void> {
|
||||
if (!attachments.length) return;
|
||||
if (!session?.symEncKey || !session?.symMacKey) throw new Error('Vault key unavailable');
|
||||
if (!session?.symEncKey || !session?.symMacKey) throw new Error(t('txt_vault_key_unavailable'));
|
||||
|
||||
const initialCiphers = (await ciphersQuery.refetch()).data || [];
|
||||
const cipherById = new Map(initialCiphers.map((cipher) => [String(cipher.id || ''), cipher]));
|
||||
@@ -1145,7 +1148,7 @@ export default function App() {
|
||||
}
|
||||
|
||||
if (unresolved.length) {
|
||||
throw new Error(`Failed to map ${unresolved.length} attachment(s) to imported items.`);
|
||||
throw new Error(t('txt_failed_to_map_attachments', { count: unresolved.length }));
|
||||
}
|
||||
|
||||
await ciphersQuery.refetch();
|
||||
@@ -1172,7 +1175,7 @@ export default function App() {
|
||||
options: { folderMode: 'original' | 'none' | 'target'; targetFolderId: string | null },
|
||||
attachments: ImportAttachmentFile[] = []
|
||||
): Promise<ImportResultSummary> {
|
||||
if (!session?.symEncKey || !session?.symMacKey) throw new Error('Vault key unavailable');
|
||||
if (!session?.symEncKey || !session?.symMacKey) throw new Error(t('txt_vault_key_unavailable'));
|
||||
|
||||
const mode = options.folderMode || 'original';
|
||||
const targetFolderId = (options.targetFolderId || '').trim() || null;
|
||||
@@ -1284,7 +1287,7 @@ export default function App() {
|
||||
}
|
||||
|
||||
async function handleExportAction(request: ExportRequest) {
|
||||
if (!session?.symEncKey || !session?.symMacKey) throw new Error('Vault key unavailable');
|
||||
if (!session?.symEncKey || !session?.symMacKey) throw new Error(t('txt_vault_key_unavailable'));
|
||||
const masterPassword = String(request.masterPassword || '').trim();
|
||||
if (!masterPassword) throw new Error(t('txt_master_password_is_required'));
|
||||
const email = String(profile?.email || session.email || '').trim().toLowerCase();
|
||||
@@ -1294,7 +1297,7 @@ export default function App() {
|
||||
|
||||
const rawFolders = foldersQuery.data || [];
|
||||
const rawCiphers = ciphersQuery.data || [];
|
||||
if (!rawFolders || !rawCiphers) throw new Error('Vault is not ready yet');
|
||||
if (!rawFolders || !rawCiphers) throw new Error(t('txt_vault_not_ready'));
|
||||
|
||||
let plainJsonCache: string | null = null;
|
||||
let plainJsonDocCache: Record<string, unknown> | null = null;
|
||||
@@ -1512,7 +1515,7 @@ export default function App() {
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error('Unsupported export format');
|
||||
throw new Error(t('txt_unsupported_export_format'));
|
||||
}
|
||||
|
||||
const hashPathRaw = typeof window !== 'undefined' ? window.location.hash || '' : '';
|
||||
|
||||
Reference in New Issue
Block a user