<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>FWServe - Firmware File Server</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      font-family: system-ui, -apple-system, sans-serif;
      background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
      min-height: 100vh;
      color: #e0e0e0;
    }

    /* Header & Navigation */
    .header {
      background: rgba(0,0,0,0.3);
      border-bottom: 1px solid rgba(255,255,255,0.1);
      padding: 15px 20px;
      position: sticky;
      top: 0;
      z-index: 100;
    }
    .header-content {
      max-width: 1200px;
      margin: 0 auto;
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
    .logo {
      font-size: 1.5rem;
      font-weight: 700;
      color: #00d4ff;
    }
    .nav {
      display: flex;
      gap: 5px;
    }
    .nav-btn {
      background: transparent;
      border: 1px solid rgba(255,255,255,0.2);
      color: #aaa;
      padding: 10px 20px;
      border-radius: 6px;
      cursor: pointer;
      font-size: 14px;
      transition: all 0.2s;
    }
    .nav-btn:hover {
      background: rgba(255,255,255,0.05);
      color: #fff;
    }
    .nav-btn.active {
      background: #00d4ff;
      color: #1a1a2e;
      border-color: #00d4ff;
    }

    /* Main Container */
    .container {
      max-width: 1200px;
      margin: 0 auto;
      padding: 30px 20px;
    }

    /* Tab Content */
    .tab-content {
      display: none;
    }
    .tab-content.active {
      display: block;
    }

    /* Panels */
    .panel {
      background: rgba(255,255,255,0.05);
      border: 1px solid rgba(255,255,255,0.1);
      border-radius: 12px;
      padding: 25px;
      margin-bottom: 20px;
    }
    .panel h2 {
      color: #00d4ff;
      margin-bottom: 20px;
      font-size: 1.3rem;
    }

    /* Upload Form */
    .upload-form { display: flex; flex-direction: column; gap: 15px; }
    .form-row { display: flex; gap: 15px; flex-wrap: wrap; }
    .form-group { flex: 1; min-width: 200px; }
    .form-group label { display: block; margin-bottom: 5px; color: #aaa; }
    .form-group input, .form-group select {
      width: 100%; padding: 10px; border-radius: 6px;
      border: 1px solid rgba(255,255,255,0.2); background: rgba(0,0,0,0.3);
      color: #fff; font-size: 14px;
    }
    .form-group input[type="file"] { padding: 8px; }
    .form-group input::placeholder { color: #666; }

    .btn {
      background: #00d4ff; color: #1a1a2e; padding: 12px 30px;
      border: none; border-radius: 6px; font-weight: 600;
      cursor: pointer; font-size: 14px;
    }
    .btn:hover { background: #00b8e6; }
    .btn:disabled { background: #555; cursor: not-allowed; }
    .btn-secondary {
      background: transparent;
      border: 1px solid #00d4ff;
      color: #00d4ff;
    }
    .btn-secondary:hover {
      background: rgba(0,212,255,0.1);
    }

    /* Progress Bar */
    .progress-container { display: none; margin-top: 15px; }
    .progress-bar {
      height: 24px; background: rgba(0,0,0,0.3); border-radius: 12px;
      overflow: hidden; position: relative;
    }
    .progress-fill {
      height: 100%; background: linear-gradient(90deg, #00d4ff, #00ff88);
      width: 0%; transition: width 0.2s;
    }
    .progress-text {
      position: absolute; top: 50%; left: 50%;
      transform: translate(-50%, -50%); font-size: 12px; font-weight: 600;
    }
    .upload-status { margin-top: 10px; font-size: 14px; }
    .status-success { color: #28a745; }
    .status-error { color: #dc3545; }

    /* File Table */
    .file-table { width: 100%; border-collapse: collapse; }
    .file-table th, .file-table td {
      text-align: left; padding: 12px;
      border-bottom: 1px solid rgba(255,255,255,0.1);
    }
    .file-table th { color: #00d4ff; font-weight: 600; }
    .file-table td { color: #ccc; }
    .file-table a { color: #00d4ff; text-decoration: none; }
    .file-table a:hover { text-decoration: underline; }
    .no-data { color: #888; font-style: italic; padding: 20px; text-align: center; }
    .size { white-space: nowrap; }
    .md5 { font-family: monospace; font-size: 11px; color: #888; }

    /* Syslog Controls */
    .controls {
      display: flex; gap: 15px; flex-wrap: wrap; align-items: flex-end;
      margin-bottom: 20px;
    }
    .control-group { flex: 1; min-width: 150px; }
    .control-group label {
      display: block; margin-bottom: 5px; color: #aaa; font-size: 14px;
    }
    .control-group input {
      width: 100%; padding: 10px; border-radius: 6px;
      border: 1px solid rgba(255,255,255,0.2); background: rgba(0,0,0,0.3);
      color: #fff; font-size: 14px;
    }

    /* Syslog Messages */
    .messages-container {
      background: rgba(0,0,0,0.3);
      border-radius: 8px;
      padding: 15px;
      max-height: 500px;
      overflow-y: auto;
      font-family: monospace;
      font-size: 13px;
      line-height: 1.6;
    }
    .message-line {
      padding: 4px 0;
      border-bottom: 1px solid rgba(255,255,255,0.05);
    }
    .msg-timestamp { color: #888; }
    .msg-host { color: #00d4ff; font-weight: 600; }
    .msg-severity { padding: 2px 6px; border-radius: 3px; font-size: 11px; }
    .sev-emerg, .sev-alert, .sev-crit { background: #dc3545; color: white; }
    .sev-err { background: #fd7e14; color: white; }
    .sev-warning { background: #ffc107; color: #1a1a2e; }
    .sev-notice { background: #17a2b8; color: white; }
    .sev-info { background: #28a745; color: white; }
    .sev-debug { background: #6c757d; color: white; }

    .status-bar {
      display: flex; justify-content: space-between; align-items: center;
      margin-top: 15px; font-size: 12px; color: #888;
    }
    .status-dot {
      display: inline-block; width: 8px; height: 8px;
      border-radius: 50%; margin-right: 6px;
    }
    .status-connected { background: #28a745; }
    .status-disconnected { background: #dc3545; }

    /* API Section */
    .api-table { width: 100%; border-collapse: collapse; }
    .api-table th, .api-table td {
      text-align: left; padding: 12px;
      border-bottom: 1px solid rgba(255,255,255,0.1);
    }
    .api-table th { color: #00d4ff; font-weight: 600; }
    .api-table code {
      background: rgba(0,212,255,0.1); padding: 3px 8px;
      border-radius: 4px; font-family: monospace;
    }
    .method {
      display: inline-block; padding: 3px 8px; border-radius: 4px;
      font-size: 0.85rem; font-weight: 600;
    }
    .method-get { background: #28a745; color: white; }
    .method-post { background: #007bff; color: white; }

    /* Footer Status */
    .footer {
      text-align: center; padding: 20px; color: #666; font-size: 0.9rem;
    }
    .footer .status-dot {
      display: inline-block; width: 10px; height: 10px;
      background: #28a745; border-radius: 50%; margin-right: 8px;
      animation: pulse 2s infinite;
    }
    @keyframes pulse {
      0%, 100% { opacity: 1; }
      50% { opacity: 0.5; }
    }
  </style>
</head>
<body>
  <header class="header">
    <div class="header-content">
      <div class="logo">FWServe</div>
      <nav class="nav">
        <button class="nav-btn active" data-tab="files">Files</button>
        <button class="nav-btn" data-tab="syslog">Syslog</button>
        <button class="nav-btn" data-tab="api">API</button>
      </nav>
    </div>
  </header>

  <div class="container">
    <!-- Files Tab -->
    <div id="files-tab" class="tab-content active">
      <div class="panel">
        <h2>Upload Firmware</h2>
        <form class="upload-form" id="uploadForm">
          <div class="form-row">
            <div class="form-group">
              <label for="file">Firmware File (.bin)</label>
              <input type="file" id="file" name="file" accept=".bin" required />
            </div>
            <div class="form-group">
              <label for="md5">MD5 Hash (optional)</label>
              <input type="text" id="md5" name="md5"
                placeholder="e.g. d41d8cd98f00b204e9800998ecf8427e"
                pattern="[a-fA-F0-9]{32}" maxlength="32" />
            </div>
          </div>
          <div style="display: flex; gap: 10px;">
            <button type="submit" class="btn" id="uploadBtn">Upload</button>
          </div>
        </form>
        <div class="progress-container" id="progressContainer">
          <div class="progress-bar">
            <div class="progress-fill" id="progressFill"></div>
            <span class="progress-text" id="progressText">0%</span>
          </div>
          <div class="upload-status" id="uploadStatus"></div>
        </div>
      </div>

      <div class="panel">
        <h2>Available Files</h2>
        <div id="fileList"><p class="no-data">Loading...</p></div>
      </div>
    </div>

    <!-- Syslog Tab -->
    <div id="syslog-tab" class="tab-content">
      <div class="panel">
        <h2>Filters</h2>
        <div class="controls">
          <div class="control-group">
            <label for="syslog-host">Host</label>
            <input type="text" id="syslog-host" placeholder="Filter by hostname" />
          </div>
          <div class="control-group">
            <label for="syslog-severity">Severity</label>
            <input type="text" id="syslog-severity" placeholder="e.g. error, warning" />
          </div>
          <div class="control-group">
            <label for="syslog-query">Message Contains</label>
            <input type="text" id="syslog-query" placeholder="Search in messages" />
          </div>
          <button class="btn" id="syslog-apply">Apply Filters</button>
        </div>
      </div>

      <div class="panel">
        <h2>Messages</h2>
        <div class="messages-container" id="syslog-messages">
          <p class="no-data">Loading messages...</p>
        </div>
        <div class="status-bar">
          <span id="syslog-connection">
            <span class="status-dot status-disconnected"></span>
            Connecting...
          </span>
          <span id="syslog-count">0 messages</span>
        </div>
      </div>
    </div>

    <!-- API Tab -->
    <div id="api-tab" class="tab-content">
      <div class="panel">
        <h2>API Endpoints</h2>
        <table class="api-table">
          <tr>
            <th>Endpoint</th>
            <th>Method</th>
            <th>Description</th>
          </tr>
          <tr>
            <td><code>/health</code></td>
            <td><span class="method method-get">GET</span></td>
            <td>Health check endpoint</td>
          </tr>
          <tr>
            <td><code>/files</code></td>
            <td><span class="method method-get">GET</span></td>
            <td>List available .bin files with metadata</td>
          </tr>
          <tr>
            <td><code>/files/{filename}</code></td>
            <td><span class="method method-get">GET</span></td>
            <td>Download a specific file</td>
          </tr>
          <tr>
            <td><code>/upload</code></td>
            <td><span class="method method-post">POST</span></td>
            <td>Upload a .bin file with optional MD5 hash</td>
          </tr>
          <tr>
            <td><code>/syslog/history</code></td>
            <td><span class="method method-get">GET</span></td>
            <td>Get recent syslog entries</td>
          </tr>
          <tr>
            <td><code>/syslog/stream</code></td>
            <td><span class="method method-get">GET</span></td>
            <td>SSE stream of syslog messages</td>
          </tr>
        </table>
        <div style="margin-top: 20px;">
          <a href="/docs" class="btn btn-secondary" target="_blank">
            Open Swagger UI
          </a>
        </div>
      </div>
    </div>
  </div>

  <footer class="footer">
    <span class="status-dot"></span>
    Server is running
  </footer>

  <script>
    // Tab Navigation
    document.querySelectorAll('.nav-btn').forEach(btn => {
      btn.addEventListener('click', () => {
        document.querySelectorAll('.nav-btn').forEach(b => b.classList.remove('active'));
        document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
        btn.classList.add('active');
        document.getElementById(btn.dataset.tab + '-tab').classList.add('active');

        // Initialize syslog when tab is opened
        if (btn.dataset.tab === 'syslog' && !syslogInitialized) {
          initSyslog();
        }
      });
    });

    // Utility Functions
    function formatSize(bytes) {
      if (bytes === 0) return '0 B';
      const k = 1024;
      const sizes = ['B', 'KB', 'MB', 'GB'];
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }

    // File Management
    async function loadFiles() {
      try {
        const resp = await fetch('/files');
        const data = await resp.json();
        const container = document.getElementById('fileList');

        if (!data.files || data.files.length === 0) {
          container.innerHTML = '<p class="no-data">No files uploaded yet</p>';
          return;
        }

        let html = '<table class="file-table"><tr>';
        html += '<th>Filename</th><th>Size</th><th>MD5 Hash</th></tr>';
        for (const f of data.files) {
          html += '<tr>';
          html += '<td><a href="/files/' + f.name + '">' + f.name + '</a></td>';
          html += '<td class="size">' + formatSize(f.size) + '</td>';
          html += '<td class="md5">' + (f.md5 || '<em>Not provided</em>') + '</td>';
          html += '</tr>';
        }
        html += '</table>';
        container.innerHTML = html;
      } catch (e) {
        document.getElementById('fileList').innerHTML =
          '<p class="no-data">Error loading files</p>';
      }
    }

    // Upload with Progress
    document.getElementById('uploadForm').addEventListener('submit', function(e) {
      e.preventDefault();

      const fileInput = document.getElementById('file');
      const md5Input = document.getElementById('md5');
      const btn = document.getElementById('uploadBtn');
      const progress = document.getElementById('progressContainer');
      const fill = document.getElementById('progressFill');
      const text = document.getElementById('progressText');
      const status = document.getElementById('uploadStatus');

      if (!fileInput.files.length) return;

      const formData = new FormData();
      formData.append('file', fileInput.files[0]);
      formData.append('md5', md5Input.value.trim());

      btn.disabled = true;
      progress.style.display = 'block';
      status.textContent = '';
      status.className = 'upload-status';
      fill.style.width = '0%';
      text.textContent = '0%';

      const xhr = new XMLHttpRequest();

      xhr.upload.addEventListener('progress', function(e) {
        if (e.lengthComputable) {
          const pct = Math.round((e.loaded / e.total) * 100);
          fill.style.width = pct + '%';
          text.textContent = pct + '% (' + formatSize(e.loaded) +
            ' / ' + formatSize(e.total) + ')';
        }
      });

      xhr.addEventListener('load', function() {
        btn.disabled = false;
        if (xhr.status === 200) {
          status.textContent = 'Upload complete!';
          status.className = 'upload-status status-success';
          fileInput.value = '';
          md5Input.value = '';
          loadFiles();
        } else {
          let msg = 'Upload failed';
          try {
            const err = JSON.parse(xhr.responseText);
            msg = err.detail || msg;
          } catch (e) {}
          status.textContent = msg;
          status.className = 'upload-status status-error';
        }
      });

      xhr.addEventListener('error', function() {
        btn.disabled = false;
        status.textContent = 'Network error';
        status.className = 'upload-status status-error';
      });

      xhr.open('POST', '/upload');
      xhr.send(formData);
    });

    // Syslog Viewer
    let syslogInitialized = false;
    let eventSource = null;
    let syslogCount = 0;

    function getSeverityClass(sev) {
      const s = (sev || '').toLowerCase();
      if (s.includes('emerg')) return 'sev-emerg';
      if (s.includes('alert')) return 'sev-alert';
      if (s.includes('crit')) return 'sev-crit';
      if (s.includes('err')) return 'sev-err';
      if (s.includes('warn')) return 'sev-warning';
      if (s.includes('notice')) return 'sev-notice';
      if (s.includes('info')) return 'sev-info';
      if (s.includes('debug')) return 'sev-debug';
      return '';
    }

    function formatSyslogEntry(entry) {
      const sev = entry.severity_label || entry.severity || '';
      const sevClass = getSeverityClass(sev);
      return `<div class="message-line">
        <span class="msg-timestamp">${entry.timestamp || ''}</span>
        <span class="msg-host">${entry.host || ''}</span>
        <span class="msg-severity ${sevClass}">${sev}</span>
        <span class="msg-text">${entry.message || ''}</span>
      </div>`;
    }

    function updateSyslogStatus(connected) {
      const el = document.getElementById('syslog-connection');
      el.innerHTML = `<span class="status-dot ${connected ? 'status-connected' : 'status-disconnected'}"></span>` +
        (connected ? 'Connected (streaming)' : 'Disconnected');
    }

    function updateSyslogCount() {
      document.getElementById('syslog-count').textContent =
        syslogCount + ' message' + (syslogCount !== 1 ? 's' : '');
    }

    async function loadSyslogHistory() {
      const host = document.getElementById('syslog-host').value;
      const severity = document.getElementById('syslog-severity').value;
      const query = document.getElementById('syslog-query').value;
      const params = new URLSearchParams({ host, severity, q: query });

      try {
        const response = await fetch(`/syslog/history?${params.toString()}`);
        const data = await response.json();
        const container = document.getElementById('syslog-messages');
        syslogCount = data.entries.length;

        if (syslogCount === 0) {
          container.innerHTML = '<p class="no-data">No messages yet</p>';
        } else {
          container.innerHTML = data.entries.map(formatSyslogEntry).join('');
        }
        updateSyslogCount();
        container.scrollTop = container.scrollHeight;
      } catch (e) {
        document.getElementById('syslog-messages').innerHTML =
          '<p class="no-data" style="color: #dc3545;">Error loading messages</p>';
      }
    }

    function connectSyslogStream() {
      const host = document.getElementById('syslog-host').value;
      const severity = document.getElementById('syslog-severity').value;
      const query = document.getElementById('syslog-query').value;
      const params = new URLSearchParams({ host, severity, q: query });

      if (eventSource) eventSource.close();

      eventSource = new EventSource(`/syslog/stream?${params.toString()}`);
      eventSource.onopen = () => updateSyslogStatus(true);
      eventSource.onerror = () => updateSyslogStatus(false);

      eventSource.onmessage = (event) => {
        if (!event.data) return;
        try {
          const entry = JSON.parse(event.data);
          const container = document.getElementById('syslog-messages');
          if (syslogCount === 0) {
            container.innerHTML = formatSyslogEntry(entry);
          } else {
            container.innerHTML += formatSyslogEntry(entry);
          }
          syslogCount++;
          updateSyslogCount();
          container.scrollTop = container.scrollHeight;
        } catch (e) {}
      };
    }

    function initSyslog() {
      syslogInitialized = true;
      loadSyslogHistory().then(connectSyslogStream);

      document.getElementById('syslog-apply').addEventListener('click', () => {
        syslogCount = 0;
        loadSyslogHistory().then(connectSyslogStream);
      });
    }

    // Initialize
    loadFiles();
  </script>
</body>
</html>
