// app-core.js — Auth, navigation, dashboard, WebSocket, processes, logs, sites, domains, SSL, services

var _navToken = 0; // increments on every page change
var API = 'http://localhost:4080';
var TOKEN = localStorage.getItem('sp_token') || '';
var CU = JSON.parse(localStorage.getItem('sp_user') || '{}');
// Restore id from JWT payload if not already in stored user object
if (TOKEN && !CU.id) {
  try {
    var _payload = JSON.parse(atob(TOKEN.split('.')[1]));
    CU.id = _payload.id;
  } catch(e) {}
}
var cpuChart, cpuData = [], cpuLabels = [], wsConn;
var sidebarCollapsed = false;
var dashCache = null, dashCacheTime = 0, dashRefreshTimer = null;
var curFilePath = 'C:\\';
var curPermUserId = null;
var curDbPermUser = '';
var curQueryDb = '';
var dbList = [];
var curLog = 'Application', curLevel = 'All';


var _cache = {
  sites:    { data: null, ts: 0 },
  apppools: { data: null, ts: 0 },
  services: { data: null, ts: 0 },
  dotnet:   { data: null, ts: 0 }
};
var _CACHE_TTL = 30000;

function cacheGet(key) {
  var c = _cache[key];
  return (c && c.data && (Date.now() - c.ts) < _CACHE_TTL) ? c.data : null;
}
function cacheSet(key, data) {
  _cache[key] = { data: data, ts: Date.now() };
}
function cacheClear(key) {
  if (_cache[key]) _cache[key] = { data: null, ts: 0 };
}

var ALL_PERMS = ['dashboard','processes','logs','sites','apppools','domains','ssl','services','database','files','smtp','scheduler','security','traffic','docker','dotnet','users','redirects','backup','git','settings'];
var PERM_LABELS = {
  dashboard: 'Dashboard', processes: 'Task Manager', logs: 'Event Logs',
  sites: 'IIS Sites', domains: 'Domain Manager', ssl: 'SSL Certs',
  services: 'Services', database: 'Databases', files: 'File Manager', smtp: 'SMTP', scheduler: 'Task Scheduler',
  security: 'Security Check', traffic: 'Traffic & IPs', apppools: 'App Pools', dotnet: '.NET Manager', docker: 'Docker Manager', backup: 'Backup Manager', redirects: 'Redirects', git: 'Git Repositories', users: 'Users', settings: 'Settings'
};
var PAGE_TITLES = {
  dashboard: 'Dashboard', processes: 'Task Manager', logs: 'Event Logs',
  sites: 'IIS Sites', domains: 'Domain Manager', ssl: 'SSL Certificates',
  services: 'Windows Services', database: 'Databases', query: 'Query Runner',
  files: 'File Manager', smtp: 'SMTP Configuration',
  scheduler: 'Task Scheduler', backup: 'Backup Manager', redirects: 'Redirects', git:    'Git Repositories',
  security: 'Security Check', users: 'Users & Permissions', settings: 'Settings',
  audit: 'Audit Log', dotnet: '.NET Manager', apppools: 'App Pool Manager', docker: 'Docker Manager', traffic: 'Traffic & IP Manager'
};

// ── Helpers ──────────────────────────────────────────────
function el(id) { return document.getElementById(id); }
function val(id) { return el(id).value; }
function om(id) {
  var modal = el(id);
  if (!modal) return;
  // When opening the upload modal, show the current path
  if (id === 'm-upload') {
    var pathLabel = el('upload-path');
    if (pathLabel) pathLabel.textContent = curFilePath || 'C:\\';
    hideErr('upload-err');
    var msg = el('upload-msg'); if (msg) msg.textContent = '';
    var bar = el('upload-progress'); if (bar) bar.style.display = 'none';
  }
  modal.style.display = 'flex';
}
function cm(id) { el(id).style.display = 'none'; }
function showErr(id, msg) { var e = el(id); e.textContent = msg; e.style.display = 'block'; }
function hideErr(id) { el(id).style.display = 'none'; }

function api(path, method, body) {
  method = method || 'GET';
  var opts = { method: method, headers: { Authorization: 'Bearer ' + TOKEN } };
  if (body) { opts.headers['Content-Type'] = 'application/json'; opts.body = JSON.stringify(body); }
  return fetch(API + path, opts).then(function(r) { return r.json(); });
}

function formatBytes(b) {
  if (!b) return '0 B';
  if (b < 1024) return b + ' B';
  if (b < 1048576) return (b / 1024).toFixed(1) + ' KB';
  if (b < 1073741824) return (b / 1048576).toFixed(1) + ' MB';
  return (b / 1073741824).toFixed(2) + ' GB';
}
function formatDate(d) {
  if (!(d instanceof Date) || isNaN(d)) return '–';
  return d.toLocaleDateString();
}
function formatDateTime(str) {
  if (!str) return '–';
  return new Date(str).toLocaleString();
}

// ── Auth ─────────────────────────────────────────────────
function doLogin() {
  var user = val('inp-user'), pass = val('inp-pass');
  var errEl = el('login-err');
  errEl.style.display = 'none';
  fetch(API + '/api/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username: user, password: pass })
  }).then(function(r) { return r.json(); }).then(function(d) {
    if (!d.token) throw new Error(d.error || 'Login failed');
    TOKEN = d.token;
    CU = { id: d.id, username: d.username, role: d.role, permissions: d.permissions || ALL_PERMS };
    localStorage.setItem('sp_token', TOKEN);
    localStorage.setItem('sp_user', JSON.stringify(CU));
    initApp();
  }).catch(function(e) {
    errEl.textContent = e.message;
    errEl.style.display = 'block';
  });
}

function signOut() {
  TOKEN = ''; CU = {};
  localStorage.removeItem('sp_token');
  localStorage.removeItem('sp_user');
  clearInterval(dashRefreshTimer);
  if (wsConn) wsConn.close();
  el('app').style.display = 'none';
  el('login-screen').style.display = 'flex';
}

function initApp() {
  el('login-screen').style.display = 'none';
  el('app').style.display = 'block';
  el('sb-av').textContent = (CU.username || 'A')[0].toUpperCase();
  el('sb-name').textContent = CU.username || 'Admin';
  el('sb-role').textContent = CU.role === 'admin' ? 'Administrator' : 'Viewer';
  el('role-badge').textContent = CU.role === 'admin' ? 'Admin' : 'Viewer';
  resetNav();
  // Reset content so old page never shows
  el('content').innerHTML = '<div class="loading">Loading...</div>';
  if (CU.role === 'admin') {
    var dash = document.querySelector('.nav-item[data-page="dashboard"]');
    showPage('dashboard', dash);
  } else {
    applyNavPermissions();
  }
  startWS();
  startRestartDetection();
}

function resetNav() {
  // Restore all nav items and sections to visible (needed on re-login with different user)
  document.querySelectorAll('.nav-item[data-page]').forEach(function(n) {
    n.style.display = '';
  });
  document.querySelectorAll('.nav-section').forEach(function(n) {
    n.style.display = '';
  });
}

function applyNavPermissions() {
  if (CU.role === 'admin') return;
  var perms = CU.permissions || [];

  document.querySelectorAll('.nav-item[data-page]').forEach(function(n) {
    n.style.display = perms.includes(n.dataset.page) ? '' : 'none';
  });

  document.querySelectorAll('.nav-section').forEach(function(section) {
    var next = section.nextElementSibling;
    var hasVisible = false;
    while (next && !next.classList.contains('nav-section')) {
      if (next.classList.contains('nav-item') && next.style.display !== 'none') {
        hasVisible = true; break;
      }
      next = next.nextElementSibling;
    }
    section.style.display = hasVisible ? '' : 'none';
  });

  var firstAllowed = document.querySelector('.nav-item[data-page]:not([style*="none"])');
  if (firstAllowed) showPage(firstAllowed.dataset.page, firstAllowed);
}
function hasPerm(p) { return CU.role === 'admin' || (CU.permissions || []).includes(p); }

