@@ -59,6 +59,7 @@ document.addEventListener('DOMContentLoaded', () => {
59
59
const moonIcon = document . getElementById ( 'moon-icon' ) ;
60
60
// Run All Test Elements
61
61
const runAllTestBtn = document . getElementById ( 'run-all-test-btn' ) ;
62
+ const ignoreAllErrorsBtn = document . getElementById ( 'ignore-all-errors-btn' ) ;
62
63
const cleanErrorKeysBtn = document . getElementById ( 'clean-error-keys-btn' ) ;
63
64
const geminiKeysActionsDiv = document . getElementById ( 'gemini-keys-actions' ) ;
64
65
const testProgressArea = document . getElementById ( 'test-progress-area' ) ;
@@ -411,10 +412,12 @@ async function renderGeminiKeys(keys) {
411
412
// Check if there are any error keys
412
413
const hasErrorKeys = keys . some ( key => key . errorStatus === 400 || key . errorStatus === 401 || key . errorStatus === 403 ) ;
413
414
414
- // Show/hide clean error keys button based on error keys existence
415
+ // Show/hide error-related buttons based on error keys existence
415
416
if ( hasErrorKeys ) {
417
+ ignoreAllErrorsBtn . classList . remove ( 'hidden' ) ;
416
418
cleanErrorKeysBtn . classList . remove ( 'hidden' ) ;
417
419
} else {
420
+ ignoreAllErrorsBtn . classList . add ( 'hidden' ) ;
418
421
cleanErrorKeysBtn . classList . add ( 'hidden' ) ;
419
422
}
420
423
@@ -860,95 +863,8 @@ async function renderGeminiKeys(keys) {
860
863
} ) ;
861
864
} ) ;
862
865
863
- // Add test button click event (no changes needed here)
864
- document . querySelectorAll ( '.test-gemini-key' ) . forEach ( btn => {
865
- btn . addEventListener ( 'click' , ( e ) => {
866
- const keyId = e . target . dataset . id ;
867
- const testSection = document . querySelector ( `.test-model-section[data-key-id="${ keyId } "]` ) ;
868
-
869
- // Toggle display status
870
- if ( testSection . classList . contains ( 'hidden' ) ) {
871
- // Hide all other test areas
872
- document . querySelectorAll ( '.test-model-section' ) . forEach ( section => {
873
- section . classList . add ( 'hidden' ) ;
874
- section . querySelector ( '.test-result' ) ?. classList . add ( 'hidden' ) ;
875
- } ) ;
876
-
877
- // Show current test area
878
- testSection . classList . remove ( 'hidden' ) ;
879
- } else {
880
- testSection . classList . add ( 'hidden' ) ;
881
- }
882
- } ) ;
883
- } ) ;
884
-
885
- // Add run test button click event(修正:测试报错只显示在测试区域)
886
- document . querySelectorAll ( '.run-test-btn' ) . forEach ( btn => {
887
- btn . addEventListener ( 'click' , async ( e ) => {
888
- const testSection = e . target . closest ( '.test-model-section' ) ;
889
- const keyId = testSection . dataset . keyId ;
890
- const modelId = testSection . querySelector ( '.model-select' ) . value ;
891
- const resultDiv = testSection . querySelector ( '.test-result' ) ;
892
- const resultPre = resultDiv . querySelector ( 'pre' ) ;
893
-
894
- if ( ! modelId ) {
895
- showError ( t ( 'please_select_model' ) ) ;
896
- return ;
897
- }
898
-
899
- // Show result area and set "Loading" text
900
- resultDiv . classList . remove ( 'hidden' ) ;
901
- resultPre . textContent = t ( 'testing' ) ;
902
-
903
- // Send test request directly to handle both success and error responses
904
- let result = null ;
905
- try {
906
- // Use direct fetch instead of apiFetch to get raw response
907
- const response = await fetch ( '/api/admin/test-gemini-key' , {
908
- method : 'POST' ,
909
- headers : {
910
- 'Content-Type' : 'application/json' ,
911
- } ,
912
- credentials : 'include' ,
913
- body : JSON . stringify ( { keyId, modelId } )
914
- } ) ;
915
-
916
- // Parse response regardless of status code
917
- const contentType = response . headers . get ( "content-type" ) ;
918
- if ( contentType && contentType . indexOf ( "application/json" ) !== - 1 ) {
919
- result = await response . json ( ) ;
920
- } else {
921
- const textContent = await response . text ( ) ;
922
- result = {
923
- success : false ,
924
- status : response . status ,
925
- content : textContent || 'No response content'
926
- } ;
927
- }
928
-
929
- if ( result ) {
930
- const formattedContent = typeof result . content === 'object'
931
- ? JSON . stringify ( result . content , null , 2 )
932
- : result . content ;
933
-
934
- if ( result . success ) {
935
- resultPre . textContent = `${ t ( 'test_passed' ) } \n${ t ( 'status' ) } : ${ result . status } \n\n${ t ( 'response' ) } :\n${ formattedContent } ` ;
936
- resultPre . className = 'text-xs bg-green-50 text-green-800 p-2 rounded overflow-x-auto' ;
937
- } else {
938
- resultPre . textContent = `${ t ( 'test_failed' ) } \n${ t ( 'status' ) } : ${ result . status } \n\n${ t ( 'response' ) } :\n${ formattedContent } ` ;
939
- resultPre . className = 'text-xs bg-red-50 text-red-800 p-2 rounded overflow-x-auto' ;
940
- }
941
- } else {
942
- resultPre . textContent = t ( 'test_failed_no_response' ) ;
943
- resultPre . className = 'text-xs bg-red-50 text-red-800 p-2 rounded overflow-x-auto' ;
944
- }
945
- } catch ( error ) {
946
- // 只在测试区域显示网络错误
947
- resultPre . textContent = t ( 'test_failed_network' , error . message || t ( 'unknown_error' ) ) ;
948
- resultPre . className = 'text-xs bg-red-50 text-red-800 p-2 rounded overflow-x-auto' ;
949
- }
950
- } ) ;
951
- } ) ;
866
+ // Note: Event listeners for .test-gemini-key and .run-test-btn are now handled
867
+ // by global event delegation to prevent duplicate listeners and DOM reference issues
952
868
}
953
869
954
870
function renderWorkerKeys ( keys ) {
@@ -1499,8 +1415,117 @@ async function renderGeminiKeys(keys) {
1499
1415
}
1500
1416
} ) ;
1501
1417
1502
- // Delete Gemini Key (no changes needed)
1418
+ // Global event delegation for Gemini key actions
1503
1419
document . addEventListener ( 'click' , async ( e ) => {
1420
+ // Handle test gemini key button clicks
1421
+ if ( e . target . classList . contains ( 'test-gemini-key' ) ) {
1422
+ const keyId = e . target . dataset . id ;
1423
+ const testSection = document . querySelector ( `.test-model-section[data-key-id="${ keyId } "]` ) ;
1424
+
1425
+ // Check if testSection exists (防止DOM重新渲染后元素不存在的错误)
1426
+ if ( ! testSection ) {
1427
+ console . warn ( 'Test section not found for keyId:' , keyId ) ;
1428
+ return ;
1429
+ }
1430
+
1431
+ // Toggle display status
1432
+ if ( testSection . classList . contains ( 'hidden' ) ) {
1433
+ // Hide all other test areas
1434
+ document . querySelectorAll ( '.test-model-section' ) . forEach ( section => {
1435
+ section . classList . add ( 'hidden' ) ;
1436
+ section . querySelector ( '.test-result' ) ?. classList . add ( 'hidden' ) ;
1437
+ } ) ;
1438
+
1439
+ // Show current test area
1440
+ testSection . classList . remove ( 'hidden' ) ;
1441
+ } else {
1442
+ testSection . classList . add ( 'hidden' ) ;
1443
+ }
1444
+ return ;
1445
+ }
1446
+
1447
+ // Handle run test button clicks
1448
+ if ( e . target . classList . contains ( 'run-test-btn' ) && ! e . target . id ) { // Exclude the main "run all test" button
1449
+ const testSection = e . target . closest ( '.test-model-section' ) ;
1450
+
1451
+ // Check if testSection exists (防止DOM重新渲染后元素不存在的错误)
1452
+ if ( ! testSection ) {
1453
+ console . warn ( 'Test section not found, possibly due to DOM re-rendering' ) ;
1454
+ return ;
1455
+ }
1456
+
1457
+ const keyId = testSection . dataset . keyId ;
1458
+ const modelSelect = testSection . querySelector ( '.model-select' ) ;
1459
+ const resultDiv = testSection . querySelector ( '.test-result' ) ;
1460
+ const resultPre = resultDiv ?. querySelector ( 'pre' ) ;
1461
+
1462
+ // Additional safety checks
1463
+ if ( ! keyId || ! modelSelect || ! resultDiv || ! resultPre ) {
1464
+ console . warn ( 'Required elements not found in test section' ) ;
1465
+ return ;
1466
+ }
1467
+
1468
+ const modelId = modelSelect . value ;
1469
+
1470
+ if ( ! modelId ) {
1471
+ showError ( t ( 'please_select_model' ) ) ;
1472
+ return ;
1473
+ }
1474
+
1475
+ // Show result area and set "Loading" text
1476
+ resultDiv . classList . remove ( 'hidden' ) ;
1477
+ resultPre . textContent = t ( 'testing' ) ;
1478
+
1479
+ // Send test request directly to handle both success and error responses
1480
+ let result = null ;
1481
+ try {
1482
+ // Use direct fetch instead of apiFetch to get raw response
1483
+ const response = await fetch ( '/api/admin/test-gemini-key' , {
1484
+ method : 'POST' ,
1485
+ headers : {
1486
+ 'Content-Type' : 'application/json' ,
1487
+ } ,
1488
+ credentials : 'include' ,
1489
+ body : JSON . stringify ( { keyId, modelId } )
1490
+ } ) ;
1491
+
1492
+ // Parse response regardless of status code
1493
+ const contentType = response . headers . get ( "content-type" ) ;
1494
+ if ( contentType && contentType . indexOf ( "application/json" ) !== - 1 ) {
1495
+ result = await response . json ( ) ;
1496
+ } else {
1497
+ const textContent = await response . text ( ) ;
1498
+ result = {
1499
+ success : false ,
1500
+ status : response . status ,
1501
+ content : textContent || 'No response content'
1502
+ } ;
1503
+ }
1504
+
1505
+ if ( result ) {
1506
+ const formattedContent = typeof result . content === 'object'
1507
+ ? JSON . stringify ( result . content , null , 2 )
1508
+ : result . content ;
1509
+
1510
+ if ( result . success ) {
1511
+ resultPre . textContent = `${ t ( 'test_passed' ) } \n${ t ( 'status' ) } : ${ result . status } \n\n${ t ( 'response' ) } :\n${ formattedContent } ` ;
1512
+ resultPre . className = 'text-xs bg-green-50 text-green-800 p-2 rounded overflow-x-auto' ;
1513
+ } else {
1514
+ resultPre . textContent = `${ t ( 'test_failed' ) } \n${ t ( 'status' ) } : ${ result . status } \n\n${ t ( 'response' ) } :\n${ formattedContent } ` ;
1515
+ resultPre . className = 'text-xs bg-red-50 text-red-800 p-2 rounded overflow-x-auto' ;
1516
+ }
1517
+ } else {
1518
+ resultPre . textContent = t ( 'test_failed_no_response' ) ;
1519
+ resultPre . className = 'text-xs bg-red-50 text-red-800 p-2 rounded overflow-x-auto' ;
1520
+ }
1521
+ } catch ( error ) {
1522
+ // 只在测试区域显示网络错误
1523
+ resultPre . textContent = t ( 'test_failed_network' , error . message || t ( 'unknown_error' ) ) ;
1524
+ resultPre . className = 'text-xs bg-red-50 text-red-800 p-2 rounded overflow-x-auto' ;
1525
+ }
1526
+ return ;
1527
+ }
1528
+
1504
1529
if ( e . target . classList . contains ( 'delete-gemini-key' ) ) {
1505
1530
const keyId = e . target . dataset . id ;
1506
1531
if ( confirm ( t ( 'delete_confirm_gemini' , keyId ) ) ) {
@@ -1822,6 +1847,42 @@ async function renderGeminiKeys(keys) {
1822
1847
cancelAllTestBtn . disabled = true ;
1823
1848
} ) ;
1824
1849
1850
+ // Ignore All Errors Logic
1851
+ ignoreAllErrorsBtn . addEventListener ( 'click' , async ( ) => {
1852
+ // Prevent concurrent operations
1853
+ if ( operationInProgress ) {
1854
+ showError ( t ( 'operation_in_progress' ) || 'Another operation is in progress. Please wait.' ) ;
1855
+ return ;
1856
+ }
1857
+
1858
+ if ( ! confirm ( t ( 'ignore_all_errors_confirm' ) ) ) {
1859
+ return ;
1860
+ }
1861
+
1862
+ try {
1863
+ operationInProgress = true ; // Lock operations
1864
+ showLoading ( ) ;
1865
+ const result = await apiFetch ( '/clear-all-errors' , {
1866
+ method : 'POST' ,
1867
+ } ) ;
1868
+
1869
+ if ( result && result . success ) {
1870
+ if ( result . clearedCount === 0 ) {
1871
+ showSuccess ( t ( 'no_error_keys_found' ) ) ;
1872
+ } else {
1873
+ showSuccess ( t ( 'error_keys_ignored' , result . clearedCount ) ) ;
1874
+ }
1875
+ await loadGeminiKeys ( ) ; // Reload the keys list
1876
+ }
1877
+ } catch ( error ) {
1878
+ console . error ( 'Error ignoring error keys:' , error ) ;
1879
+ showError ( t ( 'failed_to_ignore_error_keys' , error . message ) ) ;
1880
+ } finally {
1881
+ operationInProgress = false ; // Release lock
1882
+ hideLoading ( ) ;
1883
+ }
1884
+ } ) ;
1885
+
1825
1886
// Clean Error Keys Logic
1826
1887
cleanErrorKeysBtn . addEventListener ( 'click' , async ( ) => {
1827
1888
// Prevent concurrent operations
0 commit comments