{"statusCode":200,"headers":{"x-powered-by":"Express","access-control-allow-origin":"*","access-control-allow-methods":"GET, POST, OPTIONS","access-control-allow-headers":"Content-Type","accept-ranges":"bytes","cache-control":"public, max-age=0","last-modified":"Fri, 19 Jun 2026 21:06:57 GMT","etag":"W/\"7a8e-19ee1b55168\"","content-type":"text/html; charset=UTF-8","content-length":"31374"},"isBase64Encoded":false,"body":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>GOLEM - Gas-tank Operational Levels: Equipment Monitor</title>\n  <style>\n    * { box-sizing: border-box; margin: 0; padding: 0; }\n    body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #1a1a2e; color: #e0e0e0; min-height: 100vh; padding: 20px; }\n    .container { max-width: 1100px; margin: 0 auto; }\n    h1 { color: #00d4aa; font-size: 2em; margin-bottom: 5px; }\n    .subtitle { color: #888; margin-bottom: 30px; font-size: 0.9em; }\n    .card { background: #16213e; border-radius: 8px; padding: 20px; margin-bottom: 20px; border: 1px solid #0f3460; }\n    .card h2 { color: #00d4aa; margin-bottom: 15px; font-size: 1.2em; }\n    .status-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin-bottom: 20px; }\n    .status-item { background: #0f3460; padding: 15px; border-radius: 6px; text-align: center; }\n    .status-item .number { font-size: 2em; font-weight: bold; color: #00d4aa; }\n    .status-item .label { font-size: 0.75em; color: #888; margin-top: 5px; }\n    .status-item.alert .number { color: #ff6b6b; }\n    .btn { padding: 10px 18px; border: none; border-radius: 6px; cursor: pointer; font-size: 0.9em; font-weight: 600; transition: all 0.2s; }\n    .btn-primary { background: #00d4aa; color: #1a1a2e; }\n    .btn-primary:hover { background: #00f5c4; }\n    .btn-secondary { background: #0f3460; color: #e0e0e0; border: 1px solid #00d4aa; }\n    .btn-secondary:hover { background: #16213e; }\n    .btn-danger { background: #ff6b6b; color: #fff; }\n    .btn-danger:hover { background: #ff8787; }\n    .btn:disabled { opacity: 0.5; cursor: not-allowed; }\n    .btn-group { display: flex; gap: 10px; flex-wrap: wrap; }\n    .search-box { display: flex; gap: 10px; margin-bottom: 15px; }\n    .search-box input { flex: 1; padding: 10px 15px; background: #0f3460; border: 1px solid #00d4aa; border-radius: 6px; color: #e0e0e0; font-size: 1em; }\n    .search-box input::placeholder { color: #666; }\n    .sim-list { list-style: none; }\n    .sim-item { background: #0f3460; padding: 12px 15px; border-radius: 6px; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; }\n    .sim-item .site { font-weight: bold; color: #00d4aa; min-width: 60px; }\n    .sim-item .util { color: #ff6b6b; font-size: 0.85em; }\n    .sim-item .meta { font-size: 0.8em; color: #888; }\n    .badge { display: inline-block; padding: 2px 8px; border-radius: 10px; font-size: 0.75em; font-weight: 600; }\n    .badge-danger { background: #ff6b6b33; color: #ff6b6b; }\n    .badge-ok { background: #00d4aa33; color: #00d4aa; }\n    .output { background: #0a0a1a; border: 1px solid #333; border-radius: 6px; padding: 15px; margin-top: 15px; font-family: monospace; font-size: 0.82em; white-space: pre-wrap; max-height: 400px; overflow-y: auto; display: none; }\n    .output.visible { display: block; }\n    .collapsible { cursor: pointer; user-select: none; }\n    .collapsible::before { content: '▶ '; font-size: 0.8em; }\n    .collapsible.open::before { content: '▼ '; }\n    .collapse-content { display: none; margin-top: 10px; }\n    .collapse-content.open { display: block; }\n    .site-detail { background: #0a0a1a; border-radius: 6px; padding: 15px; margin-top: 10px; }\n    .site-detail h3 { color: #00d4aa; margin-bottom: 10px; }\n    .site-detail .stat-row { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid #1a1a2e; }\n    .history-item { font-size: 0.8em; color: #888; padding: 3px 0; }\n    .spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid #00d4aa; border-top-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; margin-right: 6px; }\n    @keyframes spin { to { transform: rotate(360deg); } }\n    .timestamp { color: #666; font-size: 0.8em; }\n  </style>\n</head>\n<body>\n  <div class=\"container\">\n    <h1>🗿 GOLEM</h1>\n    <p class=\"subtitle\">Gas-tank Operational Levels: Equipment Monitor | MCS Automated GTM SIM Updates (3-hour cycle)</p>\n\n    <!-- Setup Instructions -->\n    <div class=\"card\" id=\"instructionsCard\">\n      <h2 style=\"cursor:pointer\" onclick=\"toggleInstructions()\">📋 Setup Instructions <span id=\"instrToggle\" style=\"font-size:0.7em;color:#888\">(click to expand)</span></h2>\n      <div id=\"instructionsContent\" style=\"display:none; margin-top:15px; line-height:1.8; color:#ccc;\">\n        <p style=\"margin-bottom:12px; color:#00d4aa; font-weight:600;\">To keep GOLEM running, your team needs:</p>\n\n        <ol style=\"padding-left:20px;\">\n          <li style=\"margin-bottom:12px;\">\n            <strong>Install the GOLEM Data Push Tampermonkey script</strong><br>\n            <span style=\"color:#888;\">Install Tampermonkey extension if you don't have it:</span>\n            <a href=\"https://www.tampermonkey.net/\" target=\"_blank\" style=\"color:#00d4aa;\">tampermonkey.net</a><br>\n            <span style=\"color:#888;\">Then install the GOLEM data push script:</span>\n            <a href=\"/scripts/golem-data-push.user.js\" target=\"_blank\" style=\"color:#00d4aa;\">Install GOLEM Data Push Script</a>\n          </li>\n\n          <li style=\"margin-bottom:12px;\">\n            <strong>Keep AAP Fleet Monitoring open in a browser tab</strong><br>\n            <a href=\"https://aap-na.corp.amazon.com/page/dfd44913-d2f4-4e96-99e2-64729dbdc19a\" target=\"_blank\" style=\"color:#00d4aa;\">Open AAP Fleet Monitoring</a><br>\n            <span style=\"color:#888;\">The script runs in the background on this page — pushes data to GOLEM every 30 minutes and immediately on page load. No user action needed.</span>\n          </li>\n\n          <li style=\"margin-bottom:12px;\">\n            <strong>At least one person per shift should have AAP open</strong><br>\n            <span style=\"color:#888;\">Multiple people can have it open — that's fine, it just means fresher data. If there's a gap between shifts (e.g., everyone closes for an hour), GOLEM uses the last data it received and continues posting updates normally. When the next shift opens AAP, fresh data flows immediately.</span>\n          </li>\n\n          <li style=\"margin-bottom:12px;\">\n            <strong>Update the Active GTM Sites list regularly</strong><br>\n            <span style=\"color:#888;\">Sites enter and leave GTM gridlock throughout the day. To update GOLEM's site list:</span>\n            <ol style=\"padding-left:20px; margin-top:8px; color:#888;\">\n              <li>Go to the <a href=\"https://us-east-1.quicksight.aws.amazon.com/sn/account/alpaca/dashboards/9ce8cbe5-7ff9-49c7-8b62-6376063377f5\" target=\"_blank\" style=\"color:#00d4aa;\">Gas Tank Model QuickSight Dashboard</a></li>\n              <li>Select the <strong>\"Current State\"</strong> tab</li>\n              <li>Download the CSV (click the export/download icon on the table)</li>\n              <li>Open the CSV and copy the Site ID column for all sites at or above 85% utilization</li>\n              <li>Paste the site codes into the <strong>\"📍 Active GTM Sites\"</strong> box below (comma-separated, e.g., SAT3, ATL2, DFW7)</li>\n              <li>Click <strong>Update Sites</strong> — the Tampermonkey script will pick up the new list on its next cycle</li>\n            </ol>\n            <span style=\"color:#888;\">Do this whenever the GTM dashboard changes (typically every few hours during peak events).</span>\n          </li>\n\n          <li style=\"margin-bottom:12px;\">\n            <strong>GOLEM itself runs 24/7 in AWS — no one needs to keep this dashboard open</strong><br>\n            <span style=\"color:#888;\">This UI is just for visibility, manual triggers, and site lookups. The automated 3-hour SIM updates happen in the background regardless of whether anyone is viewing this page.</span>\n          </li>\n\n          <li style=\"margin-bottom:12px;\">\n            <strong>Verify data is flowing</strong><br>\n            <span style=\"color:#888;\">The \"Cached Sites\" count in the status bar shows how many sites have data. You can also check the 🗿 indicator in the bottom-left of the AAP page — it confirms the script is active. Click it to force an immediate push.</span>\n          </li>\n        </ol>\n\n        <div style=\"background:#0f3460; padding:12px; border-radius:6px; margin-top:15px;\">\n          <strong style=\"color:#00d4aa;\">🗿 How it works:</strong>\n          <span style=\"color:#ccc;\"> Tampermonkey (on AAP page) fetches damaged trailers + hostlers every 30 min → pushes to GOLEM in AWS → GOLEM posts MCS updates to GTM SIMs every 3 hours automatically.</span>\n        </div>\n\n        <div style=\"background:#0f3460; padding:12px; border-radius:6px; margin-top:10px;\">\n          <strong style=\"color:#888;\">Edge cases:</strong>\n          <ul style=\"padding-left:18px; margin-top:6px; color:#888;\">\n            <li>Gap between shifts? GOLEM uses last received data (valid for up to 3 hours). Updates still post.</li>\n            <li>Multiple people have AAP open? Great — more pushes = fresher data. No conflicts.</li>\n            <li>Someone refreshes AAP? Also fine — script pushes fresh data immediately on page load.</li>\n            <li>GOLEM will never post fake/mock data to a real SIM. If data is too stale, it skips until fresh data arrives.</li>\n          </ul>\n        </div>\n      </div>\n    </div>\n\n    <!-- Status Overview (hidden until Maxis auto-post is live) -->\n    <div class=\"card\" style=\"display:none;\">\n      <h2>System Status</h2>\n      <div class=\"status-grid\">\n        <div class=\"status-item\"><div class=\"number\" id=\"openSimCount\">-</div><div class=\"label\">Open GTM SIMs</div></div>\n        <div class=\"status-item alert\"><div class=\"number\" id=\"overThresholdCount\">-</div><div class=\"label\">Over Damage Threshold</div></div>\n        <div class=\"status-item\"><div class=\"number\" id=\"trackedSites\">-</div><div class=\"label\">Sites Tracked</div></div>\n        <div class=\"status-item\"><div class=\"number\" id=\"updateInterval\">3h</div><div class=\"label\">Update Cycle</div></div>\n      </div>\n      <div class=\"btn-group\">\n        <button class=\"btn btn-secondary\" onclick=\"refreshStatus()\">Refresh Status</button>\n        <button class=\"btn btn-secondary\" id=\"toggleGtmBtn\" onclick=\"toggleGtmSites()\">Show All GTM Sites</button>\n        <button class=\"btn btn-secondary\" id=\"toggleThresholdBtn\" onclick=\"toggleOverThreshold()\">Show Over Threshold</button>\n      </div>\n      <div id=\"gtmSitesList\" class=\"collapse-content\"></div>\n      <div id=\"overThresholdList\" class=\"collapse-content\"></div>\n    </div>\n\n    <!-- VPN Reminder -->\n    <div style=\"background:#ff6b6b22; border:1px solid #ff6b6b; border-radius:6px; padding:10px 15px; margin-bottom:20px; text-align:center;\">\n      <span style=\"color:#ff6b6b; font-weight:600;\">⚠️</span> <span style=\"color:#ccc;\">Confirm user is connected to VPN before using GOLEM</span>\n    </div>\n\n    <!-- Step 1: Enter Sites -->\n    <div class=\"card\">\n      <h2>📍 Step 1: Enter GTM Sites</h2>\n      <p style=\"margin-bottom:5px; color:#ccc;\">Enter site codes to monitor. Download the CSV from the <a href=\"https://us-east-1.quicksight.aws.amazon.com/sn/account/alpaca/dashboards/9ce8cbe5-7ff9-49c7-8b62-6376063377f5\" target=\"_blank\" style=\"color:#00d4aa;\">GTM Dashboard (Current State tab)</a> and paste Site IDs here. You can also add individual sites (e.g., SDF9) at any time.</p>\n      <p style=\"margin-bottom:10px; color:#ff9800; font-size:0.8em;\">⚠️ This is a shared list — updating it replaces the previous list for all users.</p>\n      <div class=\"search-box\">\n        <input type=\"text\" id=\"activeSitesInput\" placeholder=\"Paste site codes: SAT3, ATL2, DFW7, BOS1...\" style=\"flex:3\">\n        <button class=\"btn btn-primary\" onclick=\"updateActiveSites()\">Update Sites</button>\n      </div>\n      <div id=\"activeSitesStatus\" style=\"margin-top:10px;\"></div>\n    </div>\n\n    <!-- Step 2: Site Lookup -->\n    <div class=\"card\">\n      <h2>🔍 Step 2: Site Lookup</h2>\n      <p style=\"margin-bottom:10px; color:#ccc;\">Enter a site code to see current damaged asset status — trailer/hostler counts, work request details, vendor assignments, and repair descriptions. Data pulled directly from TEDS.</p>\n      <div class=\"search-box\">\n        <input type=\"text\" id=\"siteSearchInput\" placeholder=\"Enter site code (e.g., SAT3)\" onkeydown=\"if(event.key==='Enter')searchSite()\">\n        <button class=\"btn btn-primary\" onclick=\"searchSite()\">Search</button>\n      </div>\n      <div id=\"siteDetail\"></div>\n    </div>\n\n    <!-- Step 3: Generate & Copy -->\n    <div class=\"card\">\n      <h2>📋 Step 3: Generate MCS Update (Copy & Paste)</h2>\n      <p style=\"margin-bottom:15px; color:#888;\">Generate the formatted MCS status update for a site, then copy and paste into the GTM SIM. Includes damaged trailer count, work request states, vendor assignments, and repair details.</p>\n      <div class=\"search-box\">\n        <input type=\"text\" id=\"generateSiteInput\" placeholder=\"Site code (e.g., SAT3)\" onkeydown=\"if(event.key==='Enter')generateForCopy()\">\n        <button class=\"btn btn-primary\" onclick=\"generateForCopy()\">Generate Update</button>\n      </div>\n      <div id=\"copyOutput\" style=\"display:none;\">\n        <div style=\"display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;\">\n          <span style=\"color:#00d4aa; font-weight:600;\">✅ Update generated — copy and paste into the SIM</span>\n          <button class=\"btn btn-primary\" id=\"copyBtn\" onclick=\"copyToClipboard()\">📋 Copy to Clipboard</button>\n        </div>\n        <pre id=\"copyText\" style=\"background:#0a0a1a; border:1px solid #00d4aa; border-radius:6px; padding:15px; font-family:monospace; font-size:0.82em; white-space:pre-wrap; max-height:400px; overflow-y:auto; color:#e0e0e0;\"></pre>\n      </div>\n    </div>\n      </div>\n      <div id=\"output\" class=\"output\"></div>\n    </div>\n  </div>\n\n  <!-- Manager Metrics (hidden by default) -->\n  <div class=\"container\" id=\"metricsSection\" style=\"display:none; margin-top:20px;\">\n    <div class=\"card\" style=\"border-color:#ff9800;\">\n      <h2 style=\"color:#ff9800;\">📊 Performance Metrics (Leadership View)</h2>\n      <div id=\"metricsContent\"></div>\n    </div>\n  </div>\n\n  <!-- Tiny metrics link at very bottom -->\n  <div class=\"container\" style=\"margin-top:40px; text-align:center;\">\n    <span style=\"color:#333; font-size:0.7em; cursor:pointer;\" onclick=\"document.getElementById('metricsLogin').style.display='block'\">📊</span>\n    <div id=\"metricsLogin\" style=\"display:none; margin-top:10px;\">\n      <input type=\"text\" id=\"metricsAlias\" placeholder=\"Manager alias\" style=\"padding:6px 10px; background:#0f3460; border:1px solid #333; border-radius:4px; color:#e0e0e0; font-size:0.85em;\">\n      <button class=\"btn btn-secondary\" style=\"padding:6px 12px; font-size:0.8em;\" onclick=\"loadMetrics()\">View Metrics</button>\n    </div>\n  </div>\n\n  <script>\n    const API = '';  // Same origin — custom domain serves both UI and API\n\n    // No API key needed — Midway auth will be added\n\n    async function refreshStatus() {\n      try {\n        const [statusRes, thresholdRes] = await Promise.all([\n          fetch(`${API}/api/status`),\n          fetch(`${API}/api/sites/over-threshold`),\n        ]);\n        const status = await statusRes.json();\n        const threshold = await thresholdRes.json();\n\n        document.getElementById('openSimCount').textContent = status.openSims || 0;\n        document.getElementById('overThresholdCount').textContent = threshold.total || 0;\n        document.getElementById('trackedSites').textContent = status.sims?.filter(s => s.lastUpdate).length || 0;\n\n        const simList = document.getElementById('simList');\n        if (status.sims?.length > 0) {\n          simList.innerHTML = status.sims.map(sim => {\n            const overBadge = sim.overThreshold ? '<span class=\"badge badge-danger\">OVER</span>' : (sim.actuals !== null ? '<span class=\"badge badge-ok\">OK</span>' : '');\n            const nextUp = sim.nextUpdate ? `Next: ${new Date(sim.nextUpdate).toLocaleTimeString()}` : '';\n            return `<li class=\"sim-item\">\n              <span><span class=\"site\">${sim.siteCode || '?'}</span> ${overBadge} ${sim.title}</span>\n              <span>\n                <span class=\"util\">${sim.yardUtilization ? sim.yardUtilization + '%' : ''}</span>\n                <span class=\"meta\">${nextUp}</span>\n                <button class=\"btn btn-secondary\" style=\"padding:5px 10px;font-size:0.75em;margin-left:8px\" onclick=\"processSim('${sim.id}')\">Update Now</button>\n              </span>\n            </li>`;\n          }).join('');\n        } else {\n          simList.innerHTML = '<li class=\"sim-item\"><span>No open GTM SIMs</span></li>';\n        }\n      } catch (e) {\n        document.getElementById('openSimCount').textContent = '?';\n      }\n    }\n\n    async function toggleGtmSites() {\n      const el = document.getElementById('gtmSitesList');\n      if (el.classList.contains('open')) { el.classList.remove('open'); return; }\n      el.innerHTML = '<span class=\"spinner\"></span> Loading...';\n      el.classList.add('open');\n      try {\n        const res = await fetch(`${API}/api/sites/gtm`);\n        const data = await res.json();\n        el.innerHTML = `<strong>${data.total} sites in GTM gridlock:</strong><br><br>` +\n          data.sites.map(s => `<span class=\"badge badge-danger\" style=\"margin:3px\">${s.siteCode}</span> ${s.yardUtilization ? s.yardUtilization + '%' : ''}`).join('<br>');\n      } catch (e) { el.innerHTML = 'Error loading'; }\n    }\n\n    async function toggleOverThreshold() {\n      const el = document.getElementById('overThresholdList');\n      if (el.classList.contains('open')) { el.classList.remove('open'); return; }\n      el.innerHTML = '<span class=\"spinner\"></span> Loading...';\n      el.classList.add('open');\n      try {\n        const res = await fetch(`${API}/api/sites/over-threshold`);\n        const data = await res.json();\n        if (data.sites.length === 0) { el.innerHTML = '<em>No sites currently over damage threshold</em>'; return; }\n        el.innerHTML = `<strong>${data.total} sites over damage threshold:</strong><br><br>` +\n          data.sites.map(s => `<span class=\"badge badge-danger\" style=\"margin:3px\">${s.siteCode}</span> Threshold: ${s.threshold}, Actual: ${s.actuals || '?'}, Delta: ${s.delta !== null ? '+' + s.delta : '?'}`).join('<br>');\n      } catch (e) { el.innerHTML = 'Error loading'; }\n    }\n\n    async function searchSite() {\n      const siteCode = document.getElementById('siteSearchInput').value.trim().toUpperCase();\n      if (!siteCode) return;\n      const el = document.getElementById('siteDetail');\n      el.innerHTML = '<span class=\"spinner\"></span> Fetching real-time data...';\n      try {\n        const res = await fetch(`${API}/api/site/${siteCode}`);\n        const data = await res.json();\n\n        if (data.error || !data.realTime) {\n          el.innerHTML = `<div class=\"site-detail\"><h3>${siteCode}</h3><p style=\"color:#ff9800;\">⚠️ ${data.error || 'No data available. Ensure this site is in the Active GTM Sites list and Tampermonkey has pushed data.'}</p></div>`;\n          return;\n        }\n\n        const rt = data.realTime;\n        const tr = data.tracking;\n        el.innerHTML = `\n          <div class=\"site-detail\">\n            <h3>${siteCode} — Real-Time Status</h3>\n            <div class=\"stat-row\"><span>Damaged Trailers</span><strong>${rt.damagedTrailers}</strong></div>\n            <div class=\"stat-row\"><span>Unavailable Hostlers/YT</span><strong>${rt.unavailableHostlers}</strong></div>\n            <div class=\"stat-row\"><span>Threshold</span><strong>${tr.threshold || 'N/A'}</strong></div>\n            <div class=\"stat-row\"><span>Over Threshold</span><strong>${tr.overThreshold ? '⚠️ YES (+' + (tr.actuals - tr.threshold) + ')' : '✅ No'}</strong></div>\n            <div class=\"stat-row\"><span>Last GOLEM Update</span><strong>${tr.lastUpdate ? new Date(tr.lastUpdate).toLocaleString() : 'Never'}</strong></div>\n            <div class=\"stat-row\"><span>Next Update Due</span><strong>${tr.nextUpdate ? new Date(tr.nextUpdate).toLocaleString() : 'N/A'}</strong></div>\n            <div class=\"stat-row\"><span>SIM ID</span><strong>${tr.simId || 'Not tracked'}</strong></div>\n            ${tr.history?.length ? '<h4 style=\"margin-top:15px;color:#888;\">Update History</h4>' + tr.history.slice().reverse().map(h => `<div class=\"history-item\">${new Date(h.timestamp).toLocaleString()} — ${h.trailersCount} trailers, ${h.hostlersCount} hostlers ${h.posted ? '✅' : '❌'}</div>`).join('') : ''}\n            <div style=\"margin-top:15px;\">\n              <button class=\"btn btn-secondary\" style=\"font-size:0.8em\" onclick=\"previewSite('${siteCode}')\">Preview Update</button>\n            </div>\n          </div>`;\n      } catch (e) { el.innerHTML = `<div class=\"site-detail\"><p style=\"color:#ff6b6b;\">❌ Error: ${e.message}</p></div>`; }\n    }\n\n    async function previewSite(siteCode) {\n      showOutput(`Fetching preview for ${siteCode}...\\n`);\n      try {\n        const res = await fetch(`${API}/api/preview`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ siteCode }) });\n        const data = await res.json();\n        showOutput(`Preview for ${data.siteCode}:\\nTrailers: ${data.trailersCount}, Hostlers: ${data.hostlersCount || 0}\\n\\n--- Comment ---\\n\\n${data.comment}`);\n      } catch (e) { showOutput('Error: ' + e.message); }\n    }\n\n    async function processAll(force) {\n      const btn = document.getElementById('processAllBtn');\n      btn.disabled = true; btn.innerHTML = '<span class=\"spinner\"></span>Processing...';\n      showOutput('Processing...\\n');\n      try {\n        const res = await fetch(`${API}/api/process-all`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ force }) });\n        const data = await res.json();\n        let out = `✅ Processed: ${data.processed}, Skipped: ${data.skipped || 0}\\n\\n`;\n        (data.results || []).forEach(r => {\n          if (r.skipped) { out += `${r.siteCode}: ⏭️ Skipped (next: ${new Date(r.nextUpdate).toLocaleTimeString()})\\n`; }\n          else { out += `${r.siteCode}: ${r.success ? '✅' : '❌'} ${r.trailersCount} trailers, ${r.hostlersCount} hostlers${r.overThreshold ? ' ⚠️ OVER' : ''}\\n`; }\n        });\n        showOutput(out);\n        refreshStatus();\n      } catch (e) { showOutput('Error: ' + e.message); }\n      finally { btn.disabled = false; btn.innerHTML = 'Process Due SIMs'; }\n    }\n\n    async function processSim(simId) {\n      showOutput(`Processing ${simId}...\\n`);\n      try {\n        const res = await fetch(`${API}/api/process-sim`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ simId }) });\n        const data = await res.json();\n        showOutput(JSON.stringify(data, null, 2));\n        refreshStatus();\n      } catch (e) { showOutput('Error: ' + e.message); }\n    }\n\n    async function showPreview() {\n      showOutput('Fetching preview for mock site...\\n');\n      try {\n        const res = await fetch(`${API}/api/preview`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ siteCode: 'SAT3' }) });\n        const data = await res.json();\n        showOutput(`Preview for ${data.siteCode}:\\nTrailers: ${data.trailersCount}, Hostlers: ${data.hostlersCount || 0}\\n\\n--- Comment ---\\n\\n${data.comment}`);\n      } catch (e) { showOutput('Error: ' + e.message); }\n    }\n\n    function showOutput(text) { const el = document.getElementById('output'); el.textContent = text; el.classList.add('visible'); }\n\n    async function generateForCopy() {\n      const siteCode = document.getElementById('generateSiteInput').value.trim().toUpperCase();\n      if (!siteCode) return;\n      const outputDiv = document.getElementById('copyOutput');\n      const copyText = document.getElementById('copyText');\n      outputDiv.style.display = 'block';\n      copyText.textContent = 'Generating...';\n      const startTime = Date.now();\n      try {\n        const res = await fetch(`${API}/api/preview`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ siteCode }) });\n        const data = await res.json();\n        if (data.error) {\n          copyText.textContent = '⚠️ ' + data.error;\n          document.getElementById('copyBtn').style.display = 'none';\n          return;\n        }\n        copyText.textContent = data.comment;\n        document.getElementById('copyBtn').style.display = '';\n        document.getElementById('copyBtn').textContent = '📋 Copy to Clipboard';\n        fetch(`${API}/api/metrics/log`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'generate', siteCode, durationMs: Date.now() - startTime, trailersCount: data.trailersCount, hostlersCount: data.hostlersCount }) }).catch(() => {});\n      } catch (e) { copyText.textContent = '❌ Error: ' + e.message; }\n    }\n\n    function copyToClipboard() {\n      const text = document.getElementById('copyText').textContent;\n      navigator.clipboard.writeText(text).then(() => {\n        const btn = document.getElementById('copyBtn');\n        btn.textContent = '✅ Copied!';\n        setTimeout(() => btn.textContent = '📋 Copy to Clipboard', 2000);\n        // Silent metrics log\n        fetch(`${API}/api/metrics/log`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'copy', siteCode: document.getElementById('generateSiteInput').value.trim().toUpperCase() }) }).catch(() => {});\n      });\n    }\n\n    refreshStatus();\n    setInterval(refreshStatus, 60000);\n    loadActiveSites();\n\n    async function updateActiveSites() {\n      const input = document.getElementById('activeSitesInput').value;\n      const sites = input.split(/[,\\s]+/).map(s => s.trim().toUpperCase()).filter(s => s.length >= 3);\n      if (sites.length === 0) return;\n      try {\n        const res = await fetch(`${API}/api/active-sites`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ sites }) });\n        const data = await res.json();\n        const siteList = data.sites ? data.sites.join(', ') : sites.join(', ');\n        const count = data.count || sites.length;\n        document.getElementById('activeSitesStatus').innerHTML = `<span style=\"color:#00d4aa\">✅ Updated: ${count} sites — ${siteList}</span><br><span style=\"color:#888;font-size:0.8em\">Tampermonkey will pick these up on next push cycle (or click 🗿 on AAP to push now)</span>`;\n      } catch (e) { document.getElementById('activeSitesStatus').innerHTML = `<span style=\"color:#ff6b6b\">❌ Error: ${e.message}</span>`; }\n    }\n\n    async function loadActiveSites() {\n      try {\n        const res = await fetch(`${API}/api/active-sites`);\n        const data = await res.json();\n        const sites = Array.isArray(data) ? data : (data.sites || []);\n        if (sites.length > 0) {\n          document.getElementById('activeSitesInput').value = sites.join(', ');\n          document.getElementById('activeSitesStatus').innerHTML = `<span style=\"color:#888\">Current: ${sites.length} sites active</span>`;\n        }\n      } catch (e) {}\n    }\n\n    async function loadMetrics() {\n      const alias = document.getElementById('metricsAlias').value.trim();\n      if (!alias) return;\n      try {\n        const res = await fetch(`${API}/api/metrics/savings?alias=${alias}`);\n        if (res.status === 403) {\n          document.getElementById('metricsLogin').innerHTML += '<p style=\"color:#ff6b6b;font-size:0.8em;\">Access denied.</p>';\n          return;\n        }\n        const data = await res.json();\n        document.getElementById('metricsSection').style.display = 'block';\n        document.getElementById('metricsContent').innerHTML = `\n          <div class=\"status-grid\">\n            <div class=\"status-item\"><div class=\"number\">${data.currentState.updatesGenerated}</div><div class=\"label\">Updates Generated (7d)</div></div>\n            <div class=\"status-item\"><div class=\"number\">${data.currentState.updatesCopied}</div><div class=\"label\">Updates Copied (7d)</div></div>\n            <div class=\"status-item\"><div class=\"number\">${data.currentState.avgGenerateTimeSec}s</div><div class=\"label\">Avg Generate Time</div></div>\n            <div class=\"status-item\"><div class=\"number\">${data.currentState.timeSavedPerUpdateMin}m</div><div class=\"label\">Time Saved/Update</div></div>\n            <div class=\"status-item\"><div class=\"number\">${data.currentState.weeklyTimeSavedHours}h</div><div class=\"label\">Weekly Hours Saved</div></div>\n            <div class=\"status-item\"><div class=\"number\">${data.last30Days.monthlyTimeSavedHours}h</div><div class=\"label\">Monthly Hours Saved</div></div>\n          </div>\n          <div style=\"margin-top:20px; padding:15px; background:#0f3460; border-radius:6px;\">\n            <h3 style=\"color:#ff9800; margin-bottom:10px;\">Baseline (Manual Process)</h3>\n            <div class=\"status-grid\">\n              <div class=\"status-item\"><div class=\"number\">${data.baseline.ahtMinutes}m</div><div class=\"label\">AHT per Update</div></div>\n              <div class=\"status-item\"><div class=\"number\">${data.baseline.monthlyVolume.toLocaleString()}</div><div class=\"label\">Monthly Volume</div></div>\n              <div class=\"status-item\"><div class=\"number\">${data.baseline.monthlyLaborHours.toLocaleString()}h</div><div class=\"label\">Monthly Labor Hours</div></div>\n              <div class=\"status-item\"><div class=\"number\">${data.baseline.headcountRequired}</div><div class=\"label\">HC Required</div></div>\n            </div>\n          </div>\n          <div style=\"margin-top:20px; padding:15px; background:#0a3d2a; border:1px solid #00d4aa; border-radius:6px;\">\n            <h3 style=\"color:#00d4aa; margin-bottom:10px;\">🚀 Auto-Post Projection (when Maxis approved)</h3>\n            <div class=\"status-grid\">\n              <div class=\"status-item\"><div class=\"number\">${data.autoPostProjection.savingsPerUpdateMin}m</div><div class=\"label\">Saved per Update</div></div>\n              <div class=\"status-item\"><div class=\"number\">${data.autoPostProjection.monthlyHoursSaved.toLocaleString()}h</div><div class=\"label\">Monthly Hours Saved</div></div>\n              <div class=\"status-item\"><div class=\"number\">${data.autoPostProjection.hcReduction}</div><div class=\"label\">HC Equivalent Saved</div></div>\n              <div class=\"status-item\"><div class=\"number\">${data.autoPostProjection.percentReduction}%</div><div class=\"label\">HC Reduction</div></div>\n            </div>\n          </div>\n        `;\n        document.getElementById('metricsSection').scrollIntoView({ behavior: 'smooth' });\n      } catch (e) {\n        document.getElementById('metricsContent').innerHTML = '<p style=\"color:#ff6b6b;\">Error loading metrics: ' + e.message + '</p>';\n      }\n    }\n\n    function toggleInstructions() {\n      const el = document.getElementById('instructionsContent');\n      const toggle = document.getElementById('instrToggle');\n      if (el.style.display === 'none') { el.style.display = 'block'; toggle.textContent = '(click to collapse)'; }\n      else { el.style.display = 'none'; toggle.textContent = '(click to expand)'; }\n    }\n  </script>\n</body>\n</html>\n"}