monitor.js 5.41 KB
const express = require('express');
const fs = require('fs');
const readline = require('readline');
const { exec } = require('child_process');
const { isAuthenticated } = require('../middleware/auth');
const router = express.Router();

const NGINX_LOG = process.env.NGINX_LOG || '/var/log/nginx/access.log';

// ─── Bandwidth dari /proc/net/dev ───────────────────────────────────────────
function getNetStats() {
  try {
    const raw = fs.readFileSync('/proc/net/dev', 'utf8');
    const lines = raw.trim().split('\n').slice(2);
    const result = {};
    lines.forEach(line => {
      const parts = line.trim().split(/\s+/);
      const iface = parts[0].replace(':', '');
      if (iface === 'lo') return;
      result[iface] = {
        rxBytes: parseInt(parts[1]),
        txBytes: parseInt(parts[9]),
      };
    });
    return result;
  } catch(e) { return {}; }
}

// Cache untuk hitung delta bps
let prevStats = {};
let prevTime = Date.now();

function getBandwidth() {
  const now = Date.now();
  const current = getNetStats();
  const elapsed = (now - prevTime) / 1000; // detik
  const bandwidth = {};

  Object.keys(current).forEach(iface => {
    const prev = prevStats[iface];
    if (prev && elapsed > 0) {
      bandwidth[iface] = {
        rxBps: Math.max(0, (current[iface].rxBytes - prev.rxBytes) / elapsed),
        txBps: Math.max(0, (current[iface].txBytes - prev.txBytes) / elapsed),
        rxBytes: current[iface].rxBytes,
        txBytes: current[iface].txBytes,
      };
    } else {
      bandwidth[iface] = { rxBps: 0, txBps: 0, ...current[iface] };
    }
  });

  prevStats = current;
  prevTime = now;
  return bandwidth;
}

// ─── Parse nginx access log ──────────────────────────────────────────────────
// Format nginx default: IP - - [date] "METHOD /path HTTP/x.x" status bytes "ref" "ua"
function parseNginxLog(limit = 100) {
  return new Promise((resolve) => {
    const entries = [];
    try {
      if (!fs.existsSync(NGINX_LOG)) return resolve([]);
      const content = fs.readFileSync(NGINX_LOG, 'utf8');
      const lines = content.trim().split('\n').filter(Boolean);
      // Ambil dari bawah (terbaru)
      const recent = lines.slice(-limit).reverse();

      recent.forEach(line => {
        // Hanya tampilkan request ke /debian12/live/
        if (!line.includes('/debian12/live/')) return;

        // Parse format nginx combined log
        const m = line.match(/^(\S+)\s+-\s+-\s+\[([^\]]+)\]\s+"(\S+)\s+(\S+)\s+\S+"\s+(\d+)\s+(\d+)/);
        if (!m) return;

        const [, ip, time, method, path, status, bytes] = m;
        const filename = path.split('/').pop().split('?')[0] || path;

        entries.push({
          ip,
          time,
          method,
          path,
          filename,
          status: parseInt(status),
          bytes: parseInt(bytes),
        });
      });
    } catch(e) { console.error('Gagal baca nginx log:', e.message); }
    resolve(entries);
  });
}

// ─── Routes ──────────────────────────────────────────────────────────────────

// Halaman monitoring
router.get('/', isAuthenticated, async (req, res) => {
  const logs = await parseNginxLog(100);
  const bandwidth = getBandwidth();

  // Statistik ringkasan dari log
  const stats = {
    totalRequests: logs.length,
    uniqueClients: new Set(logs.map(l => l.ip)).size,
    totalBytes: logs.reduce((s, l) => s + (l.bytes || 0), 0),
    success: logs.filter(l => l.status >= 200 && l.status < 300).length,
    errors: logs.filter(l => l.status >= 400).length,
  };

  // Top file yang paling sering diakses
  const fileCounts = {};
  logs.forEach(l => {
    fileCounts[l.filename] = (fileCounts[l.filename] || 0) + 1;
  });
  const topFiles = Object.entries(fileCounts)
    .sort((a, b) => b[1] - a[1])
    .slice(0, 5)
    .map(([name, count]) => ({ name, count }));

  // Top client IP
  const ipCounts = {};
  logs.forEach(l => { ipCounts[l.ip] = (ipCounts[l.ip] || 0) + 1; });
  const topClients = Object.entries(ipCounts)
    .sort((a, b) => b[1] - a[1])
    .slice(0, 10)
    .map(([ip, count]) => ({ ip, count }));

  res.render('monitor', {
    title: 'Traffic Monitor',
    logs: logs.slice(0, 50),
    bandwidth,
    stats,
    topFiles,
    topClients,
    nginxLog: NGINX_LOG,
  });
});

// ─── API endpoint untuk polling realtime ─────────────────────────────────────
router.get('/api/bandwidth', isAuthenticated, (req, res) => {
  res.json({ bandwidth: getBandwidth(), timestamp: Date.now() });
});

router.get('/api/logs', isAuthenticated, async (req, res) => {
  const logs = await parseNginxLog(50);
  const stats = {
    totalRequests: logs.length,
    uniqueClients: new Set(logs.map(l => l.ip)).size,
    totalBytes: logs.reduce((s, l) => s + (l.bytes || 0), 0),
    success: logs.filter(l => l.status >= 200 && l.status < 300).length,
    errors: logs.filter(l => l.status >= 400).length,
  };
  res.json({ logs: logs.slice(0, 50), stats, timestamp: Date.now() });
});

module.exports = router;