// ── Sidebar ───────────────────────────────────────────────
function toggleSidebar() {
  sidebarCollapsed = !sidebarCollapsed;
  el('sidebar').classList.toggle('collapsed', sidebarCollapsed);
  document.querySelector('.collapse-btn').textContent = sidebarCollapsed ? '»' : '«';
}

// ── Navigation ────────────────────────────────────────────
function showPage(page, elem) {
  checkServerRestart(); 
  if (!hasPerm(page) && page !== 'dashboard') 
    { 
    el('content').innerHTML = '<div class="placeholder"><div class="ph-icon">🔒</div><h3>Access denied</h3><p>You do not have permission to view this page.</p></div>';
    return;
  }
  
  document.querySelectorAll('.nav-item').forEach(function(n) { n.classList.remove('active'); });
  if (elem) elem.classList.add('active');
  el('page-title').textContent = PAGE_TITLES[page] || page;
  el('topbar-sub').textContent = new Date().toLocaleString();
  clearInterval(dashRefreshTimer);
  // Always clear content first so old page never bleeds through
  var c = el('content');
var _myToken = _navToken;
c.innerHTML = '<div class="loading">Loading...</div>';
c._token = _myToken;   // stamp the container
  if      (page === 'dashboard') loadDashboard(c);
  else if (page === 'processes') loadProcesses(c);
  else if (page === 'logs')      loadLogs(c);
  else if (page === 'sites')     loadSites(c);
  else if (page === 'domains')   loadDomains(c);
  else if (page === 'ssl')       loadSSL(c);
  else if (page === 'services')  loadServices(c);
  else if (page === 'database')  loadDatabase(c);
  else if (page === 'files')     loadFiles(c, curFilePath);
  else if (page === 'smtp')      loadSMTP(c);
  else if (page === 'users')     loadUsers(c);
  else if (page === 'settings')  loadSettings(c);
  else if (page === 'security')  loadSecurity(c);
  else if (page === 'scheduler') loadScheduler(c);
  else if (page === 'audit')     loadAuditLog(c);
  else if (page === 'apppools')  loadAppPools(c);
  else if (page === 'traffic')   loadTraffic(c);
  else if (page === 'dotnet') loadDotnet(c);
  else if (page === 'docker') loadDocker(c);
  else if (page === 'backup') loadBackup(c);
  else if (page === 'git') loadGit(c);
  else if (page === 'redirects') loadRedirects(c);
_navToken++;          // ← add this line
}

// Safe write — only renders if page hasn't changed since request started
function safeRender(html) {
  var c = el('content');
  if (c._token !== _navToken) return; // stale — discard
  c.innerHTML = html;
}

// ── Dashboard ─────────────────────────────────────────────
function loadDashboard(c) {
  // Show skeleton immediately
  c.innerHTML =
    '<div class="stat-grid">' +
      ['CPU Usage','RAM Usage','Hostname','Uptime'].map(function(lbl) {
        return '<div class="stat-card">' +
          '<div class="lbl" style="margin-bottom:8px">' + lbl + '</div>' +
          '<div class="sk-line" style="width:55%;height:22px;margin-bottom:8px"></div>' +
          '<div class="sk-line" style="width:70%"></div>' +
        '</div>';
      }).join('') +
    '</div>' +
    '<div class="two-col">' +
      '<div class="card"><h3>CPU — live</h3>' +
        '<div style="position:relative;height:140px;display:flex;align-items:center;justify-content:center">' +
          '<div class="sk-line" style="width:90%;height:120px;border-radius:8px"></div>' +
        '</div>' +
      '</div>' +
      '<div class="card"><h3>Memory & disk</h3>' +
        '<div class="sk-line" style="width:100%;margin-bottom:8px"></div>' +
        '<div class="sk-line" style="width:100%;height:8px;margin-bottom:16px"></div>' +
        '<div class="sk-line" style="width:100%;margin-bottom:8px"></div>' +
        '<div class="sk-line" style="width:100%;height:8px;margin-bottom:16px"></div>' +
        '<div class="sk-line" style="width:60%;margin-bottom:8px"></div>' +
        '<div class="sk-line" style="width:100%;height:8px"></div>' +
      '</div>' +
    '</div>';

  if (dashCache) renderDashboard(c, dashCache);
  fetchDash(c);
  clearInterval(dashRefreshTimer);
  dashRefreshTimer = setInterval(function() { fetchDash(c); }, 10000);
}

function fetchDash(c) {
  return api('/api/system/stats').then(function(s) {
    dashCache = s;
    dashCacheTime = Date.now();
    renderDashboard(c, s);
  }).catch(function() {
    if (!dashCache) {
      c.innerHTML = '<div class="err-box">Could not reach backend. Is the server running?</div>';
    }
  });
}

function renderDashboard(c, s) {
  var ru = parseFloat(s.ram && s.ram.used) || 0;
  var rt = parseFloat(s.ram && s.ram.total) || 1;
  console.log('Dashboard data:', s.cpu, ru, rt, s.disks);

  var rp = Math.round(ru / rt * 100);
  var rc = rp > 85 ? '#ef4444' : rp > 65 ? '#f59e0b' : '#10b981';
  var cpu = parseInt(s.cpu) || 0;
  var diskRows = '';
  (s.disks || []).forEach(function(d) {
    var p = d.total ? Math.round(d.used / d.total * 100) : 0;
    var col = p > 85 ? '#ef4444' : p > 65 ? '#f59e0b' : '#10b981';
    diskRows += '<div class="prog-label"><span>' + d.label + ' — ' + p + '%</span><span>' + d.used + '/' + d.total + ' GB</span></div>' +
                '<div class="prog-track"><div class="prog-fill" style="width:' + p + '%;background:' + col + '"></div></div>';
  });
  var hadChart = !!el('cpu-chart');
  c.innerHTML =
    '<div class="stat-grid">' +
      '<div class="stat-card"><div class="lbl">CPU Usage</div><div class="val" id="cpu-val" style="color:' + (cpu > 80 ? '#ef4444' : '#0f172a') + '">' + cpu + '%</div><div class="sub">Live via WebSocket</div></div>' +
      '<div class="stat-card"><div class="lbl">RAM Usage</div><div class="val" id="ram-val">' + rp + '%</div><div class="sub" id="ram-sub">' + ru + ' / ' + rt + ' GB</div></div>' +
      '<div class="stat-card"><div class="lbl">Hostname</div><div class="val" style="font-size:14px">' + (s.hostname || '–') + '</div><div class="sub">' + (s.os || 'Windows Server') + '</div></div>' +
      '<div class="stat-card"><div class="lbl">Uptime</div><div class="val" style="font-size:14px">' + (s.uptime || '–') + '</div><div class="sub">Since last reboot</div></div>' +
    '</div>' +
    '<div class="two-col">' +
      '<div class="card"><h3>CPU — live</h3><div style="position:relative;height:140px"><canvas id="cpu-chart"></canvas></div></div>' +
      '<div class="card"><h3>Memory & disk</h3>' +
        '<div class="prog-label"><span>RAM — <span id="ram-pct">' + rp + '</span>%</span><span id="ram-detail">' + ru + '/' + rt + ' GB</span></div>' +
        '<div class="prog-track"><div class="prog-fill" id="ram-bar" style="width:' + rp + '%;background:' + rc + '"></div></div>' +
        '<div id="disk-rows">' + diskRows + '</div>' +
        '<div class="net-row">' +
          '<div class="net-item"><div class="nl">Network ↑ sent</div><div class="nv" id="net-sent">' + ((s.network && s.network.sent != null) ? s.network.sent + ' KB/s' : '–') + '</div></div>' +
          '<div class="net-item"><div class="nl">Network ↓ recv</div><div class="nv" id="net-recv">' + ((s.network && s.network.received != null) ? s.network.received + ' KB/s' : '–') + '</div></div>' +
        '</div>' +
      '</div>' +
    '</div>';
  if (!hadChart) initCpuChart(cpu);
}
function initCpuChart(v) {
  cpuData = []; cpuLabels = [];
  for (var i = 0; i < 30; i++) { cpuData.push(v); cpuLabels.push(''); }
  var ctx = el('cpu-chart'); if (!ctx) return;
  if (cpuChart) cpuChart.destroy();
  cpuChart = new Chart(ctx, {
    type: 'line',
    data: { labels: cpuLabels, datasets: [{ data: cpuData, borderColor: '#3b82f6', backgroundColor: 'rgba(59,130,246,0.1)', fill: true, tension: 0.3, pointRadius: 0, borderWidth: 1.5 }] },
    options: {
      animation: false, responsive: true, maintainAspectRatio: false,
      scales: { y: { min: 0, max: 100, ticks: { font: { size: 10 }, color: '#94a3b8' }, grid: { color: '#f1f5f9' } }, x: { display: false } },
      plugins: { legend: { display: false }, tooltip: { callbacks: { label: function(c) { return c.raw + '%'; } } } }
    }
  });
}
function startWS() {
  try {
    wsConn = new WebSocket('ws://localhost:4080');
    wsConn.onmessage = function(e) {
      try {
        var d = JSON.parse(e.data);
        if (d.type === 'stats') {
          updateLiveStats(d);
        } else if (d.type === 'cpu') {
          updateCpuVal(d.value);
        }
      } catch(err) {}
    };
    wsConn.onerror = function() {};
    wsConn.onclose = function() { setTimeout(startWS, 3000); };
  } catch(e) {}
}

