Skip to content

Commit 7e45278

Browse files
committed
Fix new UI version
Signed-off-by: Mihai Criveti <[email protected]>
1 parent 29a5699 commit 7e45278

File tree

3 files changed

+181
-70
lines changed

3 files changed

+181
-70
lines changed

mcpgateway/static/admin.js

Lines changed: 88 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -29,46 +29,44 @@ document.addEventListener("DOMContentLoaded", function () {
2929
showTab("version-info");
3030
});
3131

32-
// Preload version info on page load (optional)
32+
/* ------------------------------------------------------------------
33+
* Pre-load the "Version & Environment Info" partial once per page
34+
* ------------------------------------------------------------------ */
35+
/* Pre-load version-info once */
3336
document.addEventListener("DOMContentLoaded", () => {
3437
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-
})
38+
if (!panel || panel.innerHTML.trim() !== "") return; // already loaded
5639

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-
);
40+
fetch(`${window.ROOT_PATH}/version?partial=true`)
41+
.then((response) => {
42+
if (!response.ok) throw new Error("Network response was not ok");
43+
return response.text();
44+
})
45+
.then((html) => {
46+
panel.innerHTML = html;
47+
48+
// If the page was opened at #version-info, show that tab now
49+
if (window.location.hash === "#version-info") {
50+
showTab("version-info");
51+
}
52+
})
53+
.catch((error) => {
54+
console.error("Failed to preload version info:", error);
55+
panel.innerHTML =
56+
"<p class='text-red-600'>Failed to load version info.</p>";
57+
});
58+
});
6359

60+
/* ------------------------------------------------------------------
61+
* HTMX debug hooks
62+
* ------------------------------------------------------------------ */
6463
document.body.addEventListener("htmx:afterSwap", (event) => {
6564
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-
});
65+
console.log("HTMX: Content swapped into version-info-panel");
6966
}
7067
});
7168

