diff --git a/core/scripts/webpanel/assets/js/users.js b/core/scripts/webpanel/assets/js/users.js index ce28caf..0abab35 100644 --- a/core/scripts/webpanel/assets/js/users.js +++ b/core/scripts/webpanel/assets/js/users.js @@ -17,6 +17,7 @@ $(function () { const passwordRegex = /^[a-zA-Z0-9]+$/; let cachedUserData = []; let searchTimeout = null; + const PRACTICAL_MAX_DAYS = 36500; function setCookie(name, value, days) { let expires = ""; @@ -63,6 +64,7 @@ $(function () { const isValid = usernameRegex.test(username); $(errorElement).text(isValid ? "" : "Usernames can only contain letters, numbers, and underscores."); $(inputElement).closest('form').find('button[type="submit"]').prop('disabled', !isValid); + return isValid; } function validatePassword(inputElement, errorElement) { @@ -70,8 +72,26 @@ $(function () { const isValid = password === '' || passwordRegex.test(password); $(errorElement).text(isValid ? "" : "Password can only contain letters and numbers."); $('#editSubmitButton').prop('disabled', !isValid); + return isValid; } + function validateExpirationDays(inputElement, errorElement) { + const days = parseInt($(inputElement).val(), 10); + let isValid = !isNaN(days) && days >= 0; + let errorMessage = ""; + + if (!isValid) { + errorMessage = "Please enter a non-negative number."; + } else if (days > PRACTICAL_MAX_DAYS) { + isValid = false; + errorMessage = `For unlimited duration, please use 0. Values above ${PRACTICAL_MAX_DAYS} are not practical.`; + } + + $(errorElement).text(errorMessage); + $(inputElement).closest('form').find('button[type="submit"]').prop('disabled', !isValid); + return isValid; + } + function refreshUserList() { const query = $("#searchInput").val().trim(); if (query !== "") { @@ -149,6 +169,10 @@ $(function () { validateUsername(this, `#${this.id}Error`); }); + $('#addExpirationDays, #addBulkExpirationDays, #editExpirationDays').on('input', function() { + validateExpirationDays(this, `#${this.id}Error`); + }); + $(".filter-button").on("click", function (e) { e.preventDefault(); const filter = $(this).data("filter"); @@ -260,6 +284,7 @@ $(function () { const statusText = dataRow.find("td:eq(3)").text().trim(); $('#editPasswordError').text(''); + $('#editExpirationDaysError').text(''); $('#editSubmitButton').prop('disabled', false); $("#originalUsername").val(user); @@ -480,7 +505,7 @@ $(function () { $('#addUserModal').on('show.bs.modal', function () { $('#addUserForm, #addBulkUsersForm').trigger('reset'); - $('#addUsernameError, #addBulkPrefixError').text(''); + $('#addUsernameError, #addBulkPrefixError, #addExpirationDaysError, #addBulkExpirationDaysError').text(''); Object.assign(document.getElementById('addTrafficLimit'), {value: 30}); Object.assign(document.getElementById('addExpirationDays'), {value: 30}); Object.assign(document.getElementById('addBulkTrafficLimit'), {value: 30}); diff --git a/core/scripts/webpanel/routers/user/viewmodel.py b/core/scripts/webpanel/routers/user/viewmodel.py index a3e55f1..ab19365 100644 --- a/core/scripts/webpanel/routers/user/viewmodel.py +++ b/core/scripts/webpanel/routers/user/viewmodel.py @@ -51,13 +51,22 @@ class User(BaseModel): day_usage = "On-hold" display_expiry_days = "On-hold" display_expiry_date = "On-hold" + + # 100 years. This cap exists for two critical reasons: + # 1. Technical: Prevents an OverflowError, as Python's `datetime` library has an existential crisis + # when confronted with any date beyond the year 9999. + # 2. Philosophical: We assume any user needing a subscription longer than a century is a vampire, + # a time-traveler, or a very optimistic cyborg. Our customer support policy does not cover + # the undead or temporal paradoxes. This is a feature, not a bug, designed to prevent + # inter-millennial bug reports. + PRACTICAL_MAX_DAYS = 36500 if creation_date_str: try: creation_date = datetime.strptime(creation_date_str, "%Y-%m-%d") day_usage = str((datetime.now() - creation_date).days) - if expiration_days <= 0: + if expiration_days <= 0 or expiration_days > PRACTICAL_MAX_DAYS: display_expiry_days = "Unlimited" display_expiry_date = "Unlimited" else: diff --git a/core/scripts/webpanel/templates/users.html b/core/scripts/webpanel/templates/users.html index 5e28b9a..12b85d2 100644 --- a/core/scripts/webpanel/templates/users.html +++ b/core/scripts/webpanel/templates/users.html @@ -252,7 +252,7 @@