Skip to content

Commit bac4c6e

Browse files
authored
Merge pull request #127 from IBM/loading_animation
UI changes: Added loading animation when running or testing a tool, also making it easier to differentiate between previous and current tool call responses. Added loading animation when adding a new gateway or MCP server.
2 parents f1a298e + d634b7b commit bac4c6e

File tree

3 files changed

+103
-58
lines changed

3 files changed

+103
-58
lines changed

mcpgateway/static/admin.css

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,24 @@
77
color: #a94442;
88
border: 1px solid #ebccd1;
99
}
10+
11+
12+
/* Add this CSS for the spinner */
13+
.spinner {
14+
border: 4px solid #f3f3f3;
15+
border-top: 4px solid #3498db;
16+
border-radius: 50%;
17+
width: 24px;
18+
height: 24px;
19+
animation: spin 1s linear infinite;
20+
/* margin: 10px auto; */
21+
22+
/* Positioning to the left */
23+
margin: 10px 0 10px 10px; /* top, right, bottom, left */
24+
display: block; /* Ensures it behaves like a block-level element */
25+
}
26+
27+
@keyframes spin {
28+
0% { transform: rotate(0deg); }
29+
100% { transform: rotate(360deg); }
30+
}

mcpgateway/static/admin.js

Lines changed: 72 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -148,30 +148,43 @@ document.addEventListener("DOMContentLoaded", function () {
148148
});
149149