function updateRamWidget(ram) {
  if (!ram || !ram.total) return; 
  var used = parseFloat(ram.used) || 0;
  var total = parseFloat(ram.total) || 1;
  var pct = Math.round(used / total * 100);
  // update stat card if visible
  var cards = document.querySelectorAll('.stat-card .val');
  if (cards[1]) cards[1].textContent = pct + '%';
}

function updateLiveStats(d) {
  // CPU
  if (!el('cpu-val')) return;  // ← add this line
  if (d.cpu != null) updateCpuVal(d.cpu);

  // RAM stat card
  var ru = parseFloat(d.ram && d.ram.used) || 0;
  var rt = parseFloat(d.ram && d.ram.total) || 1;
  var rp = Math.round(ru / rt * 100);
  var rc = rp > 85 ? '#ef4444' : rp > 65 ? '#f59e0b' : '#10b981';
  var ramVal = el('ram-val'), ramSub = el('ram-sub'), ramPct = el('ram-pct'), ramDetail = el('ram-detail'), ramBar = el('ram-bar');
  if (ramVal)    { ramVal.textContent = rp + '%'; }
  if (ramSub)    { ramSub.textContent = ru + ' / ' + rt + ' GB'; }
  if (ramPct)    { ramPct.textContent = rp; }
  if (ramDetail) { ramDetail.textContent = ru + '/' + rt + ' GB'; }
  if (ramBar)    { ramBar.style.width = rp + '%'; ramBar.style.background = rc; }

  // Disks
  var diskContainer = el('disk-rows');
  if (diskContainer && Array.isArray(d.disks) && d.disks.length) {
    var html = '';
    d.disks.forEach(function(disk) {
      var p = disk.total ? Math.round(disk.used / disk.total * 100) : 0;
      var col = p > 85 ? '#ef4444' : p > 65 ? '#f59e0b' : '#10b981';
      html += '<div class="prog-label"><span>' + disk.label + ' — ' + p + '%</span><span>' + disk.used + '/' + disk.total + ' GB</span></div>' +
              '<div class="prog-track"><div class="prog-fill" style="width:' + p + '%;background:' + col + '"></div></div>';
    });
    diskContainer.innerHTML = html;
  }

  // Network
  if (d.network) {
    var ns = el('net-sent'), nr = el('net-recv');
    if (ns) ns.textContent = (d.network.sent != null ? d.network.sent : '–') + ' KB/s';
    if (nr) nr.textContent = (d.network.received != null ? d.network.received : '–') + ' KB/s';
  }
}

function updateCpuVal(val) {
  var e = el('cpu-val');
  if (e) { e.textContent = val + '%'; e.style.color = val > 80 ? '#ef4444' : '#0f172a'; }
  if (cpuChart) {
    cpuData.push(val); cpuData.shift();
    cpuLabels.push(''); cpuLabels.shift();
    cpuChart.data.datasets[0].data = cpuData.slice();
    cpuChart.update('none');
  }
}

// Keep old name as alias so any other callers still work
function updateCpu(val) { updateCpuVal(val); }


function killProc(pid, name) {
  if (!confirm('Kill process "' + name + '" (PID ' + pid + ')?')) return;
  api('/api/processes/' + pid, 'DELETE').then(function() { loadProcesses(el('content')); });
}

// ── Event Logs ────────────────────────────────────────────
var curLogMode = 'system';
function loadLogs(c) {
  c.innerHTML =
    '<div class="card">' +
      '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:14px;flex-wrap:wrap;gap:10px">' +
        '<div class="tabs" style="margin-bottom:0;border-bottom:none">' +
          '<div class="tab' + (curLogMode==="system"?" active":"") + '" onclick="switchLogMode(\'system\')">📋 System Logs</div>' +
          '<div class="tab' + (curLogMode==="crash"?" active":"") + '" onclick="switchLogMode(\'crash\')" style="color:#dc2626">💥 Crash Logs</div>' +
          (CU.role === 'admin' ? '<div class="tab' + (curLogMode==="audit"?" active":"") + '" onclick="switchLogMode(\'audit\')" style="color:#7c3aed">🗒 Audit Log</div>' : '') +
        '</div>' +
        '<div id="log-controls" style="display:flex;gap:8px;align-items:center"></div>' +
      '</div>' +
      '<div id="log-body"></div>' +
    '</div>';
  renderLogControls();
  fetchLogs();
}
function switchLogMode(mode) {
  curLogMode = mode;
  document.querySelectorAll('.tab').forEach(function(t) {
    var txt = t.textContent.trim();
    t.classList.toggle('active',
      (mode==='system' && txt.includes('System')) ||
      (mode==='crash'  && txt.includes('Crash'))  ||
      (mode==='audit'  && txt.includes('Audit')));
  });
  renderLogControls();
  fetchLogs();
}
function renderLogControls() {
  var ctrl = el('log-controls'); if (!ctrl) return;
  if (curLogMode === 'system') {
    ctrl.innerHTML =
      '<div class="tabs" style="margin-bottom:0;border-bottom:none;margin-right:4px">' +
        ['Application','System','Security'].map(function(l) {
          return '<div class="tab' + (l===curLog?' active':'') + '" onclick="switchLog(\'' + l + '\')">' + l + '</div>';
        }).join('') +
      '</div>' +
      '<select id="log-level" onchange="curLevel=this.value;fetchLogs()" style="font-size:12px;padding:5px 8px;border:1px solid #e2e8f0;border-radius:6px">' +
        ['All','Error','Warning','Information'].map(function(l) {
          return '<option value="' + l + '"' + (l===curLevel?' selected':'') + '>' + l + '</option>';
        }).join('') +
      '</select>' +
      '<button class="btn-neutral" onclick="fetchLogs()">↻ Refresh</button>';
  } else if (curLogMode === 'crash') {
    ctrl.innerHTML =
      '<button class="btn-neutral" onclick="fetchLogs()">↻ Refresh</button>' +
      '<button class="btn-danger" onclick="clearCrashLogs()">🗑 Clear</button>';
  } else {
    // audit mode — controls rendered inside loadAuditLog
    ctrl.innerHTML = '';
  }
}
function switchLog(log) {
  curLog = log;
  document.querySelectorAll('.tab').forEach(function(t) {
    if (['Application','System','Security'].includes(t.textContent.trim()))
      t.classList.toggle('active', t.textContent.trim()===log);
  });
  fetchLogs();
}
function fetchLogs() {
  var body = el('log-body'); if (!body) return;
  if (curLogMode === 'audit') {
    fetchAuditInline(body);
    return;
  }
  body.innerHTML = '<div class="loading">Loading logs...</div>';
  if (curLogMode === 'crash') {
    api('/api/logs/crash').then(function(list) {
      if (list.error) { body.innerHTML = '<div class="err-box">' + list.error + '</div>'; return; }
      if (!list.length) {
        body.innerHTML = '<div style="text-align:center;padding:40px 0"><div style="font-size:32px;margin-bottom:12px">✅</div><p style="color:#64748b;font-size:13px">No crashes recorded. System is healthy.</p></div>';
        return;
      }
      var rows = list.map(function(e) {
        var isUnhandled = e.type === 'unhandledRejection';
        var bc = isUnhandled ? 'warn' : 'error';
        var label = isUnhandled ? 'Rejection' : 'Exception';
        var msg = (e.message||'').replace(/</g,'<').substring(0,200);
        var stack = e.stack ? '<details style="margin-top:6px"><summary style="cursor:pointer;font-size:11px;color:#94a3b8">Stack trace</summary><pre style="font-size:11px;white-space:pre-wrap;color:#64748b;margin-top:6px">' + e.stack.replace(/</g,'<').substring(0,1000) + '</pre></details>' : '';
        return '<tr><td style="font-size:11px;white-space:nowrap;color:#64748b;vertical-align:top">' + (e.time||'') + '</td>' +
          '<td style="vertical-align:top"><span class="badge ' + bc + '">' + label + '</span></td>' +
          '<td style="font-size:12px;color:#0f172a;vertical-align:top"><div>' + msg + '</div>' + stack + '</td></tr>';
      }).join('');
      body.innerHTML = '<table><thead><tr><th>Time</th><th>Type</th><th>Details</th></tr></thead><tbody>' + rows + '</tbody></table>';
    }).catch(function() { body.innerHTML = '<div class="err-box">Could not load crash logs.</div>'; });
  } else {
    api('/api/logs?log=' + curLog + '&level=' + (curLevel==='All'?'':curLevel) + '&count=100').then(function(list) {
      if (list.error) { body.innerHTML = '<div class="err-box">' + list.error + '</div>'; return; }
      if (!Array.isArray(list)) list = [list];
      var rows = list.map(function(e) {
        var lvl = (e.EntryType||'').toString();
        var bc = lvl==='Error'?'error':lvl==='Warning'?'warn':'info';
        var msg = (e.Message||'').substring(0,150).replace(/</g,'<');
        return '<tr>' +
          '<td style="font-size:11px;white-space:nowrap;color:#64748b">' + formatDateTime(e.TimeGenerated) + '</td>' +
          '<td><span class="badge ' + bc + '">' + lvl + '</span></td>' +
          '<td class="b" style="font-size:12px">' + (e.Source||'') + '</td>' +
          '<td class="m">' + (e.EventID||'') + '</td>' +
          '<td style="font-size:12px;color:#475569;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">' + msg + '</td>' +
        '</tr>';
      }).join('');
      body.innerHTML = rows
        ? '<table><thead><tr><th>Time</th><th>Level</th><th>Source</th><th>Event ID</th><th>Message</th></tr></thead><tbody>' + rows + '</tbody></table>'
        : '<p style="color:#94a3b8;font-size:13px;padding:12px 0">No entries found.</p>';
    }).catch(function() { body.innerHTML = '<div class="err-box">Could not load system logs.</div>'; });
  }
}
function clearCrashLogs() {
  if (!confirm('Clear all crash logs? This cannot be undone.')) return;
  api('/api/logs/crash', 'DELETE').then(function() { fetchLogs(); });
}

