Skip to content

Commit 9181304

Browse files
Merge pull request #282 from ChrispyBacon-dev/unstable
expire countdown for removed containers with grace period
2 parents d96a7b9 + 864ff68 commit 9181304

File tree

2 files changed

+65
-39
lines changed

2 files changed

+65
-39
lines changed

dockflare/app/static/js/main.js

Lines changed: 64 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -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

7573
const 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);

dockflare/app/templates/status_page.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ <h3 class="font-bold text-xs uppercase opacity-80">Last Action</h3>
150150
<th class="p-3 w-1/6">Path</th>
151151
<th class="p-3 w-1/5">Service Target</th>
152152
<th class="p-3 w-1/5">Access Policy</th>
153-
<th class="p-3 w-auto">Expires At</th>
153+
<th class="p-3 w-auto">Expires In</th>
154154
<th class="p-3 w-auto">Actions</th>
155155
</tr>
156156
</thead>

0 commit comments

Comments
 (0)