feat: k6 load testing web UI with Docker
- Multi-stage Dockerfile builds k6 from source (grafana/k6) - Express backend with SSE live output streaming - SQLite-backed test history with delete support - Frontend: URL input, VUs, duration, RPS limit, custom headers - Persisted via Docker volume, listens on port 8118 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
104
frontend/index.html
Normal file
104
frontend/index.html
Normal file
@@ -0,0 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>k6 Load Tester</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<header>
|
||||
<h1>⚡ k6 Load Tester</h1>
|
||||
<nav>
|
||||
<button class="tab-btn active" data-tab="run">Run Test</button>
|
||||
<button class="tab-btn" data-tab="history">History</button>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<!-- RUN TAB -->
|
||||
<section id="tab-run" class="tab active">
|
||||
<form id="test-form">
|
||||
<div class="form-grid">
|
||||
<div class="form-group full">
|
||||
<label for="url">Target URL</label>
|
||||
<input type="url" id="url" placeholder="https://example.com" required />
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="httpMethod">HTTP Method</label>
|
||||
<select id="httpMethod">
|
||||
<option value="GET">GET</option>
|
||||
<option value="POST">POST</option>
|
||||
<option value="PUT">PUT</option>
|
||||
<option value="PATCH">PATCH</option>
|
||||
<option value="DELETE">DELETE</option>
|
||||
<option value="HEAD">HEAD</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="vus">Virtual Users (VUs)</label>
|
||||
<div class="range-row">
|
||||
<input type="range" id="vus" min="1" max="500" value="10" />
|
||||
<input type="number" id="vus-num" min="1" max="500" value="10" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="duration">Duration (seconds)</label>
|
||||
<div class="range-row">
|
||||
<input type="range" id="duration" min="5" max="300" value="30" />
|
||||
<input type="number" id="duration-num" min="5" max="300" value="30" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="rpsLimit">Max RPS <span class="hint">(0 = unlimited)</span></label>
|
||||
<input type="number" id="rpsLimit" min="0" value="0" />
|
||||
</div>
|
||||
|
||||
<div class="form-group full" id="body-group" style="display:none">
|
||||
<label for="requestBody">Request Body</label>
|
||||
<textarea id="requestBody" rows="4" placeholder='{"key": "value"}'></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group full">
|
||||
<label for="headers">
|
||||
Custom Headers <span class="hint">(JSON, optional)</span>
|
||||
</label>
|
||||
<input type="text" id="headers" placeholder='{"Authorization": "Bearer token"}' />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" id="start-btn" class="btn-primary">▶ Run Test</button>
|
||||
</form>
|
||||
|
||||
<div id="result-panel" style="display:none">
|
||||
<div class="result-header">
|
||||
<h2>Test Running</h2>
|
||||
<span id="result-status" class="badge running">running</span>
|
||||
</div>
|
||||
<pre id="output-log"></pre>
|
||||
<div id="summary-panel" style="display:none">
|
||||
<h3>Summary</h3>
|
||||
<div id="summary-cards" class="summary-grid"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- HISTORY TAB -->
|
||||
<section id="tab-history" class="tab">
|
||||
<div class="history-header">
|
||||
<h2>Test History</h2>
|
||||
<button id="refresh-history" class="btn-secondary">↻ Refresh</button>
|
||||
</div>
|
||||
<div id="history-list">
|
||||
<p class="empty">No tests yet.</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user