// ── Audit log inline (rendered inside the Logs page tab) ──
var _auditCache = [];
function fetchAuditInline(body) {
  body.innerHTML = '<div class="loading">Loading audit log...</div>';
  var ctrl = el('log-controls');
  if (ctrl) {
    ctrl.innerHTML =
      '<input id="audit-q" placeholder="Search..." oninput="filterAuditInline()" ' +
        'style="font-size:12px;padding:5px 8px;border:1px solid #e2e8f0;border-radius:6px;width:140px" />' +
      '<button class="btn-neutral" onclick="fetchLogs()">&#8635; Refresh</button>' +
      (CU.role === 'admin' ? '<button class="btn-danger" onclick="clearAuditInline()">&#128465; Clear</button>' : '');
  }
  api('/api/audit?limit=500').then(function(list) {
    _auditCache = list;
    renderAuditInline(body, list);
  }).catch(function() {
    body.innerHTML = '<div class="err-box">Could not load audit log. Ensure <code>routes/audit.js</code> is deployed and <code>server.js</code> mounts <code>/api/audit</code>.</div>';
  });
}

function filterAuditInline() {
  var q = (el('audit-q') || {}).value || '';
  var lq = q.toLowerCase();
  var filtered = q ? _auditCache.filter(function(e) {
    return (e.action || '').toLowerCase().includes(lq) ||
           (e.detail || '').toLowerCase().includes(lq) ||
           (e.user  || '').toLowerCase().includes(lq);
  }) : _auditCache;
  renderAuditInline(el('log-body'), filtered);
}

var _AUDIT_COLORS = {
  'auth.login': 'info', 'auth.login.fail': 'error',
  'users.create': 'info', 'users.delete': 'error',
  'users.password': 'warn', 'users.permissions': 'warn',
  'files.upload': 'info', 'files.download': 'info',
  'files.delete': 'error', 'files.mkdir': 'info',
  'sites.create': 'info', 'sites.start': 'running',
  'sites.stop': 'stopped', 'sites.restart': 'warn',
  'sites.edit': 'warn', 'sites.backup': 'info',
  'audit.clear': 'error'
};

function renderAuditInline(body, list) {
  if (!body) return;
  if (!list || !list.length) {
    body.innerHTML = '<div style="text-align:center;padding:40px 0">' +
      '<div style="font-size:32px;margin-bottom:12px">&#128203;</div>' +
      '<p style="color:#64748b;font-size:13px">No audit entries yet. Actions taken in the panel will appear here.</p></div>';
    return;
  }
  var rows = list.map(function(e) {
    var bc = _AUDIT_COLORS[e.action] || 'info';
    var timeStr = e.time ? new Date(e.time).toLocaleString() : '';
    var detail = (e.detail || '').replace(/</g, '&lt;');
    return '<tr>' +
      '<td style="font-size:11px;white-space:nowrap;color:#64748b">' + timeStr + '</td>' +
      '<td class="b" style="font-size:12px">' + (e.user || '&ndash;') + '</td>' +
      '<td style="font-size:11px;color:#94a3b8;font-family:monospace">' + (e.ip || '&ndash;') + '</td>' +
      '<td><span class="badge ' + bc + '" style="font-family:monospace;font-size:10px;white-space:nowrap">' + (e.action || '') + '</span></td>' +
      '<td style="font-size:12px;color:#475569;max-width:300px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + detail + '">' + detail + '</td>' +
    '</tr>';
  }).join('');
  body.innerHTML =
    '<table><thead><tr><th>Time</th><th>User</th><th>IP</th><th>Action</th><th>Detail</th></tr></thead>' +
    '<tbody>' + rows + '</tbody></table>' +
    '<p style="font-size:11px;color:#94a3b8;margin-top:10px;text-align:right">' + list.length + ' entries</p>';
}

function clearAuditInline() {
  if (!confirm('Clear the entire audit log? This cannot be undone.')) return;
  api('/api/audit', 'DELETE').then(function(r) {
    if (r.ok) fetchLogs();
    else alert('Failed: ' + (r.error || 'Unknown'));
  });
}

// ── IIS Sites ─────────────────────────────────────────────
// ── Skeleton HTML helper ───────────────────────────────────
function skeletonRows(cols, rows) {
  rows = rows || 5;
  var widths = ['40%','60%','30%','50%','20%','45%','55%','35%'];
  var tr = '';
  for (var i = 0; i < rows; i++) {
    var td = '';
    for (var j = 0; j < cols; j++) {
      td += '<td><div class="sk-line" style="width:' + widths[(i+j)%widths.length] + '"></div></td>';
    }
    tr += '<tr>' + td + '</tr>';
  }
  return tr;
}

function skeletonTable(headers, rows) {
  var th = headers.map(function(h){ return '<th>' + h + '</th>'; }).join('');
  return '<table><thead><tr>' + th + '</tr></thead><tbody>' + skeletonRows(headers.length, rows||5) + '</tbody></table>';
}

