@@ -53,8 +53,7 @@ function initializeAllTomSelects() {
5353 direction : 'asc'
5454 }
5555 } ;
56-
57- // Initialize selects on the Dashboard page (Add/Edit Rule modals)
56+
5857 const addGroupSelect = document . getElementById ( 'manual_access_group' ) ;
5958 if ( addGroupSelect ) {
6059 new TomSelect ( addGroupSelect , multiCheckboxOptions ) ;
@@ -69,7 +68,6 @@ function initializeAllTomSelects() {
6968 manualTunnelTomSelect = new TomSelect ( manualTunnelSelect , singleSelectOptions ) ;
7069 }
7170
72- // Note: country selector is now initialized in access_policies.html with enhanced features
7371}
7472
7573const themeManager = ( function ( ) {
@@ -519,8 +517,7 @@ function connectStateUpdateSource() {
519517 eventSource . onerror = function ( err ) {
520518 console . error ( "State update stream connection error. It will be retried automatically by the browser." , err ) ;
521519 eventSource . close ( ) ;
522- // The browser will automatically try to reconnect. If we want to implement a custom backoff, we can do it here.
523- // For now, we'll rely on the default behavior.
520+
524521 setTimeout ( connectStateUpdateSource , 5000 ) ; // Reconnect after 5 seconds
525522 } ;
526523}
@@ -702,24 +699,61 @@ function updateCountdowns() {
702699 try {
703700 const targetDate = new Date ( deleteAtISO ) ;
704701 if ( isNaN ( targetDate . getTime ( ) ) ) throw new Error ( "Invalid date" ) ;
705- const options = {
702+
703+ const now = new Date ( ) ;
704+ const diffMs = targetDate - now ;
705+ const diffSeconds = Math . floor ( diffMs / 1000 ) ;
706+
707+ if ( diffMs < 0 ) {
708+
709+ absoluteTimeSpan . textContent = "Expired" ;
710+ countdownSpan . textContent = "" ;
711+ absoluteTimeSpan . className = 'absolute-time-display text-error font-bold' ;
712+ } else if ( diffSeconds < 3600 ) {
713+
714+ const minutes = Math . floor ( diffSeconds / 60 ) ;
715+ const seconds = diffSeconds % 60 ;
716+ const timeStr = `${ minutes } :${ seconds . toString ( ) . padStart ( 2 , '0' ) } ` ;
717+
718+ absoluteTimeSpan . textContent = `Expires in ${ timeStr } ` ;
719+ countdownSpan . textContent = "" ;
720+
721+ if ( diffSeconds <= 10 ) {
722+ absoluteTimeSpan . className = 'absolute-time-display text-error font-bold animate-pulse' ;
723+ } else if ( diffSeconds <= 30 ) {
724+ absoluteTimeSpan . className = 'absolute-time-display text-error font-semibold' ;
725+ } else if ( diffSeconds <= 120 ) {
726+ absoluteTimeSpan . className = 'absolute-time-display text-warning font-semibold' ;
727+ } else {
728+ absoluteTimeSpan . className = 'absolute-time-display text-success' ;
729+ }
730+ } else {
731+
732+ const hours = Math . floor ( diffSeconds / 3600 ) ;
733+ const minutes = Math . floor ( ( diffSeconds % 3600 ) / 60 ) ;
734+
735+ let timeStr = '' ;
736+ if ( hours > 0 ) {
737+ timeStr = minutes > 0 ? `${ hours } h ${ minutes } m` : `${ hours } h` ;
738+ } else {
739+ timeStr = `${ minutes } m` ;
740+ }
741+
742+ absoluteTimeSpan . textContent = `Expires in ${ timeStr } ` ;
743+ countdownSpan . textContent = "" ;
744+ absoluteTimeSpan . className = 'absolute-time-display text-base-content opacity-70' ;
745+ }
746+
747+ const fullTimestamp = targetDate . toLocaleString ( undefined , {
706748 hour : '2-digit' ,
707749 minute : '2-digit' ,
750+ second : '2-digit' ,
708751 day : '2-digit' ,
709752 month : 'short' ,
710753 year : 'numeric'
711- } ;
712- absoluteTimeSpan . textContent = targetDate . toLocaleString ( undefined , options ) ;
713- const now = new Date ( ) ;
714- const diff = targetDate - now ;
715- countdownSpan . textContent = `(${ formatTimeDifference ( diff ) } )` ;
716- if ( diff < 0 ) {
717- countdownSpan . classList . add ( 'text-error' ) ;
718- absoluteTimeSpan . classList . add ( 'text-error' ) ;
719- } else {
720- countdownSpan . classList . remove ( 'text-error' ) ;
721- absoluteTimeSpan . classList . remove ( 'text-error' ) ;
722- }
754+ } ) ;
755+ div . setAttribute ( 'title' , `Exact time: ${ fullTimestamp } ` ) ;
756+
723757 } catch ( e ) {
724758 absoluteTimeSpan . textContent = "(Invalid Date)" ;
725759 countdownSpan . textContent = "" ;
@@ -1245,8 +1279,7 @@ document.addEventListener('DOMContentLoaded', function() {
12451279 fixResourcesAndBase ( ) ;
12461280 themeManager . initialize ( ) ;
12471281 initializeAllTomSelects ( ) ;
1248-
1249- // Setup for Manual Rule Modal (only if on Dashboard Page)
1282+
12501283 const manualServiceTypeSelect = document . getElementById ( 'manual_service_type' ) ;
12511284 if ( manualServiceTypeSelect ) {
12521285 manualServiceTypeSelect . addEventListener ( 'change' , updateManualRuleServiceFields ) ;
@@ -1298,8 +1331,7 @@ document.addEventListener('DOMContentLoaded', function() {
12981331 } ) ;
12991332 } ) ;
13001333 }
1301-
1302- // Logic for new Access Group dropdown in ADD Manual Rule Modal
1334+
13031335 const manualAccessGroupSelect = document . getElementById ( 'manual_access_group' ) ;
13041336 const manualPolicyOptionsWrapper = document . getElementById ( 'manual_policy_options_wrapper' ) ;
13051337 if ( manualAccessGroupSelect && manualPolicyOptionsWrapper ) {
@@ -1312,8 +1344,7 @@ document.addEventListener('DOMContentLoaded', function() {
13121344 } ) ;
13131345 manualAccessGroupSelect . dispatchEvent ( new Event ( 'change' ) ) ;
13141346 }
1315-
1316- // Logic for new Access Group dropdown in EDIT Manual Rule Modal
1347+
13171348 const editManualAccessGroupSelect = document . getElementById ( 'edit_manual_access_group' ) ;
13181349 const editManualPolicyOptionsWrapper = document . getElementById ( 'edit_manual_policy_options_wrapper' ) ;
13191350 if ( editManualAccessGroupSelect && editManualPolicyOptionsWrapper ) {
@@ -1325,8 +1356,7 @@ document.addEventListener('DOMContentLoaded', function() {
13251356 } ) ;
13261357 } ) ;
13271358 }
1328-
1329- // Universal handler for all policy type dropdowns to show/hide the email auth field
1359+
13301360 document . querySelectorAll ( '.policy-type-select' ) . forEach ( select => {
13311361
13321362
@@ -1338,7 +1368,7 @@ document.addEventListener('DOMContentLoaded', function() {
13381368
13391369 const emailField = container . querySelector ( '.auth-email-field' ) ;
13401370 if ( ! emailField ) {
1341- // This is expected for some policy selectors that don't have an email field.
1371+
13421372 return ;
13431373 }
13441374
@@ -1363,8 +1393,7 @@ document.addEventListener('DOMContentLoaded', function() {
13631393 }
13641394 }
13651395 } ;
1366-
1367- // Add the event listener for user interactions
1396+
13681397 select . addEventListener ( 'change' , ( ) => {
13691398 toggleEmailField ( ) ;
13701399 clearAccessGroupOnPolicyChange ( ) ;
@@ -1435,8 +1464,7 @@ document.addEventListener('DOMContentLoaded', function() {
14351464 }
14361465 } ) ;
14371466 } ) ;
1438-
1439- // Setup for Access Group Modal (only if on Access Groups Page)
1467+
14401468 document . querySelectorAll ( '.edit-access-group-btn' ) . forEach ( button => {
14411469 button . addEventListener ( 'click' , function ( ) {
14421470 const groupId = this . dataset . groupId ;
@@ -1451,8 +1479,7 @@ document.addEventListener('DOMContentLoaded', function() {
14511479 openCreateAccessGroupModal ( ) ;
14521480 } ) ;
14531481 }
1454-
1455- // Universal Form/Link Protocol Correction
1482+
14561483 document . querySelectorAll ( 'form.protocol-aware-form' ) . forEach ( form => {
14571484 if ( form . getAttribute ( 'action' ) ) {
14581485 try {
@@ -1462,11 +1489,11 @@ document.addEventListener('DOMContentLoaded', function() {
14621489 }
14631490 } ) ;
14641491
1465- // Universal Page Timers and Connections
1492+
14661493 updateCountdowns ( ) ;
1467- setInterval ( updateCountdowns , 30000 ) ;
1494+ setInterval ( updateCountdowns , 1000 ) ;
14681495
1469- // Set up opt-in log streaming controls
1496+
14701497 if ( document . getElementById ( 'log-output' ) ) {
14711498 setupLogControls ( ) ;
14721499 }
@@ -1493,8 +1520,7 @@ document.addEventListener('DOMContentLoaded', function() {
14931520 document . getElementById ( 'idp-form' ) ?. addEventListener ( 'submit' , handleIdPFormSubmit ) ;
14941521 document . getElementById ( 'idp-type' ) ?. addEventListener ( 'change' , updateIdPFormFields ) ;
14951522 }
1496-
1497- // Universal Cleanup
1523+
14981524 window . addEventListener ( 'beforeunload' , function ( ) {
14991525 if ( activeLogSource ) activeLogSource . close ( ) ;
15001526 if ( eventSourceHealthCheck ) clearInterval ( eventSourceHealthCheck ) ;
0 commit comments