inspector-preview.html
Read-only332 lines • 9378 bytes
<!--
@id inspector-preview
@title Flutter Network Inspector Preview
@description Interactive live preview of the Interceptly Flutter Network Inspector DevTool
@order 5
@tags flutter, devtools, network, showcase
-->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Interceptly — Flutter Network Inspector</title>
  <style>
    :root {
      --bg: #1B1E2B;
      --card-bg: #292D3E;
      --border: #32374D;
      --accent: #80CBC4;
      --accent-hover: #4db6ac;
      --text: #babed8;
      --text-white: #ffffff;
      --text-muted: #757CA1;
      --status-200: #C3E88D;
      --status-400: #FFCB6B;
      --status-500: #FF5370;
    }
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "JetBrains Mono", monospace;
      background: var(--bg);
      color: var(--text);
      height: 100vh;
      display: flex;
      flex-direction: column;
      overflow: hidden;
    }
    header {
      background: var(--card-bg);
      border-bottom: 1px solid var(--border);
      padding: 10px 16px;
      display: flex;
      align-items: center;
      justify-content: space-between;
    }
    .brand {
      display: flex;
      align-items: center;
      gap: 10px;
    }
    .badge {
      background: rgba(128, 203, 196, 0.15);
      color: var(--accent);
      border: 1px solid var(--accent);
      padding: 2px 8px;
      border-radius: 4px;
      font-size: 11px;
      font-weight: 600;
      text-transform: uppercase;
    }
    .controls {
      display: flex;
      gap: 8px;
    }
    button {
      background: var(--card-bg);
      border: 1px solid var(--border);
      color: var(--text);
      padding: 6px 12px;
      border-radius: 4px;
      cursor: pointer;
      font-size: 12px;
      font-family: inherit;
      transition: all 0.2s;
    }
    button:hover {
      background: var(--border);
      color: var(--text-white);
    }
    button.primary {
      background: var(--accent);
      color: #1B1E2B;
      border: none;
      font-weight: 600;
    }
    button.primary:hover {
      background: var(--accent-hover);
    }
    .main-container {
      display: flex;
      flex: 1;
      overflow: hidden;
    }
    .requests-list {
      width: 48%;
      border-right: 1px solid var(--border);
      overflow-y: auto;
    }
    .request-item {
      padding: 10px 14px;
      border-bottom: 1px solid var(--border);
      cursor: pointer;
      display: flex;
      align-items: center;
      justify-content: space-between;
      transition: background 0.15s;
    }
    .request-item:hover {
      background: rgba(255, 255, 255, 0.03);
    }
    .request-item.active {
      background: rgba(128, 203, 196, 0.1);
      border-left: 3px solid var(--accent);
    }
    .method-badge {
      font-weight: 700;
      font-size: 11px;
      width: 45px;
      display: inline-block;
    }
    .method-GET { color: #82AAFF; }
    .method-POST { color: #C3E88D; }
    .method-PUT { color: #FFCB6B; }
    .method-DELETE { color: #FF5370; }
    .url-path {
      font-size: 12px;
      color: var(--text-white);
      flex: 1;
      margin: 0 10px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    .status-code {
      font-size: 11px;
      font-weight: 600;
      padding: 2px 6px;
      border-radius: 3px;
    }
    .status-200 { color: var(--status-200); background: rgba(195, 232, 141, 0.1); }
    .status-400 { color: var(--status-400); background: rgba(255, 203, 107, 0.1); }
    .status-500 { color: var(--status-500); background: rgba(255, 83, 112, 0.1); }
    .details-panel {
      flex: 1;
      display: flex;
      flex-direction: column;
      overflow: hidden;
      background: #202434;
    }
    .tabs {
      display: flex;
      background: var(--card-bg);
      border-bottom: 1px solid var(--border);
    }
    .tab {
      padding: 8px 16px;
      font-size: 12px;
      cursor: pointer;
      border-right: 1px solid var(--border);
      color: var(--text-muted);
    }
    .tab.active {
      background: #202434;
      color: var(--accent);
      font-weight: 600;
      border-bottom: 2px solid var(--accent);
    }
    .detail-body {
      padding: 16px;
      overflow-y: auto;
      flex: 1;
      font-size: 12px;
      line-height: 1.6;
    }
    pre {
      background: #1B1E2B;
      padding: 12px;
      border-radius: 6px;
      border: 1px solid var(--border);
      overflow-x: auto;
      color: #89DDFF;
    }
  </style>
</head>
<body>
  <header>
    <div class="brand">
      <span style="font-weight: bold; color: var(--text-white);">Interceptly</span>
      <span class="badge">Flutter DevTool</span>
    </div>
    <div class="controls">
      <button onclick="simulateRequest()">+ Trigger Request</button>
      <button onclick="clearRequests()">Clear</button>
      <button class="primary" onclick="alert('Interceptly Inspector connected to active Flutter Engine')">Connected: 127.0.0.1:8080</button>
    </div>
  </header>

  <div class="main-container">
    <div class="requests-list" id="requestsList"></div>
    <div class="details-panel">
      <div class="tabs">
        <div class="tab active" onclick="switchTab('response')">Response Body</div>
        <div class="tab" onclick="switchTab('headers')">Headers</div>
        <div class="tab" onclick="switchTab('timing')">Timing</div>
      </div>
      <div class="detail-body" id="detailBody">
        <p style="color: var(--text-muted);">Select a request on the left to view payload details.</p>
      </div>
    </div>
  </div>

  <script>
    const mockRequests = [
      {
        id: 1,
        method: "GET",
        url: "/api/v2/portfolio/profile",
        status: 200,
        time: "14ms",
        size: "1.2 KB",
        response: {
          name: "Tuan Do",
          role: "Mobile & Frontend Lead Engineer",
          focus: ["Flutter", "Dart", "Offline-First", "Next.js"],
          openSource: "https://pub.dev/packages/interceptly"
        }
      },
      {
        id: 2,
        method: "POST",
        url: "/api/v1/auth/exchange-token",
        status: 200,
        time: "65ms",
        size: "840 B",
        response: {
          tokenType: "Bearer",
          expiresIn: 3600,
          authenticated: true
        }
      },
      {
        id: 3,
        method: "GET",
        url: "/api/v2/analytics/live-telemetry",
        status: 400,
        time: "32ms",
        size: "310 B",
        response: {
          error: "BadRequest",
          message: "Missing device_fingerprint query param"
        }
      }
    ];

    let selectedReq = mockRequests[0];
    let currentTab = "response";

    function renderList() {
      const container = document.getElementById("requestsList");
      container.innerHTML = mockRequests.map(req => `
        <div class="request-item ${selectedReq && selectedReq.id === req.id ? 'active' : ''}" onclick="selectRequest(${req.id})">
          <span class="method-badge method-${req.method}">${req.method}</span>
          <span class="url-path">${req.url}</span>
          <span class="status-code status-${req.status}">${req.status}</span>
        </div>
      `).join("");
      renderDetail();
    }

    function selectRequest(id) {
      selectedReq = mockRequests.find(r => r.id === id);
      renderList();
    }

    function switchTab(tab) {
      currentTab = tab;
      document.querySelectorAll(".tab").forEach(t => t.classList.remove("active"));
      event.target.classList.add("active");
      renderDetail();
    }

    function renderDetail() {
      const body = document.getElementById("detailBody");
      if (!selectedReq) {
        body.innerHTML = '<p style="color: var(--text-muted);">No request selected.</p>';
        return;
      }
      if (currentTab === "response") {
        body.innerHTML = `
          <h4 style="margin-bottom: 8px; color: var(--text-white);">HTTP ${selectedReq.status} — ${selectedReq.time} (${selectedReq.size})</h4>
          <pre>${JSON.stringify(selectedReq.response, null, 2)}</pre>
        `;
      } else if (currentTab === "headers") {
        body.innerHTML = `
          <h4 style="margin-bottom: 8px; color: var(--text-white);">Request & Response Headers</h4>
          <pre>content-type: application/json; charset=utf-8\nx-request-id: req-${selectedReq.id}-flutter\ncache-control: no-cache</pre>
        `;
      } else {
        body.innerHTML = `
          <h4 style="margin-bottom: 8px; color: var(--text-white);">Network Waterfall</h4>
          <p>DNS Lookup: 2ms</p>
          <p>TLS Handshake: 8ms</p>
          <p>Server Processing: ${selectedReq.time}</p>
        `;
      }
    }

    function simulateRequest() {
      const newId = mockRequests.length + 1;
      mockRequests.unshift({
        id: newId,
        method: "POST",
        url: `/api/v2/orders/checkout-${newId}`,
        status: 200,
        time: `${Math.floor(Math.random() * 80) + 15}ms`,
        size: "620 B",
        response: { success: true, orderId: `ord-${Date.now()}` }
      });
      selectedReq = mockRequests[0];
      renderList();
    }

    function clearRequests() {
      mockRequests.length = 0;
      selectedReq = null;
      renderList();
    }

    renderList();
  </script>
</body>
</html>