function siteAction(name, action) {
  api('/api/sites/' + encodeURIComponent(name) + '/' + action, 'POST')
    .then(function(r) { if (r.error) alert(r.error); loadSites(el('content')); });
}
function openEditSite(name, port, host, sitePath) {
  el('es-name').textContent = name;
  el('es-port').value  = port;
  el('es-host').value  = host;
  el('es-path').value  = sitePath;
  el('es-pool').value  = '';
  el('es-err').style.display = 'none';
  el('m-editsite')._siteName = name;
  om('m-editsite');
}
function submitEditSite() {
  var name = el('m-editsite')._siteName;
  var body = { port: val('es-port'), hostname: val('es-host'), path: val('es-path'), pool: val('es-pool') };
  api('/api/sites/' + encodeURIComponent(name), 'PUT', body).then(function(r) {
    if (r.error) { showErr('es-err', r.error); return; }
    cm('m-editsite'); loadSites(el('content'));
  }).catch(function(e) { showErr('es-err', e.message); });
}
function backupSite(name, evt) {
  var btn = evt.target; btn.textContent = 'Backing up...'; btn.disabled = true;
  api('/api/sites/' + encodeURIComponent(name) + '/backup', 'POST').then(function(r) {
    btn.textContent = 'Backup'; btn.disabled = false;
    if (r.error) { alert('Backup failed: ' + r.error); return; }
    alert('Backup created: ' + r.file + ' (' + formatBytes(r.size) + ')');
    showBackups();
  }).catch(function(e) { btn.textContent = 'Backup'; btn.disabled = false; alert('Error: ' + e.message); });
}
function showBackups() {
  var p = el('backup-panel'); if (!p) return;
  p.innerHTML = '<div class="loading">Loading backups...</div>';
  api('/api/sites/backups').then(function(list) {
    if (!list.length) {
      p.innerHTML = '<div class="card"><h3>Backups</h3><p style="color:#94a3b8;font-size:13px">No backups yet. Use the Backup button next to a site.</p></div>';
      return;
    }
    var rows = list.map(function(b) {
      return '<tr><td class="b">' + b.name + '</td><td>' + formatBytes(b.size) + '</td>' +
        '<td style="font-size:12px;color:#94a3b8">' + formatDateTime(b.created) + '</td></tr>';
    }).join('');
    p.innerHTML = '<div class="card"><h3>Backups <span style="font-size:12px;color:#94a3b8;font-weight:normal">(stored in data/backups/)</span></h3>' +
      '<table><thead><tr><th>File</th><th>Size</th><th>Created</th></tr></thead><tbody>' + rows + '</tbody></table></div>';
  }).catch(function() { p.innerHTML = '<div class="err-box">Could not load backups.</div>'; });
}
function installIIS() {
  var b = el('iis-btn'); b.textContent = 'Installing... please wait'; b.disabled = true;
  api('/api/sites/iis-install', 'POST').then(function(r) {
    if (r.ok) { alert('IIS installed successfully!'); loadSites(el('content')); }
    else { alert('Failed: ' + (r.error || 'Unknown error')); b.textContent = 'Install IIS now'; b.disabled = false; }
  }).catch(function(e) { alert('Error: ' + e.message); b.textContent = 'Install IIS now'; b.disabled = false; });
}
function submitAddSite() {
  hideErr('ns-err');
  var name = val('ns-name').trim(), port = val('ns-port').trim(),
      sitePath = val('ns-path').trim(), pool = val('ns-pool').trim(),
      hostname = val('ns-host').trim(),
      createFolder = el('ns-autocreate').checked;
  if (!name || !port || !sitePath) { showErr('ns-err', 'Site name, port and path are required'); return; }
  api('/api/sites/create', 'POST', { name:name, port:port, path:sitePath, pool:pool, hostname:hostname, createFolder:createFolder }).then(function(r) {
    if (r.error) { showErr('ns-err', r.error); return; }
    cm('m-addsite'); loadSites(el('content'));
  }).catch(function(e) { showErr('ns-err', e.message); });
}

function openAddDomain() {
  api('/api/domains/sites').then(function(sites) {
    if (!Array.isArray(sites)) sites = [sites];
    el('nd-site').innerHTML = sites.map(function(s) { return '<option>' + (s.Name || s.name || s) + '</option>'; }).join('');
  }).catch(function() {});
  hideErr('nd-err'); om('m-adddomain');
}
function removeDomain(site, binding, proto) {
  if (!confirm('Remove binding "' + binding + '" from site "' + site + '"?')) return;
  api('/api/domains?site=' + encodeURIComponent(site) + '&binding=' + encodeURIComponent(binding) + '&protocol=' + proto, 'DELETE').then(function() { loadDomains(el('content')); });
}
function submitAddDomain() {
  hideErr('nd-err');
  var site = val('nd-site'), proto = val('nd-proto'), ip = val('nd-ip'), port = val('nd-port'), host = val('nd-host');
  if (!site || !port) { showErr('nd-err', 'Site and port are required'); return; }
  api('/api/domains', 'POST', { site: site, protocol: proto, ip: ip, port: parseInt(port), hostname: host }).then(function(r) {
    if (r.error) { showErr('nd-err', r.error); return; }
    cm('m-adddomain'); loadDomains(el('content'));
  }).catch(function(e) { showErr('nd-err', e.message); });
}

// ═══════════════════════════════════════════════════════════
// SKELETON HELPERS
// ═══════════════════════════════════════════════════════════
function skeletonLine(w) {
  return '<div class="sk-line" style="width:' + (w||'60%') + '"></div>';
}
function skeletonRows(cols, rows) {
  var ws = ['40%','65%','30%','55%','45%','70%','35%','50%'];
  var out = '';
  for (var i = 0; i < (rows||5); i++) {
    var tds = '';
    for (var j = 0; j < cols; j++) {
      tds += '<td style="padding:11px 8px">' + skeletonLine(ws[(i*cols+j) % ws.length]) + '</td>';
    }
    out += '<tr>' + tds + '</tr>';
  }
  return out;
}
function skeletonTable(headers, rows) {
  var th = headers.map(function(h){ return '<th>' + h + '</th>'; }).join('');
  return '<table><thead><tr>' + th + '</tr></thead><tbody>' +
    skeletonRows(headers.length, rows||5) + '</tbody></table>';
}
function skeletonCard(title, headers, rows, btns) {
  return '<div class="card">' +
    '<div class="search-row" style="margin-bottom:14px">' +
      '<h3>' + title + '</h3>' +
      '<div style="display:flex;gap:8px">' + (btns||'') + '</div>' +
    '</div>' +
    skeletonTable(headers, rows||5) +
  '</div>';
}

// ═══════════════════════════════════════════════════════════
// SERVER SECTION
// ═══════════════════════════════════════════════════════════

// ── Task Manager ───────────────────────────────────────────
var _procPage = 1;
var _procPerPage = 20;

function loadProcesses(c, page) {
  _procPage = page || 1;

  // Show skeleton on first load, loading indicator on page change
  if (_procPage === 1) {
    c.innerHTML = skeletonCard('Task Manager',
      ['Process','PID','CPU (s)','Memory','Handles','Action'], 5,
      '<button class="btn-neutral" disabled>↻ Refresh</button>');
  } else {
    // Keep existing table, just show loading on tbody
    var tbody = el('proc-tbody');
    if (tbody) {
      tbody.innerHTML = '<tr><td colspan="6" style="text-align:center;padding:20px 0">' +
        '<div class="sk-line" style="width:60%;margin:0 auto"></div></td></tr>';
    }
  }

  api('/api/processes?page=' + _procPage + '&per=' + _procPerPage).then(function(res) {
    if (res.error) { c.innerHTML = '<div class="err-box">' + res.error + '</div>'; return; }
    renderProcesses(c, res);
  }).catch(function(e) {
    c.innerHTML = '<div class="err-box">Could not load processes: ' + e.message + '</div>';
  });
}

