infopcgood 2 days ago
commit 0000d6cdee

@ -263,6 +263,82 @@ function logErrorOnce(category, message) {
return false; return false;
} }
// ============================================
// VISITOR TRACKING
// ============================================
// Lightweight in-memory visitor counter — tracks unique IPs per day
// No cookies, no external analytics, no persistent storage
// Resets on server restart; logs daily summary
const visitorStats = {
today: new Date().toISOString().slice(0, 10), // YYYY-MM-DD
uniqueIPs: new Set(),
totalRequests: 0,
allTimeVisitors: 0, // Cumulative unique visitors since server start
allTimeRequests: 0, // Cumulative requests since server start
serverStarted: new Date().toISOString(),
history: [] // Last 30 days of { date, uniqueVisitors, totalRequests }
};
function rolloverVisitorStats() {
const now = new Date().toISOString().slice(0, 10);
if (now !== visitorStats.today) {
// Save yesterday's stats to history
visitorStats.history.push({
date: visitorStats.today,
uniqueVisitors: visitorStats.uniqueIPs.size,
totalRequests: visitorStats.totalRequests
});
// Keep only last 30 days
if (visitorStats.history.length > 30) {
visitorStats.history = visitorStats.history.slice(-30);
}
const avg = visitorStats.history.length > 0
? Math.round(visitorStats.history.reduce((sum, d) => sum + d.uniqueVisitors, 0) / visitorStats.history.length)
: 0;
console.log(`[Visitors] Daily summary for ${visitorStats.today}: ${visitorStats.uniqueIPs.size} unique visitors, ${visitorStats.totalRequests} requests | All-time: ${visitorStats.allTimeVisitors} visitors, ${visitorStats.allTimeRequests} requests | ${visitorStats.history.length}-day avg: ${avg}/day`);
// Reset daily counters for new day
visitorStats.today = now;
visitorStats.uniqueIPs = new Set();
visitorStats.totalRequests = 0;
}
}
// Visitor tracking middleware — only counts page loads and API config fetches
// (not every API poll, which would inflate the count)
app.use((req, res, next) => {
rolloverVisitorStats();
// Only count meaningful "visits" — initial page load or config fetch
// This avoids counting every 5-second DX cluster poll as a "visit"
const countableRoutes = ['/', '/index.html', '/api/config'];
if (countableRoutes.includes(req.path)) {
const ip = req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.ip || req.connection?.remoteAddress || 'unknown';
const isNew = !visitorStats.uniqueIPs.has(ip);
visitorStats.uniqueIPs.add(ip);
visitorStats.totalRequests++;
visitorStats.allTimeRequests++;
if (isNew) {
visitorStats.allTimeVisitors++;
logInfo(`[Visitors] New visitor today (#${visitorStats.uniqueIPs.size}, #${visitorStats.allTimeVisitors} all-time) from ${ip.replace(/\d+$/, 'x')}`);
}
}
next();
});
// Log visitor count every hour
setInterval(() => {
rolloverVisitorStats();
if (visitorStats.uniqueIPs.size > 0 || visitorStats.allTimeVisitors > 0) {
const avg = visitorStats.history.length > 0
? Math.round(visitorStats.history.reduce((sum, d) => sum + d.uniqueVisitors, 0) / visitorStats.history.length)
: visitorStats.uniqueIPs.size;
console.log(`[Visitors] Today so far: ${visitorStats.uniqueIPs.size} unique, ${visitorStats.totalRequests} requests | All-time: ${visitorStats.allTimeVisitors} visitors | Avg: ${avg}/day`);
}
}, 60 * 60 * 1000);
// Serve static files // Serve static files
// dist/ contains the built React app (from npm run build) // dist/ contains the built React app (from npm run build)
// public/ contains the fallback page if build hasn't run // public/ contains the fallback page if build hasn't run
@ -3593,11 +3669,29 @@ function getLastWeekendOfMonth(year, month) {
// ============================================ // ============================================
app.get('/api/health', (req, res) => { app.get('/api/health', (req, res) => {
rolloverVisitorStats();
const avg = visitorStats.history.length > 0
? Math.round(visitorStats.history.reduce((sum, d) => sum + d.uniqueVisitors, 0) / visitorStats.history.length)
: visitorStats.uniqueIPs.size;
res.json({ res.json({
status: 'ok', status: 'ok',
version: APP_VERSION, version: APP_VERSION,
uptime: process.uptime(), uptime: process.uptime(),
timestamp: new Date().toISOString() timestamp: new Date().toISOString(),
visitors: {
today: {
date: visitorStats.today,
uniqueVisitors: visitorStats.uniqueIPs.size,
totalRequests: visitorStats.totalRequests
},
allTime: {
since: visitorStats.serverStarted,
uniqueVisitors: visitorStats.allTimeVisitors,
totalRequests: visitorStats.allTimeRequests
},
dailyAverage: avg,
history: visitorStats.history
}
}); });
}); });

Loading…
Cancel
Save

Powered by TurnKey Linux.