Skip to content

Commit 29a5699

Browse files
committed
Add a new tab for version and environment info
1 parent d4cbeaf commit 29a5699

File tree

5 files changed

+168
-34
lines changed

5 files changed

+168
-34
lines changed

mcpgateway/static/admin.js

Lines changed: 91 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,63 @@ document.addEventListener("DOMContentLoaded", function () {
2525
document.getElementById("tab-metrics").addEventListener("click", () => {
2626
showTab("metrics");
2727
});
28+
document.getElementById("tab-version-info").addEventListener("click", () => {
29+
showTab("version-info");
30+
});
31+
32+
// Preload version info on page load (optional)
33+
document.addEventListener("DOMContentLoaded", () => {
34+
const panel = document.getElementById("version-info-panel");
35+
if (panel && panel.innerHTML.trim() === "") {
36+
const url = `${window.ROOT_PATH}/version?partial=true`;
37+
fetch(url)
38+
.then((response) => {
39+
if (!response.ok) {
40+
throw new Error("Network response was not ok");
41+
}
42+
return response.text();
43+
})
44+
.then((html) => {
45+
panel.innerHTML = html;
46+
// Show the version-info panel and activate the tab on page load if hash is #version-info
47+
if (window.location.hash === "#version-info") {
48+
showTab("version-info");
49+
}
50+
})
51+
.cconsole.error("Failed to preload version info:", error);
52+
atch((error) => {;
53+
});
54+
}
55+
})
56+
57+
// HTMX event listeners for debugging
58+
document.body.addEventListener("htmx:beforeRequest", (event) => {
59+
if (event.detail.elt.id === "tab-version-info") {
60+
console.log("HTMX: Sending request for version info partial");
61+
}
62+
);
63+
64+
document.body.addEventListener("htmx:afterSwap", (event) => {
65+
if (event.detail.target.id === "version-info-panel") {
66+
console.log("HTMX: Content swapped into version-info-panel" console.error("Failed to preload version info:", error);
67+
panel.innerHTML = "<p class='text-red-600'>Failed to load version info.</p>";
68+
});
69+
}
70+
});
71+
72+
// HTMX event listeners for debugging
73+
document.body.addEventListener("htmx:beforeRequest", (event) => {
74+
if (event.detail.elt.id === "tab-version-info") {
75+
console.log("HTMX: Sending request for version info partial");
76+
}
77+
});
78+
79+
document.body.addEventListener("htmx:afterSwap", (event) => {
80+
if (event.detail.target.id === "version-info-panel") {
81+
console.log("HTMX: Content swapped into version-info-panel");
82+
}
83+
});
84+
2885
// Authentication toggle
2986
document.getElementById("auth-type").addEventListener("change", function () {
3087
const basicFields = document.getElementById("auth-basic-fields");
@@ -88,15 +145,18 @@ document.addEventListener("DOMContentLoaded", function () {
88145
} else {
89146
basicFields.style.display = "none";
90147
bearerFields.style.display = "none";
91-
headersFields.style.display = "none";
148+
heconsole.log(response);
149+
adersFields.style.display = "none";
92150
}
93151
});
94152

95153
document
96154
.getElementById("add-gateway-form")
97155
.addEventListener("submit", (e) => {
98156
e.preventDefault();
99-
const form = e.target;
157+
const formerror = e.t
158+
console.error("Error:", error);
159+
arget;
100160
const formData = new FormData(form);
101161
fetch(`${window.ROOT_PATH}/admin/gateways`, {
102162
method: "POST",
@@ -110,6 +170,7 @@ document.addEventListener("DOMContentLoaded", function () {
110170
status.classList.add("error-status");
111171
} else {
112172
location.reload();
173+
console.log(response);
113174
}
114175
})
115176
.catch((error) => {
@@ -118,7 +179,9 @@ document.addEventListener("DOMContentLoaded", function () {
118179
});
119180

120181
document
121-
.getElementById("add-resource-form")
182+
.getElementBerroryId("a
183+
console.error("Error:", error);
184+
dd-resource-form")
122185
.addEventListener("submit", (e) => {
123186
e.preventDefault();
124187
const form = e.target;
@@ -261,7 +324,7 @@ document.addEventListener("DOMContentLoaded", function () {
261324
"edit-tool-request-type",
262325
);
263326

264-
const requestTypeMap = {
327+
const requeM= {-ed mtho, if valid
265328
MCP: ["SSE", "STDIO"],
266329
REST: ["GET", "POST", "PUT", "DELETE"],
267330
};
@@ -329,11 +392,33 @@ function showTab(tabName) {
329392
.classList.add("border-indigo-500", "text-indigo-600");
330393
document
331394
.querySelector(`[href="#${tabName}"]`)
332-
.classList.remove("border-transparent", "text-gray-500");
395+
.classconsole.error("Failed to load version info:", error);
396+
List.remove("border-transparent", "text-gray-500");
333397

334398
if (tabName === "metrics") {
335399
loadAggregatedMetrics();
336400
}
401+
402+
if (tabName === "version-info") {
403+
const panel = document.getElementById("version-info-panel");
404+
if (panel && panel.innerHTML.trim() === "") {
405+
const url = `${window.ROOT_PATH}/version?partial=true`;
406+
fetch(url)
407+
.then((response) => {
408+
if (!response.ok) {
409+
throw new Error("Network response was not ok");
410+
}
411+
return response.text();
412+
})
413+
.then((html) => {
414+
panel.innerHTML = html;
415+
})
416+
.catch((error) => {
417+
console.error("Failed to load version info:", error);
418+
panel.innerHTML = "<p class='text-red-600'>Failed to load version info.</p>";
419+
});
420+
}
421+
}
337422
}
338423

339424
// handle auth type selection
@@ -407,29 +492,7 @@ function updateSchemaPreview() {
407492
}
408493

409494
// Refresh CodeMirror every time Direct JSON Input is selected
410-
Array.from(schemaModeRadios).forEach((radio) => {
411-
radio.addEventListener("change", () => {
412-
if (radio.value === "ui" && radio.checked) {
413-
uiBuilderDiv.style.display = "block";
414-
jsonInputContainer.style.display = "none";
415-
} else if (radio.value === "json" && radio.checked) {
416-
uiBuilderDiv.style.display = "none";
417-
jsonInputContainer.style.display = "block";
418-
updateSchemaPreview();
419-
}
420-
});
421-
});
422-
423-
// Attach event listeners to dynamically added parameter inputs
424-
function attachListeners(paramDiv) {
425-
const inputs = paramDiv.querySelectorAll("input, select, textarea");
426-
inputs.forEach((input) => {
427-
input.addEventListener("input", () => {
428-
const mode = document.querySelector(
429-
'input[name="schema_input_mode"]:checked',
430-
).value;
431-
if (mode === "json") {
432-
updateSchemaPreview();
495+
Array.from(schemaModeant) updateSchemaPreview();
433496
}
434497
});
435498
});

mcpgateway/templates/admin.html

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,15 +114,18 @@ <h1 class="text-3xl font-bold text-gray-800">
114114
</a>
115115
<!-- New Version tab (opens separate /version page) -->
116116
<a
117-
href="{{ root_path }}/version"
118-
target="_blank"
119-
class="border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"
117+
href="#version-info"
118+
id="tab-version-info"
119+
class="tab-link border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm"
120120
>
121-
Version
121+
Version and Environment Info
122122
</a>
123123
</nav>
124124
</div>
125125
</div>
126+
127+
<!-- Version Info Panel -->
128+
<div id="version-info-panel" class="tab-panel hidden"></div>
126129

127130
<!-- Catalog Panel -->
128131
<div id="catalog-panel" class="tab-panel">
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<div class="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl p-6">
2+
<div class="md:flex">
3+
<div class="md:flex-shrink-0">
4+
<svg class="h-12 w-12 text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
5+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 1.343-3 3 0 1.657 1.343 3 3 3s3-1.343 3-3c0-1.657-1.343-3-3-3z" />
6+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 2v2m0 16v2m8-10h2M2 12H4m15.364 6.364l1.414 1.414M4.222 4.222l1.414 1.414m12.728 0l-1.414 1.414M6.636 17.364l-1.414 1.414" />
7+
</svg>
8+
</div>
9+
<div class="mt-4 md:mt-0 md:ml-6">
10+
<div class="uppercase tracking-wide text-sm text-indigo-500 font-semibold">Version and Environment Info</div>
11+
<p class="block mt-1 text-lg leading-tight font-medium text-black">App: {{ payload.app.name }}</p>
12+
<p class="mt-1 text-gray-500">Version: {{ payload.app.git_revision or "N/A" }}</p>
13+
<p class="mt-1 text-gray-500">Protocol Version: {{ payload.app.mcp_protocol_version }}</p>
14+
<p class="mt-1 text-gray-500">Runtime: {{ payload.platform.python }} + FastAPI {{ payload.platform.fastapi }}</p>
15+
<p class="mt-1 text-gray-500">Container: Docker</p>
16+
<button
17+
id="copy-json-btn"
18+
class="mt-4 px-3 py-1 bg-indigo-600 text-white rounded hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500"
19+
onclick="copyJsonToClipboard()"
20+
>
21+
Copy JSON to Clipboard
22+
</button>
23+
<pre id="json-data" class="mt-2 p-2 bg-gray-100 rounded text-xs overflow-auto" style="max-height: 200px;">{{ payload | tojson(indent=2) }}</pre>
24+
</div>
25+
</div>
26+
</div>
27+
28+
<script>
29+
function copyJsonToClipboard() {
30+
const jsonText = document.getElementById('json-data').innerText;
31+
navigator.clipboard.writeText(jsonText).then(() => {
32+
alert('JSON copied to clipboard!');
33+
}, (err) => {
34+
alert('Failed to copy JSON: ' + err);
35+
});
36+
}
37+
</script>

mcpgateway/version.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,14 +364,16 @@ def _login_html(next_url: str) -> str:
364364
async def version_endpoint(
365365
request: Request,
366366
fmt: Optional[str] = None,
367+
partial: Optional[bool] = False,
367368
_user=Depends(require_auth),
368369
) -> Response:
369370
"""
370-
Serve diagnostics as JSON or HTML (if requested).
371+
Serve diagnostics as JSON, full HTML, or partial HTML (if requested).
371372
372373
Parameters:
373374
request (Request): The incoming HTTP request.
374-
fmt (Optional[str]): Query param 'html' for HTML output.
375+
fmt (Optional[str]): Query param 'html' for full HTML output.
376+
partial (Optional[bool]): Query param to request partial HTML fragment.
375377
376378
Returns:
377379
Response: JSONResponse or HTMLResponse with diagnostics data.
@@ -390,6 +392,11 @@ async def version_endpoint(
390392
redis_version = str(exc)
391393

392394
payload = _build_payload(redis_version, redis_ok)
395+
if partial:
396+
# Return partial HTML fragment for HTMX embedding
397+
from fastapi.templating import Jinja2Templates
398+
templates = Jinja2Templates(directory="mcpgateway/templates")
399+
return templates.TemplateResponse("version_info_partial.html", {"request": request, "payload": payload})
393400
wants_html = fmt == "html" or "text/html" in request.headers.get("accept", "")
394401
if wants_html:
395402
return HTMLResponse(_render_html(payload))

tests/unit/mcpgateway/test_main.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,30 @@ def test_ready_check(self, test_client):
5454
assert response.status_code == 200
5555
assert response.json()["status"] == "ready"
5656

57+
def test_version_partial_html(self, test_client, auth_headers):
58+
"""Test the /version endpoint with partial=true returns HTML fragment."""
59+
response = test_client.get("/version?partial=true", headers=auth_headers)
60+
assert response.status_code == 200
61+
assert "text/html" in response.headers["content-type"]
62+
content = response.text
63+
assert "Version and Environment Info" in content
64+
assert "App:" in content
65+
66+
def test_admin_ui_contains_version_tab(self, test_client, auth_headers):
67+
"""Test the Admin UI contains the Version and Environment Info tab."""
68+
response = test_client.get("/admin", headers=auth_headers)
69+
assert response.status_code == 200
70+
content = response.text
71+
assert 'id="tab-version-info"' in content
72+
assert "Version and Environment Info" in content
73+
74+
def test_version_partial_htmx_load(self, test_client, auth_headers):
75+
"""Test HTMX request to /version?partial=true returns the partial HTML."""
76+
response = test_client.get("/version?partial=true", headers=auth_headers)
77+
assert response.status_code == 200
78+
assert "Version and Environment Info" in response.text
79+
assert "<div" in response.text
80+
5781
def test_root_redirect(self, test_client):
5882
"""Test root path redirects to admin."""
5983
response = test_client.get("/", allow_redirects=False)

0 commit comments

Comments
 (0)