function renderProcesses(c, res) {
  var data       = res.data       || [];
  var total      = res.total      || 0;
  var totalPages = res.totalPages || 1;
  var start      = (_procPage - 1) * _procPerPage;

  var rows = data.map(function(p) {
    var cpu = p.CPU ? (typeof p.CPU === 'number' ? p.CPU.toFixed(1) : p.CPU) : '0.0';
    return '<tr>' +
      '<td class="b">'  + (p.ProcessName||'') + '</td>' +
      '<td class="m">'  + (p.Id||'')          + '</td>' +
      '<td>'            + cpu                  + '</td>' +
      '<td>'            + formatBytes(p.WorkingSet||0) + '</td>' +
      '<td>'            + (p.Handles||0)       + '</td>' +
      '<td><button class="btn-danger" onclick="killProc(' + p.Id + ',\'' + p.ProcessName + '\')">Kill</button></td>' +
    '</tr>';
  }).join('');

  // Pagination
  var pager = '';
  if (totalPages > 1) {
    var prev = _procPage > 1
      ? '<button onclick="loadProcesses(el(\'content\'),' + (_procPage-1) + ')" ' +
          'style="padding:4px 10px;border-radius:6px;border:1px solid #e2e8f0;background:#fff;color:#64748b;cursor:pointer;font-size:12px">← Prev</button>'
      : '<button disabled style="padding:4px 10px;border-radius:6px;border:1px solid #e2e8f0;background:#f8fafc;color:#cbd5e1;font-size:12px">← Prev</button>';

    var next = _procPage < totalPages
      ? '<button onclick="loadProcesses(el(\'content\'),' + (_procPage+1) + ')" ' +
          'style="padding:4px 10px;border-radius:6px;border:1px solid #e2e8f0;background:#fff;color:#64748b;cursor:pointer;font-size:12px">Next →</button>'
      : '<button disabled style="padding:4px 10px;border-radius:6px;border:1px solid #e2e8f0;background:#f8fafc;color:#cbd5e1;font-size:12px">Next →</button>';

    var pageInfo = '<span style="font-size:12px;color:#64748b">Page ' + _procPage + ' of ' + totalPages + '</span>';
    var countInfo = '<span style="font-size:12px;color:#94a3b8">Showing ' + (start+1) + '–' + Math.min(start+_procPerPage, total) + ' of ' + total + ' processes</span>';

    pager =
      '<div style="display:flex;align-items:center;justify-content:space-between;margin-top:14px;flex-wrap:wrap;gap:8px">' +
        countInfo +
        '<div style="display:flex;align-items:center;gap:8px">' + prev + pageInfo + next + '</div>' +
      '</div>';
  }

  c.innerHTML =
    '<div class="card">' +
      '<div class="search-row">' +
        '<h3>Task Manager <span style="font-size:12px;font-weight:400;color:#94a3b8">sorted by CPU</span></h3>' +
        '<button class="btn-neutral" onclick="loadProcesses(el(\'content\'),1)">↻ Refresh</button>' +
      '</div>' +
      '<table><thead><tr>' +
        '<th>Process</th><th>PID</th><th>CPU (s)</th><th>Memory</th><th>Handles</th><th>Action</th>' +
      '</tr></thead>' +
      '<tbody id="proc-tbody">' + rows + '</tbody>' +
      '</table>' +
      pager +
    '</div>';
}

function killProc(pid, name) {
  if (!confirm('Kill "' + name + '" (PID ' + pid + ')?')) return;
  api('/api/processes/' + pid, 'DELETE').then(function() {
    loadProcesses(el('content'), _procPage);
  });
}

// ═══════════════════════════════════════════════════════════
// HOSTING SECTION
// ═══════════════════════════════════════════════════════════

// ── IIS Sites ──────────────────────────────────────────────
function loadSites(c) {
  // Show skeleton immediately
  c.innerHTML = skeletonCard('IIS Sites',
    ['Site name','Status','Binding','Physical path','Actions'], 4,
    '<button class="btn-neutral" disabled>↻ Refresh</button>' +
    '<button class="btn-primary" disabled>+ Add site</button>');

  // Check cache
  var cached = cacheGet('sites');
  if (cached) { renderSitesList(c, cached); return; }

  // Fetch IIS status first, then sites
  api('/api/sites/iis-status').then(function(s) {
    if (!s.installed) {
      c.innerHTML =
        '<div class="placeholder">' +
          '<div class="ph-icon">⚠️</div>' +
          '<h3>IIS is not installed</h3>' +
          '<p style="margin-bottom:20px">Internet Information Services was not found.</p>' +
          '<button class="btn-primary" id="iis-btn" onclick="installIIS()">Install IIS now</button>' +
          '<p style="margin-top:12px;font-size:11px;color:#94a3b8">Installs Web-Server, ASP.NET 4.5 and Management Console</p>' +
        '</div>';
      return;
    }
    api('/api/sites').then(function(sites) {
      if (!Array.isArray(sites)) sites = [sites];
      cacheSet('sites', sites);
      renderSitesList(c, sites);
    }).catch(function(e) {
      c.innerHTML = '<div class="err-box">Could not load IIS sites: ' + e.message + '</div>';
    });
  }).catch(function(e) {
    c.innerHTML = '<div class="err-box">Cannot reach backend: ' + e.message + '</div>';
  });
}