69+
7270
// HTMX event listeners for debugging
7371
document.body.addEventListener("htmx:beforeRequest", (event) => {
7472
if (event.detail.elt.id === "tab-version-info") {
@@ -145,8 +143,7 @@ document.addEventListener("DOMContentLoaded", function () {
145143
} else {
146144
basicFields.style.display = "none";
147145
bearerFields.style.display = "none";
148-
heconsole.log(response);
149-
adersFields.style.display = "none";
146+
headersFields.style.display = "none";
150147
}
151148
});
152149

@@ -179,9 +176,7 @@ document.addEventListener("DOMContentLoaded", function () {
179176
});
180177

181178
document
182-
.getElementBerroryId("a
183-
console.error("Error:", error);
184-
dd-resource-form")
179+
.getElementById("add-resource-form")
185180
.addEventListener("submit", (e) => {
186181
e.preventDefault();
187182
const form = e.target;
@@ -324,11 +319,12 @@ document.addEventListener("DOMContentLoaded", function () {
324319
"edit-tool-request-type",
325320
);
326321

327-
const requeM= {-ed mtho, if valid
322+
const requestTypeMap = {
328323
MCP: ["SSE", "STDIO"],
329324
REST: ["GET", "POST", "PUT", "DELETE"],
330325
};
331326

327+
332328
// Optionally pass in a pre-selected method
333329
function updateEditToolRequestTypes(selectedMethod = null) {
334330
const selectedType = editToolTypeSelect.value;
@@ -392,8 +388,7 @@ function showTab(tabName) {
392388
.classList.add("border-indigo-500", "text-indigo-600");
393389
document
394390
.querySelector(`[href="#${tabName}"]`)
395-
.classconsole.error("Failed to load version info:", error);
396-
List.remove("border-transparent", "text-gray-500");
391+
.classList.remove("border-transparent", "text-gray-500");
397392

398393
if (tabName === "metrics") {
399394
loadAggregatedMetrics();
@@ -491,12 +486,22 @@ function updateSchemaPreview() {
491486
}
492487
}
493488

494-
// Refresh CodeMirror every time Direct JSON Input is selected
495-
Array.from(schemaModeant) updateSchemaPreview();
496-
}
497-
});
489+
/* ---------------------------------------------------------------
490+
* Switch between "UI-builder" and "JSON input" modes
491+
* ------------------------------------------------------------- */
492+
Array.from(schemaModeRadios).forEach((radio) => {
493+
radio.addEventListener("change", () => {
494+
if (radio.value === "ui" && radio.checked) {
495+
uiBuilderDiv.style.display = "block";
496+
jsonInputContainer.style.display = "none";
497+
} else if (radio.value === "json" && radio.checked) {
498+
uiBuilderDiv.style.display = "none";
499+
jsonInputContainer.style.display = "block";
500+
updateSchemaPreview(); // keep preview in sync
501+
}
498502
});
499-
}
503+
}); // closes addEventListener callback, forEach callback, and forEach call
504+
500505

501506
// On form submission, update CodeMirror with UI builder schema if needed
502507
// document.getElementById('add-tool-form').addEventListener('submit', (e) => {
@@ -1694,6 +1699,45 @@ async function runToolTest() {
16941699
});
16951700
}
16961701

1702+
/* ---------------------------------------------------------------
1703+
* Utility: copy a JSON string (or any text) to the system clipboard
1704+
* ------------------------------------------------------------- */
1705+
function copyJsonToClipboard(sourceId) {
1706+
// 1. Get the element that holds the JSON (can be a <pre>, <code>, <textarea>, etc.)
1707+
const el = document.getElementById(sourceId);
1708+
if (!el) {
1709+
console.warn(`[copyJsonToClipboard] Source element "${sourceId}" not found.`);
1710+
return;
1711+
}
1712+
1713+
// 2. Extract the text; fall back to textContent if value is undefined
1714+
const text = "value" in el ? el.value : el.textContent;
1715+
1716+
// 3. Copy to clipboard
1717+
navigator.clipboard.writeText(text).then(
1718+
() => {
1719+
console.info("JSON copied to clipboard ✔️");
1720+
// Optional: user feedback
1721+
if (el.dataset.toast !== "off") {
1722+
const toast = document.createElement("div");
1723+
toast.textContent = "Copied!";
1724+
toast.className =
1725+
"fixed bottom-4 right-4 bg-green-600 text-white px-3 py-1 rounded shadow";
1726+
document.body.appendChild(toast);
1727+
setTimeout(() => toast.remove(), 1500);
1728+
}
1729+
},
1730+
(err) => {
1731+
console.error("Clipboard write failed:", err);
1732+
alert("Unable to copy to clipboard - see console for details.");
1733+
}
1734+
);
1735+
}
1736+
1737+
// Make it available to inline onclick handlers
1738+
window.copyJsonToClipboard = copyJsonToClipboard;
1739+
1740+
16971741
// Utility functions to open and close modals
16981742
function openModal(modalId) {
16991743
document.getElementById(modalId).classList.remove("hidden");

mcpgateway/templates/admin.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ <h1 class="text-3xl font-bold text-gray-800">
123123
</nav>
124124
</div>
125125
</div>
126-
126+
127127
<!-- Version Info Panel -->
128128
<div id="version-info-panel" class="tab-panel hidden"></div>
129129

Lines changed: 92 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,104 @@
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" />
1+
<div class="max-w-2xl mx-auto bg-white rounded-xl shadow-md overflow-hidden p-6">
2+
<div class="md:flex md:space-x-6">
3+
<!-- Icon -->
4+
<div class="flex-shrink-0">
5+
<svg class="h-12 w-12 text-indigo-600" xmlns="http://www.w3.org/2000/svg" fill="none"
6+
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
7+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
8+
d="M12 8a3 3 0 11-3 3 3 3 0 013-3zm0-6v2m0 16v2m8-10h2M2 12h2m13.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"/>
79
</svg>
810
</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>
11+
12+
<!-- Details -->
13+
<div class="flex-1">
14+
<h2 class="uppercase tracking-wide text-sm text-indigo-500 font-semibold">
15+
Version & Environment
16+
</h2>
17+
18+
<!-- App section -->
19+
<div class="mt-2">
20+
<p class="text-lg font-medium text-black">App: {{ payload.app.name }}</p>
21+
<p class="text-gray-500">Git rev: {{ payload.app.git_revision or 'N/A' }}</p>
22+
<p class="text-gray-500">Protocol: {{ payload.app.mcp_protocol_version }}</p>
23+
</div>
24+
25+
<!-- Host & runtime -->
26+
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 gap-x-4 text-gray-700 text-sm">
27+
<div>
28+
<p><span class="font-semibold">Host:</span> {{ payload.host }}</p>
29+
<p><span class="font-semibold">Uptime:</span> {{ payload.uptime_seconds }} s</p>
30+
<p><span class="font-semibold">Boot:</span> {{ payload.system.boot_time }}</p>
31+
</div>
32+
<div>
33+
<p><span class="font-semibold">Python:</span> {{ payload.platform.python }}</p>
34+
<p><span class="font-semibold">FastAPI:</span> {{ payload.platform.fastapi }}</p>
35+
<p><span class="font-semibold">OS:</span> {{ payload.platform.os }}</p>
36+
</div>
37+
</div>
38+
39+
<!-- DB / Redis health -->
40+
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 gap-x-4 text-gray-700 text-sm">
41+
<div>
42+
<p class="font-semibold">Database</p>
43+
<p>Dialect: {{ payload.database.dialect }}</p>
44+
<p>Status:
45+
{% if payload.database.reachable %}
46+
<span class="text-green-600">reachable</span>
47+
{% else %}
48+
<span class="text-red-600">unreachable</span>
49+
{% endif %}
50+
</p>
51+
</div>
52+
<div>
53+
<p class="font-semibold">Redis</p>
54+
<p>
55+
{% if payload.redis.available %}
56+
<span class="text-green-600">available</span>
57+
{% else %}
58+
<span class="text-red-600">not available</span>
59+
{% endif %}
60+
</p>
61+
</div>
62+
</div>
63+
64+
<!-- System snapshot -->
65+
<div class="mt-4 text-gray-700 text-sm">
66+
<p class="font-semibold">System</p>
67+
<p>CPU: {{ payload.system.cpu_count }} × {{ payload.system.cpu_freq_mhz }} MHz</p>
68+
<p>Memory: {{ "{:.1f}".format(payload.system.mem_used_mb) }} / {{ payload.system.mem_total_mb }} MB</p>
69+
<p>Disk: {{ "{:.1f}".format(payload.system.disk_used_gb) }} / {{ "{:.1f}".format(payload.system.disk_total_gb) }} GB</p>
70+
</div>
71+
72+
<!-- Copy-JSON button -->
1673
<button
17-
id="copy-json-btn"
1874
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
75+
onclick="copyJsonToClipboard('json-data')">
76+
Copy full JSON
2277
</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>
78+
79+
<!-- Collapsible raw JSON (small & scrollable) -->
80+
<pre id="json-data"
81+
class="mt-2 p-2 bg-gray-100 rounded text-xs overflow-auto"
82+
style="max-height: 220px;">{{ payload | tojson(indent=2) }}</pre>
2483
</div>
2584
</div>
2685
</div>
2786

2887
<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-
});
88+
/**
89+
* Copy the innerText of the element with id sourceId (defaults to "json-data")
90+
* to the clipboard. Intended for inline onclick handlers.
91+
*/
92+
function copyJsonToClipboard(sourceId = 'json-data') {
93+
const el = document.getElementById(sourceId);
94+
if (!el) {
95+
console.warn('[copyJsonToClipboard] sourceId "%s" not found', sourceId);
96+
return;
3697
}
98+
navigator.clipboard.writeText(el.innerText)
99+
.then(() => alert('JSON copied to clipboard!'))
100+
.catch(err => alert('Failed to copy JSON: ' + err));
101+
}
102+
// expose globally for inline usage
103+
window.copyJsonToClipboard = copyJsonToClipboard;
37104
</script>

0 commit comments

Comments
 (0)