Skip to content

Commit dfe15bf

Browse files
author
wlanboy
committed
added html view for sending requests and store history of recent requests
1 parent fa3fc15 commit dfe15bf

File tree

2 files changed

+313
-0
lines changed

2 files changed

+313
-0
lines changed
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
document.addEventListener('DOMContentLoaded', () => {
2+
// UI Elemente
3+
const form = document.getElementById('httpForm');
4+
const headerContainer = document.getElementById('headerContainer');
5+
const addHeaderBtn = document.getElementById('addHeaderBtn');
6+
const submitBtn = document.getElementById('submitBtn');
7+
const resultArea = document.getElementById('resultArea');
8+
const responseOutput = document.getElementById('responseOutput');
9+
const errorBox = document.getElementById('errorBox');
10+
const statusBadge = document.getElementById('statusBadge');
11+
const responseTimeText = document.getElementById('responseTime');
12+
const stacktraceArea = document.getElementById('stacktraceArea');
13+
14+
// Historie Elemente
15+
const historyList = document.getElementById('historyList');
16+
const emptyHistoryMsg = document.getElementById('emptyHistoryMsg');
17+
const clearHistoryBtn = document.getElementById('clearHistoryBtn');
18+
const exportHistoryBtn = document.getElementById('exportHistoryBtn');
19+
20+
let requestHistory = [];
21+
22+
// --- INITIALISIERUNG ---
23+
addHeaderRow('Content-Type', 'application/json');
24+
25+
// --- EVENT LISTENER ---
26+
addHeaderBtn.addEventListener('click', () => addHeaderRow());
27+
28+
document.getElementById('toggleStackBtn').addEventListener('click', () => {
29+
stacktraceArea.style.display = stacktraceArea.style.display === 'none' ? 'block' : 'none';
30+
});
31+
32+
clearHistoryBtn.addEventListener('click', () => {
33+
requestHistory = [];
34+
renderHistory();
35+
emptyHistoryMsg.style.display = 'block';
36+
});
37+
38+
exportHistoryBtn.addEventListener('click', exportHistoryToJson);
39+
40+
form.addEventListener('submit', async (e) => {
41+
e.preventDefault();
42+
await performRequest();
43+
});
44+
45+
// --- CORE FUNKTIONEN ---
46+
47+
async function performRequest() {
48+
const startTime = Date.now();
49+
const payload = {
50+
url: document.getElementById('url').value,
51+
method: document.getElementById('method').value,
52+
body: document.getElementById('body').value,
53+
copyHeaders: document.getElementById('copyHeaders').checked,
54+
customHeaders: collectHeaders()
55+
};
56+
57+
prepareUIForRequest();
58+
59+
try {
60+
const response = await fetch('/client', {
61+
method: 'POST',
62+
headers: { 'Content-Type': 'application/json' },
63+
body: JSON.stringify(payload)
64+
});
65+
66+
const duration = Date.now() - startTime;
67+
const data = await response.text();
68+
69+
updateResponseMetadata(response.status, duration);
70+
addToHistory(payload, response.status, duration, data);
71+
72+
if (response.status === 502 && data.includes("---STACKTRACE---")) {
73+
handleDetailedError(data);
74+
} else {
75+
handleSuccess(data);
76+
}
77+
} catch (err) {
78+
const errorMsg = "Netzwerkfehler zum Proxy: " + err.message;
79+
handleNetworkError(errorMsg);
80+
addToHistory(payload, 0, 0, errorMsg);
81+
} finally {
82+
submitBtn.disabled = false;
83+
}
84+
}
85+
86+
// --- HILFSFUNKTIONEN ---
87+
88+
function addHeaderRow(key = '', value = '') {
89+
const div = document.createElement('div');
90+
div.className = 'row g-2 mb-2 header-row';
91+
div.innerHTML = `
92+
<div class="col-5"><input type="text" class="form-control form-control-sm h-key" placeholder="Key" value="${key}"></div>
93+
<div class="col-6"><input type="text" class="form-control form-control-sm h-val" placeholder="Value" value="${value}"></div>
94+
<div class="col-1 text-end"><button type="button" class="btn btn-sm btn-outline-danger remove-header">✕</button></div>
95+
`;
96+
div.querySelector('.remove-header').onclick = () => div.remove();
97+
headerContainer.appendChild(div);
98+
}
99+
100+
function collectHeaders() {
101+
const headers = {};
102+
document.querySelectorAll('.header-row').forEach(row => {
103+
const k = row.querySelector('.h-key').value.trim();
104+
const v = row.querySelector('.h-val').value.trim();
105+
if (k) headers[k] = v;
106+
});
107+
return headers;
108+
}
109+
110+
function prepareUIForRequest() {
111+
submitBtn.disabled = true;
112+
resultArea.style.display = 'block';
113+
errorBox.style.display = 'none';
114+
responseOutput.style.display = 'block';
115+
responseOutput.innerText = "Sende Request an Cluster...";
116+
stacktraceArea.style.display = 'none';
117+
}
118+
119+
function updateResponseMetadata(status, duration) {
120+
statusBadge.innerText = `HTTP ${status}`;
121+
statusBadge.className = `badge p-2 ${status >= 200 && status < 300 ? 'bg-success' : 'bg-danger'}`;
122+
responseTimeText.innerText = `Dauer: ${duration}ms`;
123+
}
124+
125+
function handleDetailedError(data) {
126+
const [info, stack] = data.split("---STACKTRACE---");
127+
const lines = info.split("\n");
128+
errorBox.style.display = 'block';
129+
responseOutput.style.display = 'none';
130+
document.getElementById('errorSummary').innerText = lines[0];
131+
document.getElementById('errorDetail').innerText = lines[1] || "";
132+
stacktraceArea.innerText = stack.trim();
133+
}
134+
135+
function handleSuccess(data) {
136+
errorBox.style.display = 'none';
137+
responseOutput.style.display = 'block';
138+
try {
139+
const json = JSON.parse(data);
140+
responseOutput.innerText = JSON.stringify(json, null, 2);
141+
} catch {
142+
responseOutput.innerText = data || "Empty Response";
143+
}
144+
}
145+
146+
function handleNetworkError(msg) {
147+
responseOutput.innerText = msg;
148+
statusBadge.innerText = "Error";
149+
statusBadge.className = "badge bg-warning text-dark";
150+
}
151+
152+
// --- HISTORIE & EXPORT ---
153+
154+
function addToHistory(payload, status, duration, responseData) {
155+
const entry = {
156+
id: Date.now(),
157+
time: new Date().toLocaleTimeString(),
158+
payload, status, duration, responseData
159+
};
160+
requestHistory.unshift(entry);
161+
renderHistory();
162+
}
163+
164+
function renderHistory() {
165+
emptyHistoryMsg.style.display = requestHistory.length ? 'none' : 'block';
166+
historyList.innerHTML = '';
167+
requestHistory.forEach(entry => {
168+
const isErr = entry.status >= 400 || entry.status === 0;
169+
const item = document.createElement('button');
170+
item.className = `list-group-item list-group-item-action border-start border-4 ${isErr ? 'border-danger' : 'border-success'}`;
171+
item.innerHTML = `
172+
<div class="d-flex justify-content-between"><strong>${entry.payload.method}</strong><small>${entry.time}</small></div>
173+
<div class="text-truncate small">${entry.payload.url}</div>
174+
<div class="mt-1"><span class="badge ${isErr ? 'bg-danger' : 'bg-success'}">${entry.status}</span> <small>${entry.duration}ms</small></div>
175+
`;
176+
item.onclick = () => restoreEntry(entry);
177+
historyList.appendChild(item);
178+
});
179+
}
180+
181+
function restoreEntry(entry) {
182+
document.getElementById('url').value = entry.payload.url;
183+
document.getElementById('method').value = entry.payload.method;
184+
document.getElementById('body').value = entry.payload.body;
185+
document.getElementById('copyHeaders').checked = entry.payload.copyHeaders;
186+
187+
headerContainer.innerHTML = '';
188+
Object.entries(entry.payload.customHeaders).forEach(([k, v]) => addHeaderRow(k, v));
189+
190+
resultArea.style.display = 'block';
191+
updateResponseMetadata(entry.status, entry.duration);
192+
if (entry.status === 502 && entry.responseData.includes("---STACKTRACE---")) {
193+
handleDetailedError(entry.responseData);
194+
} else {
195+
handleSuccess(entry.responseData);
196+
}
197+
}
198+
199+
function exportHistoryToJson() {
200+
if (!requestHistory.length) return alert("Historie ist leer!");
201+
const dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(requestHistory, null, 2));
202+
const downloadAnchor = document.createElement('a');
203+
downloadAnchor.setAttribute("href", dataStr);
204+
downloadAnchor.setAttribute("download", `k8s_http_history_${new Date().toISOString().split('T')[0]}.json`);
205+
document.body.appendChild(downloadAnchor);
206+
downloadAnchor.click();
207+
downloadAnchor.remove();
208+
}
209+
});
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<!DOCTYPE html>
2+
<html lang="de" xmlns:th="http://www.thymeleaf.org">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>K8s HTTP Debugger</title>
7+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
8+
<style>
9+
body { background-color: #f0f2f5; font-size: 0.85rem; }
10+
.card { border: none; border-radius: 10px; }
11+
.console { background-color: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 6px; font-family: 'Consolas', monospace; white-space: pre-wrap; min-height: 150px; }
12+
.stacktrace { font-size: 0.7rem; color: #f87171; background: #2a2a2a; padding: 10px; margin-top: 10px; border-radius: 4px; display: none; overflow-x: auto; }
13+
.list-group-item { cursor: pointer; transition: background 0.2s; }
14+
.list-group-item:hover { background-color: #f8f9fa; }
15+
</style>
16+
</head>
17+
<body>
18+
19+
<nav class="navbar navbar-dark bg-dark mb-4">
20+
<div class="container-fluid">
21+
<span class="navbar-brand mb-0 h1">Kubernetes HTTP Client Tool</span>
22+
</div>
23+
</nav>
24+
25+
<div class="container-fluid px-4">
26+
<div class="row g-4">
27+
<div class="col-lg-8">
28+
<div class="card shadow-sm mb-4">
29+
<div class="card-body">
30+
<form id="httpForm">
31+
<div class="row g-2 mb-3">
32+
<div class="col-md-2">
33+
<label class="form-label fw-bold">Methode</label>
34+
<select id="method" class="form-select fw-bold border-primary">
35+
<option>GET</option><option>POST</option><option>PUT</option><option>DELETE</option>
36+
</select>
37+
</div>
38+
<div class="col-md-10">
39+
<label class="form-label fw-bold">Ziel-URL (Cluster-intern/extern)</label>
40+
<input type="text" id="url" class="form-control" placeholder="http://service.namespace.svc.cluster.local:8080" required>
41+
</div>
42+
</div>
43+
44+
<div class="mb-3">
45+
<label class="form-label fw-bold text-muted">Headers</label>
46+
<div id="headerContainer"></div>
47+
<button type="button" id="addHeaderBtn" class="btn btn-sm btn-outline-primary mt-1">+ Header</button>
48+
</div>
49+
50+
<div class="mb-3">
51+
<label class="form-label fw-bold">Body (JSON)</label>
52+
<textarea id="body" class="form-control" rows="3" style="font-family: monospace;"></textarea>
53+
</div>
54+
55+
<div class="d-flex justify-content-between align-items-center">
56+
<div class="form-check form-switch">
57+
<input class="form-check-input" type="checkbox" id="copyHeaders" checked>
58+
<label class="form-check-label">Browser Headers spiegeln</label>
59+
</div>
60+
<button type="submit" id="submitBtn" class="btn btn-primary px-5">Senden</button>
61+
</div>
62+
</form>
63+
</div>
64+
</div>
65+
66+
<div id="resultArea" style="display:none;">
67+
<div class="d-flex align-items-center mb-2">
68+
<span id="statusBadge" class="badge"></span>
69+
<small id="responseTime" class="ms-3 text-muted fw-bold"></small>
70+
</div>
71+
72+
<div id="errorBox" class="alert alert-danger shadow-sm" style="display:none;">
73+
<h6 id="errorSummary" class="fw-bold mb-1"></h6>
74+
<p id="errorDetail" class="small mb-2"></p>
75+
<button type="button" id="toggleStackBtn" class="btn btn-sm btn-outline-danger">Stacktrace Details</button>
76+
<pre id="stacktraceArea" class="stacktrace"></pre>
77+
</div>
78+
79+
<pre id="responseOutput" class="console shadow-sm"></pre>
80+
</div>
81+
</div>
82+
83+
<div class="col-lg-4">
84+
<div class="card shadow-sm h-100">
85+
<div class="card-header bg-white d-flex justify-content-between align-items-center py-3">
86+
<h6 class="mb-0 fw-bold">Sitzungsverlauf</h6>
87+
<div>
88+
<button class="btn btn-xs btn-outline-success border-0 me-1" id="exportHistoryBtn" title="Als JSON exportieren">
89+
Export
90+
</button>
91+
<button class="btn btn-xs btn-outline-danger border-0" id="clearHistoryBtn">Löschen</button>
92+
</div>
93+
</div>
94+
<div id="historyList" class="list-group list-group-flush overflow-auto" style="max-height: 75vh;">
95+
<div id="emptyHistoryMsg" class="p-4 text-center text-muted italic">Keine Anfragen aufgezeichnet</div>
96+
</div>
97+
</div>
98+
</div>
99+
</div>
100+
</div>
101+
102+
<script src="/js/http-client.js"></script>
103+
</body>
104+
</html>

0 commit comments

Comments
 (0)