mirror of
https://github.com/sassanix/Warracker.git
synced 2026-01-06 05:29:39 -06:00
Fixes & Enhancements * Resolved five critical Apprise notification issues: • Ensured configuration reload during scheduled jobs • Fixed warranty data fetching for Apprise-only users • Refactored notification dispatch logic with dedicated helpers • Corrected handler scoping via Flask app context • Wrapped scheduler jobs with Flask app context to prevent context errors → Verified: Scheduled Apprise notifications now work reliably for "Apprise only" and "Both" channels. * Added support for SMTP\_FROM\_ADDRESS environment variable, allowing sender address customization independent of SMTP username. (PR #115) * Fixed duplicate scheduled notifications in multi-worker environments: • Strengthened should\_run\_scheduler() logic • Now guarantees exactly one scheduler instance across all Gunicorn modes. * Fixed stale database connection handling in scheduled jobs: • Fresh connection acquired each run, properly released via try/finally • Eliminates "server closed the connection" errors. * Definitive scheduler logic fix for all memory modes (ultra-light, optimized, performance): • Single-worker runs scheduler if GUNICORN\_WORKER\_ID is unset • Multi-worker: only worker 0 runs scheduler. Impact * Apprise and Email notifications are now stable, reliable, and production-ready * No more duplicate or missed notifications across all memory modes * Improved system efficiency and robustness
131 lines
4.0 KiB
JavaScript
131 lines
4.0 KiB
JavaScript
/**
|
|
* Footer Content Manager - Universal
|
|
* Manages the "Powered by" footer content across all pages
|
|
* Easy to customize footer text, links, and branding from one place
|
|
*/
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// === FOOTER CONFIGURATION ===
|
|
// Edit these values to change footer content across all pages
|
|
const FOOTER_CONFIG = {
|
|
// Main footer text
|
|
text: 'Powered by',
|
|
|
|
// Link configuration
|
|
link: {
|
|
text: 'Warracker',
|
|
url: 'https://warracker.com',
|
|
target: '_blank',
|
|
rel: 'noopener noreferrer'
|
|
},
|
|
|
|
// Optional: Additional links or text
|
|
// Uncomment and customize as needed
|
|
/*
|
|
additionalContent: [
|
|
{ type: 'text', content: ' | ' },
|
|
{ type: 'link', text: 'Privacy Policy', url: '/privacy.html' },
|
|
{ type: 'text', content: ' | ' },
|
|
{ type: 'link', text: 'Terms of Service', url: '/terms.html' }
|
|
]
|
|
*/
|
|
};
|
|
|
|
// === FOOTER CONTENT GENERATION ===
|
|
function generateFooterContent() {
|
|
let footerHTML = `${FOOTER_CONFIG.text} `;
|
|
|
|
// Add main link
|
|
footerHTML += `<a href="${FOOTER_CONFIG.link.url}" target="${FOOTER_CONFIG.link.target}" rel="${FOOTER_CONFIG.link.rel}" id="warrackerFooterLink">${FOOTER_CONFIG.link.text}</a>`;
|
|
|
|
// Add additional content if configured
|
|
if (FOOTER_CONFIG.additionalContent) {
|
|
FOOTER_CONFIG.additionalContent.forEach(item => {
|
|
if (item.type === 'text') {
|
|
footerHTML += item.content;
|
|
} else if (item.type === 'link') {
|
|
const target = item.target || '_self';
|
|
const rel = item.rel || '';
|
|
footerHTML += `<a href="${item.url}" target="${target}" rel="${rel}">${item.text}</a>`;
|
|
}
|
|
});
|
|
}
|
|
|
|
return footerHTML;
|
|
}
|
|
|
|
// === FOOTER INITIALIZATION ===
|
|
function initFooterContent() {
|
|
const footer = document.getElementById('warrackerFooter');
|
|
|
|
if (!footer) {
|
|
console.warn('Footer element with ID "warrackerFooter" not found');
|
|
return;
|
|
}
|
|
|
|
// Generate and inject footer content
|
|
const footerContent = generateFooterContent();
|
|
footer.innerHTML = `<p>${footerContent}</p>`;
|
|
|
|
console.log('Footer content initialized successfully');
|
|
}
|
|
|
|
// === DYNAMIC FOOTER CREATION ===
|
|
// If no footer exists, create one
|
|
function createFooterIfMissing() {
|
|
let footer = document.getElementById('warrackerFooter');
|
|
|
|
if (!footer) {
|
|
footer = document.createElement('footer');
|
|
footer.className = 'warracker-footer';
|
|
footer.id = 'warrackerFooter';
|
|
|
|
// Insert before closing body tag
|
|
document.body.appendChild(footer);
|
|
console.log('Footer element created dynamically');
|
|
}
|
|
|
|
return footer;
|
|
}
|
|
|
|
// === PUBLIC API ===
|
|
// Expose functions for manual updates if needed
|
|
window.FooterContent = {
|
|
// Update footer content dynamically
|
|
update: function(newConfig) {
|
|
Object.assign(FOOTER_CONFIG, newConfig);
|
|
initFooterContent();
|
|
},
|
|
|
|
// Get current configuration
|
|
getConfig: function() {
|
|
return { ...FOOTER_CONFIG };
|
|
},
|
|
|
|
// Reinitialize footer
|
|
reinit: function() {
|
|
initFooterContent();
|
|
}
|
|
};
|
|
|
|
// === AUTO-INITIALIZATION ===
|
|
function init() {
|
|
// Ensure footer element exists
|
|
createFooterIfMissing();
|
|
|
|
// Initialize footer content
|
|
initFooterContent();
|
|
}
|
|
|
|
// Initialize when DOM is ready
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
// DOM is already loaded
|
|
init();
|
|
}
|
|
|
|
console.log('Footer Content Manager loaded');
|
|
})();
|