function renderSitesList(c, sites) {
  var rows = sites.map(function(s2) {
    var on      = s2.state === 'Started' || s2.State === 'Started';
    var name    = s2.name || s2.Name || '–';
    var binding = (s2.bindings && s2.bindings.Collection &&
                   s2.bindings.Collection[0] &&
                   s2.bindings.Collection[0].bindingInformation) || '–';
    var sitePath = s2.physicalPath || '–';
    var safeN   = name.replace(/'/g, "\\'");
    var safeP   = sitePath.replace(/'/g, "\\'");
    var bparts  = binding.split(':');
    var curPort = bparts[1] || '80';
    var curHost = bparts[2] || '';
    return '<tr>' +
      '<td class="b">' + name + '</td>' +
      '<td><span class="badge ' + (on?'running':'stopped') + '"><span class="bdot"></span>' + (on?'Running':'Stopped') + '</span></td>' +
      '<td class="m" style="font-size:12px">' + binding + '</td>' +
      '<td style="font-size:12px;color:#64748b;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap" title="' + sitePath + '">' + sitePath + '</td>' +
      '<td style="white-space:nowrap">' +
        '<button class="' + (on?'btn-stop':'btn-start') + '" onclick="siteAction(\'' + safeN + '\',\'' + (on?'stop':'start') + '\')">' + (on?'Stop':'Start') + '</button>' +
        '<button class="btn-neutral" onclick="siteAction(\'' + safeN + '\',\'restart\')">↻</button>' +
        '<button class="btn-neutral" onclick="openEditSite(\'' + safeN + '\',\'' + curPort + '\',\'' + curHost + '\',\'' + safeP + '\')">Edit</button>' +
        '<button class="btn-neutral" onclick="backupSite(\'' + safeN + '\',event)">Backup</button>' +
      '</td>' +
    '</tr>';
  }).join('');
  c.innerHTML =
    '<div class="card">' +
      '<div class="search-row"><h3>IIS Sites</h3>' +
        '<div style="display:flex;gap:8px">' +
          '<button class="btn-neutral" onclick="cacheClear(\'sites\');loadSites(el(\'content\'))">↻ Refresh</button>' +
          '<button class="btn-neutral" onclick="showBackups()">📁 Backups</button>' +
          '<button class="btn-primary" onclick="om(\'m-addsite\')">+ Add site</button>' +
        '</div>' +
      '</div>' +
      '<table><thead><tr>' +
        '<th>Site name</th><th>Status</th><th>Binding</th><th>Physical path</th><th>Actions</th>' +
      '</tr></thead><tbody>' + rows + '</tbody></table>' +
    '</div>' +
    '<div id="backup-panel"></div>';
}

// ── App Pools ──────────────────────────────────────────────
function loadAppPools(c) {
  c.innerHTML = skeletonCard('IIS Application Pools',
    ['Name','State','.NET Version','Pipeline','Workers','Actions'], 4,
    '<button class="btn-neutral" disabled>↻ Refresh</button>' +
    '<button class="btn-primary" disabled>+ New pool</button>');

  var cached = cacheGet('apppools');
  if (cached) { renderAppPools(c, cached); return; }

  api('/api/apppools').then(function(list) {
    if (!Array.isArray(list)) list = [list];
    cacheSet('apppools', list);
    renderAppPools(c, list);
  }).catch(function(e) {
    c.innerHTML = '<div class="err-box">Could not load app pools: ' + e.message + '</div>';
  });
}

function renderAppPools(c, list) {
  var rows = list.map(function(p) {
    var on       = p.state === 'Started';
    var safeName = (p.name||'').replace(/'/g, "\\'");
    return '<tr>' +
      '<td class="b">' + (p.name||'') + '</td>' +
      '<td><span class="badge ' + (on?'running':'stopped') + '"><span class="bdot"></span>' + (p.state||'') + '</span></td>' +
      '<td style="font-family:monospace;font-size:12px">' + (p.dotNetVersion||'No Managed Code') + '</td>' +
      '<td style="font-size:12px;color:#64748b">' + (p.pipelineMode||'') + '</td>' +
      '<td style="font-size:12px;color:#64748b">' + (p.workerProcesses||0) + ' worker(s)</td>' +
      '<td style="white-space:nowrap">' +
        (on
          ? '<button class="btn-stop" onclick="poolAction(\'' + safeName + '\',\'stop\')">Stop</button>'
          : '<button class="btn-start" onclick="poolAction(\'' + safeName + '\',\'start\')">Start</button>') +
        '<button class="btn-neutral" onclick="poolAction(\'' + safeName + '\',\'recycle\')">↻ Recycle</button>' +
        '<button class="btn-neutral" onclick="openEditPool(\'' + safeName + '\',\'' +
          (p.dotNetVersion||'v4.0').replace(/'/g,"\\'") + '\',\'' +
          (p.pipelineMode||'Integrated') + '\',' +
          (p.enable32Bit?'true':'false') + ',' +
          (p.queueLength||1000) + ')">Edit</button>' +
        '<button class="btn-danger" onclick="poolAction(\'' + safeName + '\',\'delete\')">Delete</button>' +
      '</td>' +
    '</tr>';
  }).join('');
  c.innerHTML =
    '<div class="card">' +
      '<div class="search-row"><h3>IIS Application Pools</h3>' +
        '<div style="display:flex;gap:8px">' +
          '<button class="btn-neutral" onclick="cacheClear(\'apppools\');loadAppPools(el(\'content\'))">↻ Refresh</button>' +
          '<button class="btn-primary" onclick="om(\'m-addpool\')">+ New pool</button>' +
        '</div>' +
      '</div>' +
      '<table><thead><tr>' +
        '<th>Name</th><th>State</th><th>.NET Version</th><th>Pipeline</th><th>Workers</th><th>Actions</th>' +
      '</tr></thead><tbody>' + rows + '</tbody></table>' +
    '</div>';
}
// Load Domains //
function loadDomains(c) {
  c.innerHTML = skeletonCard('Domain Bindings',
    ['Site','Protocol','Binding','Action'], 4,
    '<button class="btn-neutral" disabled>↻ Refresh</button>' +
    '<button class="btn-primary" disabled>+ Add binding</button>');

  // ← ADD: check cache first
  var cached = cacheGet('domains');
  if (cached) { renderDomainsList(c, cached); return; }

  api('/api/domains').then(function(list) {
    if (list.error) { c.innerHTML = '<div class="err-box">' + list.error + '</div>'; return; }
    if (!Array.isArray(list)) list = [list];

    // ← ADD: store in cache
    cacheSet('domains', list);

    renderDomainsList(c, list);
  }).catch(function(e) {
    c.innerHTML = '<div class="err-box">Could not load domains: ' + e.message + '</div>';
  });
}

// ← ADD: extract rendering into separate function
function renderDomainsList(c, list) {
  var rows = list.map(function(b) {
    return '<tr>' +
      '<td class="b">' + (b.Site||'–') + '</td>' +
      '<td class="m">' + (b.Protocol||'–') + '</td>' +
      '<td class="m">' + (b.Binding||'–') + '</td>' +
      '<td><button class="btn-danger" onclick="removeDomain(\'' +
        (b.Site||'') + '\',\'' + (b.Binding||'') + '\',\'' + (b.Protocol||'http') +
      '\')">Remove</button></td>' +
    '</tr>';
  }).join('');

  c.innerHTML =
    '<div class="card">' +
      '<div class="search-row"><h3>Domain Bindings</h3>' +
        '<div style="display:flex;gap:8px">' +
          // ← CHANGE: add cacheClear to Refresh button
          '<button class="btn-neutral" onclick="cacheClear(\'domains\');loadDomains(el(\'content\'))">↻ Refresh</button>' +
          '<button class="btn-primary" onclick="openAddDomain()">+ Add binding</button>' +
        '</div>' +
      '</div>' +
      (rows
        ? '<table><thead><tr><th>Site</th><th>Protocol</th><th>Binding (IP:Port:Host)</th><th>Action</th></tr></thead><tbody>' + rows + '</tbody></table>'
        : '<p style="color:#94a3b8;font-size:13px">No bindings found. Ensure IIS is installed.</p>') +
    '</div>';
}

// ── SSL Certificates ───────────────────────────────────────
function loadSSL(c) {
  cacheClear('ssl')
  c.innerHTML =
    skeletonCard('SSL Certificates',
      ['Subject','Issuer','Valid from','Expires','Status','Thumbprint','Action'], 3) +
    '<div class="card"><h3>🔒 Let\'s Encrypt</h3>' +
      '<div style="margin-bottom:8px">' + skeletonLine('50%') + '</div>' +
      skeletonLine('70%') +
    '</div>';

  var sslP = api('/api/ssl').catch(function(){ return []; });
  var leP  = api('/api/letsencrypt/status').catch(function(){ return { installed:false }; });

  Promise.all([sslP, leP]).then(function(res) {
    var list     = res[0], leStatus = res[1] || {};
    if (!Array.isArray(list)) list = list ? [list] : [];

    var certCard = '';
    if (!list.length) {
      certCard =
        '<div class="card" style="margin-bottom:16px">' +
          '<div style="text-align:center;padding:30px 0">' +
            '<div style="font-size:32px;margin-bottom:10px">🔒</div>' +
            '<h3 style="margin-bottom:6px">No SSL Certificates</h3>' +
            '<p style="font-size:13px;color:#94a3b8">No certificates found in the Windows store.</p>' +
          '</div>' +
        '</div>';
    } else {
      var rows = list.map(function(cert) {
        var exp  = new Date(cert.NotAfter);
        var days = Math.round((exp - new Date()) / 86400000);
        var sc   = days < 0 ? 'expired' : days < 30 ? 'expiring' : 'valid';
        var st   = days < 0 ? 'Expired' : days < 30 ? 'Expiring in ' + days + 'd' : 'Valid (' + days + 'd)';
        return '<tr>' +
          '<td class="b">' + (cert.Subject||'').replace('CN=','') + '</td>' +
          '<td style="font-size:12px">' + (cert.Issuer||'').replace('CN=','').split(',')[0] + '</td>' +
          '<td style="font-size:12px">' + formatDate(new Date(cert.NotBefore)) + '</td>' +
          '<td style="font-size:12px">' + formatDate(exp) + '</td>' +
          '<td><span class="badge ' + sc + '">' + st + '</span></td>' +
          '<td class="m" style="font-size:10px">' + (cert.Thumbprint||'').substring(0,16) + '...</td>' +
          '<td><button class="btn-danger" onclick="delCert(\'' + cert.Thumbprint + '\')">Remove</button></td>' +
        '</tr>';
      }).join('');
      certCard =
        '<div class="card" style="margin-bottom:16px">' +
          '<h3>SSL Certificates</h3>' +
          '<table><thead><tr>' +
            '<th>Subject</th><th>Issuer</th><th>Valid from</th><th>Expires</th>' +
            '<th>Status</th><th>Thumbprint</th><th>Action</th>' +
          '</tr></thead><tbody>' + rows + '</tbody></table>' +
        '</div>';
    }

    var installNote = !leStatus.installed
      ? '<div style="padding:10px 14px;background:#fef3c7;border-radius:6px;border:1px solid #fde68a;' +
          'display:flex;align-items:center;justify-content:space-between;margin-bottom:14px">' +
          '<span style="font-size:13px;color:#92400e">win-acme not installed</span>' +
          '<button class="btn-primary" style="font-size:11px;padding:4px 10px" onclick="installWacs(this)">Install</button>' +
        '</div>'
      : '';

    var leCard =
      '<div class="card">' +
        '<h3>🔒 Let\'s Encrypt — Free SSL Certificate</h3>' +
        '<p style="font-size:12px;color:#94a3b8;margin-bottom:14px">Request a free certificate using win-acme.</p>' +
        installNote +
        '<div class="form-grid-2" style="margin-bottom:12px">' +
          '<div class="form-field"><label>Domain name</label><input id="le-domain" placeholder="example.com" /></div>' +
          '<div class="form-field"><label>Email</label><input id="le-email" placeholder="admin@example.com" /></div>' +
          '<div class="form-field"><label>IIS Site (optional)</label><input id="le-site" placeholder="Leave blank for manual" /></div>' +
        '</div>' +
        '<div id="le-msg" style="font-size:12px;margin-bottom:10px"></div>' +
        '<div style="display:flex;gap:8px">' +
          '<button class="btn-primary" ' + (!leStatus.installed?'disabled':'') + ' onclick="requestCert()">Request Certificate</button>' +
          '<button class="btn-neutral" onclick="renewAllCerts()">↻ Renew All</button>' +
        '</div>' +
      '</div>';

    c.innerHTML = certCard + leCard;
  }).catch(function(e) {
    c.innerHTML = '<div class="err-box">Could not load SSL data: ' + e.message + '</div>';
  });
}

function delCert(thumb) {
  if (!confirm('Remove this certificate?')) return;
  api('/api/ssl/' + thumb, 'DELETE').then(function() { loadSSL(el('content')); });
}

// ── Windows Services ────────────────────────────────────────
function loadServices(c) {
  c.innerHTML = skeletonCard('Windows Services',
    ['Display name','Service name','Status','Startup','Actions'], 8);

  var cached = cacheGet('services');
  if (cached) { renderServicesList(c, cached); return; }

  api('/api/services').then(function(list) {
    if (!Array.isArray(list)) list = [list];
    cacheSet('services', list);
    renderServicesList(c, list);
  }).catch(function(e) {
    c.innerHTML = '<div class="err-box">Could not load services: ' + e.message + '</div>';
  });
}

function renderServicesList(c, list) {
  c.innerHTML =
    '<div class="card">' +
      '<div class="search-row"><h3>Windows Services</h3>' +
        '<input id="svc-search" placeholder="Filter services..." />' +
      '</div>' +
      '<table><thead><tr>' +
        '<th>Display name</th><th>Service name</th><th>Status</th><th>Startup</th><th>Actions</th>' +
      '</tr></thead><tbody id="svc-tbody"></tbody></table>' +
    '</div>';

  function renderRows(arr) {
    var tbody = el('svc-tbody');
    if (!tbody) return;
    tbody.innerHTML = arr.map(function(s) {
      var on      = s.Status === 'Running' || s.status === 'Running';
      var name    = s.Name || s.name || '–';
      var display = s.DisplayName || s.displayName || name;
      var startup = s.StartType || s.startup || '–';
      return '<tr>' +
        '<td class="b">' + display + '</td>' +
        '<td class="m">' + name + '</td>' +
        '<td><span class="badge ' + (on?'running':'stopped') + '"><span class="bdot"></span>' + (on?'Running':'Stopped') + '</span></td>' +
        '<td style="font-size:12px;color:#94a3b8">' + startup + '</td>' +
        '<td>' +
          '<button class="' + (on?'btn-stop':'btn-start') + '" onclick="svcAct(\'' + name + '\',\'' + (on?'stop':'start') + '\')">' + (on?'Stop':'Start') + '</button>' +
          '<button class="btn-neutral" onclick="svcAct(\'' + name + '\',\'restart\')">Restart</button>' +
        '</td>' +
      '</tr>';
    }).join('');
  }

  renderRows(list);

  var srch = el('svc-search');
  if (srch) {
    srch.addEventListener('input', function(e) {
      var f = e.target.value.toLowerCase();
      renderRows(f
        ? list.filter(function(s){ return (s.DisplayName||s.Name||'').toLowerCase().includes(f); })
        : list);
    });
  }
}

function svcAct(name, action) {
  api('/api/services/' + name + '/' + action, 'POST').then(function() {
    cacheClear('services');
    loadServices(el('content'));
  });
}

// ── Panel Updates ─────────────────────────────────────────
function checkForUpdate() {
  om('m-update');
  var body    = el('update-body');
  var applyBtn = el('btn-apply-update');
  body.innerHTML = '<div class="loading" style="font-size:12px">Checking for updates…</div>';
  applyBtn.style.display = 'none';

  api('/api/update/check').then(function(r) {
    if (r.error) {
      body.innerHTML = '<div class="err-box">' + r.error + '</div>';
      return;
    }
    if (r.hasUpdate) {
      var dot = el('update-dot'); if (dot) dot.style.display = 'block';
      body.innerHTML =
        '<div style="display:flex;align-items:center;gap:10px;margin-bottom:14px">' +
          '<span style="font-size:22px">🎉</span>' +
          '<div>' +
            '<div style="font-weight:600;color:#0f172a;margin-bottom:2px">Update available — v' + r.latest + '</div>' +
            '<div style="font-size:11px;color:#94a3b8">Current version: v' + r.current + '</div>' +
          '</div>' +
        '</div>' +
        (r.releaseNotes
          ? '<div style="background:#f8fafc;border:1px solid #e2e8f0;border-radius:6px;padding:10px 12px;font-size:12px;color:#475569;white-space:pre-wrap;max-height:140px;overflow-y:auto;margin-bottom:4px">' + r.releaseNotes + '</div>'
          : '') +
        '<p style="font-size:11px;color:#94a3b8;margin-top:8px">The panel will restart automatically after the update.</p>';
      applyBtn.style.display = 'inline-flex';
    } else {
      body.innerHTML =
        '<div style="display:flex;align-items:center;gap:10px">' +
          '<span style="font-size:22px">✅</span>' +
          '<div>' +
            '<div style="font-weight:600;color:#0f172a">You\'re up to date</div>' +
            '<div style="font-size:11px;color:#94a3b8;margin-top:2px">Version v' + r.current + ' is the latest release</div>' +
          '</div>' +
        '</div>';
    }
  }).catch(function() {
    body.innerHTML = '<div class="err-box">Could not reach update server. Check your internet connection.</div>';
  });
}

function applyUpdate() {
  var body     = el('update-body');
  var applyBtn = el('btn-apply-update');
  applyBtn.disabled = true;
  applyBtn.textContent = '⏳ Installing…';
  body.innerHTML =
    '<div style="font-size:13px;color:#64748b">' +
      '<div style="margin-bottom:8px">⬇ Downloading update from GitHub…</div>' +
      '<div style="margin-bottom:8px">📦 Extracting and copying files…</div>' +
      '<div>🔄 The panel will restart automatically when done.</div>' +
    '</div>';

  api('/api/update/apply', 'POST').then(function(r) {
    if (r.error) {
      body.innerHTML = '<div class="err-box">Update failed: ' + r.error + '</div>';
      applyBtn.disabled = false;
      applyBtn.textContent = '⬇ Install Update';
      return;
    }
    body.innerHTML =
      '<div style="text-align:center;padding:10px 0">' +
        '<div style="font-size:28px;margin-bottom:8px">✅</div>' +
        '<div style="font-weight:600;color:#0f172a;margin-bottom:4px">Updated to v' + r.version + '</div>' +
        '<div style="font-size:12px;color:#94a3b8">Server is restarting — the page will reload in a few seconds.</div>' +
      '</div>';
    var dot = el('update-dot'); if (dot) dot.style.display = 'none';
    setTimeout(function() { location.reload(); }, 5000);
  }).catch(function() {
    body.innerHTML = '<div class="err-box">Connection lost — the server may be restarting. Please refresh the page.</div>';
    setTimeout(function() { location.reload(); }, 5000);
  });
}