150150
document
151-
.getElementById("add-gateway-form")
152-
.addEventListener("submit", (e) => {
153-
e.preventDefault();
154-
const form = e.target;
155-
const formData = new FormData(form);
156-
fetch(`${window.ROOT_PATH}/admin/gateways`, {
157-
method: "POST",
158-
body: formData,
151+
.getElementById("add-gateway-form")
152+
.addEventListener("submit", (e) => {
153+
e.preventDefault();
154+
155+
const form = e.target;
156+
const formData = new FormData(form);
157+
158+
const status = document.getElementById("status-gateways");
159+
const loading = document.getElementById("add-gateway-loading");
160+
161+
// Show loading and clear previous status
162+
loading.style.display = "block";
163+
status.textContent = "";
164+
status.classList.remove("error-status");
165+
166+
fetch(`${window.ROOT_PATH}/admin/gateways`, {
167+
method: "POST",
168+
body: formData,
169+
})
170+
.then((response) => {
171+
if (!response.ok) {
172+
status.textContent = "Connection failed!";
173+
status.classList.add("error-status");
174+
} else {
175+
location.reload(); // Will exit before hiding spinner
176+
}
159177
})
160-
.then((response) => {
161-
console.log(response);
162-
if (!response.ok) {
163-
const status = document.getElementById("status-gateways");
164-
status.textContent = "Connection failed!";
165-
status.classList.add("error-status");
166-
} else {
167-
location.reload();
168-
console.log(response);
169-
}
170-
})
171-
.catch((error) => {
172-
console.error("Error:", error);
173-
});
174-
});
178+
.catch((error) => {
179+
console.error("Error:", error);
180+
status.textContent = "An error occurred!";
181+
status.classList.add("error-status");
182+
})
183+
.finally(() => {
184+
loading.style.display = "none"; // Hide loading
185+
});
186+
});
187+
175188

176189
document
177190
.getElementById("add-resource-form")
@@ -319,7 +332,7 @@ document.addEventListener("DOMContentLoaded", function () {
319332

320333
const requestTypeMap = {
321334
MCP: ["SSE", "STREAMABLE", "STDIO"],
322-
REST: ["GET", "POST", "PUT", "PATCH", "DELETE"],
335+
REST: ["GET", "POST", "PUT", "DELETE"],
323336
};
324337

325338

@@ -1650,9 +1663,9 @@ async function runToolTest() {
16501663
const formData = new FormData(form);
16511664
const params = {};
16521665
for (const [key, value] of formData.entries()) {
1653-
if(isNaN(value)) {
1654-
if(value.toLowerCase() === "true" || value.toLowerCase() === "false") {
1655-
params[key] = Boolean(value.toLowerCase() === "true");
1666+
if (isNaN(value)) {
1667+
if (value.toLowerCase() === "true" || value.toLowerCase() === "false") {
1668+
params[key] = value.toLowerCase() === "true";
16561669
} else {
16571670
params[key] = value;
16581671
}
@@ -1661,46 +1674,47 @@ async function runToolTest() {
16611674
}
16621675
}
16631676

1664-
// Build the JSON-RPC payload using the tool's name as the method
16651677
const payload = {
16661678
jsonrpc: "2.0",
16671679
id: Date.now(),
16681680
method: currentTestTool.name,
16691681
params: params,
16701682
};
16711683

1672-
// Send the request to your /rpc endpoint
1673-
fetch(`${window.ROOT_PATH}/rpc`, {
1674-
method: "POST",
1675-
headers: {
1676-
"Content-Type": "application/json", // ← make sure we include this
1677-
},
1678-
body: JSON.stringify(payload),
1679-
credentials: "include",
1680-
})
1681-
.then((response) => response.json())
1682-
.then((result) => {
1683-
const resultStr = JSON.stringify(result, null, 2);
1684-
1685-
const container = document.getElementById("tool-test-result");
1686-
container.innerHTML = ''; // clear any old editor
1687-
1688-
toolTestResultEditor = window.CodeMirror(
1689-
document.getElementById("tool-test-result"),
1690-
{
1691-
value: resultStr,
1692-
mode: "application/json",
1693-
theme: "monokai",
1694-
readOnly: true,
1695-
lineNumbers: true,
1696-
},
1697-
);
1698-
})
1699-
.catch((error) => {
1700-
document.getElementById("tool-test-result").innerText = "Error: " + error;
1684+
// Show loading
1685+
const loadingElement = document.getElementById("tool-test-loading");
1686+
loadingElement.style.display = "block";
1687+
const resultContainer = document.getElementById("tool-test-result");
1688+
resultContainer.innerHTML = "";
1689+
1690+
try {
1691+
const response = await fetch(`${window.ROOT_PATH}/rpc`, {
1692+
method: "POST",
1693+
headers: {
1694+
"Content-Type": "application/json",
1695+
},
1696+
body: JSON.stringify(payload),
1697+
credentials: "include",
1698+
});
1699+
1700+
const result = await response.json();
1701+
const resultStr = JSON.stringify(result, null, 2);
1702+
1703+
toolTestResultEditor = window.CodeMirror(resultContainer, {
1704+
value: resultStr,
1705+
mode: "application/json",
1706+
theme: "monokai",
1707+
readOnly: true,
1708+
lineNumbers: true,
17011709
});
1710+
} catch (error) {
1711+
resultContainer.innerText = "Error: " + error;
1712+
} finally {
1713+
loadingElement.style.display = "none"; // Hide loading after fetch or error
1714+
}
17021715
}
17031716

1717+
17041718
/* ---------------------------------------------------------------
17051719
* Utility: copy a JSON string (or any text) to the system clipboard
17061720
* ------------------------------------------------------------- */
@@ -1759,7 +1773,7 @@ function closeModal(modalId, clearId=null) {
17591773

17601774
const integrationRequestMap = {
17611775
MCP: ["SSE", "STREAMABLE", "STDIO"],
1762-
REST: ["GET", "POST", "PUT", "PATCH", "DELETE"],
1776+
REST: ["GET", "POST", "PUT", "DELETE"],
17631777
};
17641778

17651779
function updateRequestTypeOptions(preselectedValue = null) {

mcpgateway/templates/admin.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,9 @@ <h3 class="text-lg font-bold mb-4">Add New Gateway</h3>
14781478
/>
14791479
</div>
14801480
</div>
1481+
<div id="add-gateway-loading" style="display: none;">
1482+
<div class="spinner"></div>
1483+
</div>
14811484
<div id="status-gateways"></div>
14821485
</div>
14831486
<div class="mt-6">
@@ -1730,6 +1733,10 @@ <h3 class="text-lg font-medium text-gray-900">Tool Details</h3>
17301733
Run Tool
17311734
</button>
17321735
</div>
1736+
<div id="tool-test-loading" style="display: none;">
1737+
<div class="spinner"></div>
1738+
</div>
1739+
17331740
</form>
17341741
<!-- The result area now uses a fixed height for a larger view -->
17351742
<div
@@ -1751,6 +1758,9 @@ <h3 class="text-lg font-medium text-gray-900">Tool Details</h3>
17511758
</div>
17521759
</div>
17531760

1761+
1762+
1763+
17541764
<!-- Tool Edit Modal -->
17551765
<div id="tool-edit-modal" class="fixed z-10 inset-0 overflow-y-auto hidden">
17561766
<div

0 commit comments

Comments
 (0)