@@ -8,7 +8,6 @@ document.addEventListener('DOMContentLoaded', () => {
88 const tableBody = document . getElementById ( 'applications-body' ) ;
99 const tableWrapper = document . querySelector ( '.table-wrapper' ) ;
1010 const refreshButton = document . getElementById ( 'refresh-button' ) ;
11- const ADMIN_PASS_STORAGE_KEY = 'mb3r-admin-pass' ;
1211 const storage = window . MB3RStorage ;
1312 const getI18n = ( ) => window . MB3RI18n ;
1413 const t = ( key ) => ( getI18n ( ) ?. t ? getI18n ( ) . t ( key ) : key ) ;
@@ -36,7 +35,6 @@ document.addEventListener('DOMContentLoaded', () => {
3635 }
3736 return null ;
3837 } ;
39- sessionStorage . removeItem ( ADMIN_PASS_STORAGE_KEY ) ;
4038 let currentPassword = '' ;
4139 let hasRemoteSuccess = false ;
4240
@@ -171,7 +169,8 @@ document.addEventListener('DOMContentLoaded', () => {
171169
172170 try {
173171 const response = await fetch ( endpoint , {
174- headers : { 'x-admin-pass' : password }
172+ headers : { 'x-admin-pass' : password } ,
173+ cache : 'no-store'
175174 } ) ;
176175
177176 const data = await response . json ( ) . catch ( ( ) => ( { } ) ) ;
@@ -215,43 +214,58 @@ document.addEventListener('DOMContentLoaded', () => {
215214 try {
216215 const rows = await fetchApplications ( password ) ;
217216 currentPassword = password ;
218- sessionStorage . setItem ( ADMIN_PASS_STORAGE_KEY , password ) ;
219217 renderTable ( rows ) ;
220218 unlockTable ( ) ;
221219 closeModal ( ) ;
222220 hasRemoteSuccess = true ;
223221 setOverlayMessage ( t ( 'admin.table.overlayLocked' ) , 'admin.table.overlayLocked' ) ;
224222 return ;
225223 } catch ( error ) {
226- if ( error . status === 401 || error . status === 403 ) {
227- sessionStorage . removeItem ( ADMIN_PASS_STORAGE_KEY ) ;
224+ if ( error . status === 401 || error . status === 403 || error . status === 429 ) {
225+ const isIncorrectPassword = / i n c o r r e c t p a s s w o r d / i. test ( String ( error . message || '' ) ) ;
226+ const isRateLimited = error . status === 429 ;
228227 currentPassword = '' ;
229228 lockTable ( ) ;
230- setAuthStatus ( error . message , 'error' ) ;
231- setOverlayMessage ( t ( 'admin.status.incorrectPassword' ) , 'admin.status.incorrectPassword' ) ;
229+ setAuthStatus (
230+ isRateLimited ? t ( 'admin.status.tooManyAttempts' ) : error . message ,
231+ 'error'
232+ ) ;
233+ if ( isRateLimited ) {
234+ setOverlayMessage ( t ( 'admin.status.tooManyAttempts' ) , 'admin.status.tooManyAttempts' ) ;
235+ } else if ( isIncorrectPassword ) {
236+ setOverlayMessage ( t ( 'admin.status.incorrectPassword' ) , 'admin.status.incorrectPassword' ) ;
237+ } else {
238+ setOverlayMessage ( error . message || t ( 'admin.errors.loadFailed' ) ) ;
239+ }
232240 openModal ( ) ;
233241 throw error ;
234242 }
235243
236244 if ( error . offline ) {
245+ const cached = storage ?. list ?. ( ) || [ ] ;
246+ if ( cached . length ) {
247+ renderTable ( cached ) ;
248+ unlockTable ( ) ;
249+ closeModal ( ) ;
250+ setAuthStatus ( t ( 'admin.status.showingCached' ) , 'error' ) ;
251+ return ;
252+ }
253+
237254 if ( ! hasRemoteSuccess ) {
238255 setAuthStatus (
239256 t ( 'admin.status.apiUnavailable' ) ,
240257 'error'
241258 ) ;
242- sessionStorage . removeItem ( ADMIN_PASS_STORAGE_KEY ) ;
243259 currentPassword = '' ;
244260 lockTable ( ) ;
245261 setOverlayMessage ( t ( 'admin.status.apiUnavailableLater' ) , 'admin.status.apiUnavailableLater' ) ;
246262 openModal ( ) ;
247263 throw error ;
248264 }
249265
250- const cached = storage ?. list ?. ( ) || [ ] ;
251- renderTable ( cached ) ;
252266 unlockTable ( ) ;
253267 closeModal ( ) ;
254- setAuthStatus ( t ( 'admin.status.showingCached ' ) , 'error' ) ;
268+ setAuthStatus ( t ( 'admin.status.apiUnavailableLater ' ) , 'error' ) ;
255269 return ;
256270 }
257271
@@ -291,15 +305,49 @@ document.addEventListener('DOMContentLoaded', () => {
291305 loadApplications ( { silent : true } ) . catch ( ( ) => { } ) ;
292306 } ) ;
293307
308+ const isModalOpen = ( ) => modal ?. classList . contains ( 'is-open' ) ;
309+
310+ document . addEventListener ( 'keydown' , ( event ) => {
311+ if ( ! isModalOpen ( ) ) {
312+ return ;
313+ }
314+
315+ if ( event . key === 'Escape' ) {
316+ closeModal ( ) ;
317+ return ;
318+ }
319+
320+ if ( event . key !== 'Tab' || ! modal ) {
321+ return ;
322+ }
323+
324+ const focusable = Array . from (
325+ modal . querySelectorAll (
326+ 'a[href], button:not([disabled]), input:not([disabled]), textarea:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])'
327+ )
328+ ) ;
329+
330+ if ( ! focusable . length ) {
331+ return ;
332+ }
333+
334+ const firstEl = focusable [ 0 ] ;
335+ const lastEl = focusable [ focusable . length - 1 ] ;
336+
337+ if ( event . shiftKey && document . activeElement === firstEl ) {
338+ event . preventDefault ( ) ;
339+ lastEl . focus ( ) ;
340+ } else if ( ! event . shiftKey && document . activeElement === lastEl ) {
341+ event . preventDefault ( ) ;
342+ firstEl . focus ( ) ;
343+ }
344+ } ) ;
345+
294346 if ( ! isApiConfigured ) {
295347 whenI18nReady ( ) . then ( ( ) => {
296348 setTableMessage ( t ( 'admin.status.apiNotConfiguredHint' ) ) ;
297349 } ) ;
298350 }
299351
300- if ( currentPassword && isApiConfigured ) {
301- loadApplications ( { silent : true } ) . catch ( ( ) => { } ) ;
302- } else {
303- openModal ( ) ;
304- }
352+ openModal ( ) ;
305353} ) ;
0 commit comments