Skip to content

Commit ec66a7f

Browse files
Vickycrivetimihai
authored andcommitted
feat: Add bulk import UI modal for tools
Signed-off-by: Vicky <vicky.kuo.contact@gmail.com>
1 parent e7dcd88 commit ec66a7f

File tree

5 files changed

+219
-5
lines changed

5 files changed

+219
-5
lines changed

cookies.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Netscape HTTP Cookie File
2+
# https://curl.se/docs/http-cookies.html
3+
# This file was generated by libcurl! Edit at your own risk.
4+

headers.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
HTTP/1.1 307 Temporary Redirect
2+
date: Wed, 13 Aug 2025 05:39:40 GMT
3+
server: uvicorn
4+
content-length: 0
5+
location: http://localhost:4444/admin/
6+

mcpgateway/static/admin.js

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6905,3 +6905,81 @@ window.updateAuthHeadersJSON = updateAuthHeadersJSON;
69056905
window.loadAuthHeaders = loadAuthHeaders;
69066906

69076907
console.log("🛡️ ContextForge MCP Gateway admin.js initialized");
6908+
6909+
6910+
// ===================================================================
6911+
// BULK IMPORT TOOLS — MODAL WIRING
6912+
// ===================================================================
6913+
6914+
(function initBulkImportModal() {
6915+
// ensure it runs after the DOM is ready
6916+
window.addEventListener("DOMContentLoaded", function () {
6917+
const openBtn = safeGetElement("open-bulk-import", true);
6918+
const modalId = "bulk-import-modal";
6919+
const modal = safeGetElement(modalId, true);
6920+
6921+
if (!openBtn || !modal) {
6922+
console.warn("Bulk Import modal wiring skipped (missing button or modal).");
6923+
return;
6924+
}
6925+
6926+
// avoid double-binding if admin.js gets evaluated more than once
6927+
if (openBtn.dataset.wired === "1") return;
6928+
openBtn.dataset.wired = "1";
6929+
6930+
const closeBtn = safeGetElement("close-bulk-import", true);
6931+
const backdrop = safeGetElement("bulk-import-backdrop", true);
6932+
const resultEl = safeGetElement("import-result", true);
6933+
6934+
const focusTarget =
6935+
modal.querySelector("#tools_json") ||
6936+
modal.querySelector("#tools_file") ||
6937+
modal.querySelector("[data-autofocus]");
6938+
6939+
// helpers
6940+
const open = (e) => {
6941+
if (e) e.preventDefault();
6942+
// clear previous results each time we open
6943+
if (resultEl) resultEl.innerHTML = "";
6944+
openModal(modalId);
6945+
// prevent background scroll
6946+
document.documentElement.classList.add("overflow-hidden");
6947+
document.body.classList.add("overflow-hidden");
6948+
if (focusTarget) setTimeout(() => focusTarget.focus(), 0);
6949+
return false;
6950+
};
6951+
6952+
const close = () => {
6953+
// also clear results on close to keep things tidy
6954+
closeModal(modalId, "import-result");
6955+
document.documentElement.classList.remove("overflow-hidden");
6956+
document.body.classList.remove("overflow-hidden");
6957+
};
6958+
6959+
// wire events
6960+
openBtn.addEventListener("click", open);
6961+
6962+
if (closeBtn) {
6963+
closeBtn.addEventListener("click", (e) => {
6964+
e.preventDefault();
6965+
close();
6966+
});
6967+
}
6968+
6969+
// click on backdrop only (not the dialog content) closes the modal
6970+
if (backdrop) {
6971+
backdrop.addEventListener("click", (e) => {
6972+
if (e.target === backdrop) close();
6973+
});
6974+
}
6975+
6976+
// ESC to close
6977+
modal.addEventListener("keydown", (e) => {
6978+
if (e.key === "Escape") {
6979+
e.stopPropagation();
6980+
close();
6981+
}
6982+
});
6983+
6984+
});
6985+
})();

mcpgateway/templates/admin.html

Lines changed: 112 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1070,11 +1070,63 @@ <h2 class="text-2xl font-bold dark:text-gray-200">
10701070
</table>
10711071
</div>
10721072
</div>
1073-
10741073
<div class="bg-white shadow rounded-lg p-6 dark:bg-gray-800">
1075-
<h3 class="text-lg font-bold mb-4 dark:text-gray-200">
1076-
Add New Tool
1077-
</h3>
1074+
<div class="flex items-center justify-between mb-4">
1075+
<h3 class="text-lg font-bold dark:text-gray-200">Add New Tool</h3>
1076+
<button
1077+
id="open-bulk-import"
1078+
type="button"
1079+
class="inline-flex items-center gap-1 text-sm text-indigo-600 hover:text-indigo-700"
1080+
>
1081+
+ Bulk Import Tools
1082+
</button>
1083+
<!-- Right before </body> tag -->
1084+
<script>
1085+
// Super simple version - no dependencies
1086+
window.addEventListener('load', function() {
1087+
console.log('Setting up bulk import...');
1088+
1089+
const button = document.getElementById('open-bulk-import');
1090+
const modal = document.getElementById('bulk-import-modal');
1091+
1092+
console.log('Button found:', !!button);
1093+
console.log('Modal found:', !!modal);
1094+
1095+
if (button && modal) {
1096+
button.onclick = function(e) {
1097+
e.preventDefault();
1098+
console.log('Button clicked!');
1099+
modal.style.display = 'block';
1100+
modal.classList.remove('hidden');
1101+
return false;
1102+
};
1103+
1104+
// Close button
1105+
const closeBtn = document.getElementById('close-bulk-import');
1106+
if (closeBtn) {
1107+
closeBtn.onclick = function() {
1108+
modal.style.display = 'none';
1109+
modal.classList.add('hidden');
1110+
};
1111+
}
1112+
1113+
// Click backdrop to close
1114+
const backdrop = document.getElementById('bulk-import-backdrop');
1115+
if (backdrop) {
1116+
backdrop.onclick = function() {
1117+
modal.style.display = 'none';
1118+
modal.classList.add('hidden');
1119+
};
1120+
}
1121+
} else {
1122+
console.error('Missing elements!', {button: !!button, modal: !!modal});
1123+
}
1124+
});
1125+
</script>
1126+
1127+
</div>
1128+
1129+
10781130
<form id="add-tool-form">
10791131
<div class="grid grid-cols-1 gap-6">
10801132
<div>
@@ -1302,8 +1354,63 @@ <h3 class="text-lg font-bold mb-4 dark:text-gray-200">
13021354
</div>
13031355
</form>
13041356
</div>
1305-
</div>
1357+
<!-- Modal -->
1358+
<div id="bulk-import-modal" class="fixed inset-0 z-[60] hidden">
1359+
<div id="bulk-import-backdrop" class="absolute inset-0 bg-black/50"></div>
1360+
<div role="dialog" aria-modal="true"
1361+
class="relative mx-auto my-12 w-full max-w-2xl rounded-xl bg-white p-0 shadow-xl dark:bg-gray-900">
1362+
<div class="flex items-center justify-between border-b px-5 py-3 dark:border-gray-700">
1363+
<h3 class="text-base font-semibold dark:text-gray-100">Bulk Import Tools</h3>
1364+
<button id="close-bulk-import" data-close-bulk-import
1365+
class="rounded p-1 text-gray-500 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
1366+
aria-label="Close"></button>
1367+
</div>
1368+
1369+
<form id="bulk-import-form"
1370+
hx-post="{{ root_path }}/admin/tools/import"
1371+
hx-target="#bulk-import-result"
1372+
hx-swap="innerHTML"
1373+
hx-indicator="#bulk-import-indicator"
1374+
enctype="multipart/form-data"
1375+
class="space-y-4 p-5">
1376+
<p class="text-sm text-gray-600 dark:text-gray-400">
1377+
Paste a JSON array or upload a <code>.json</code> file. Max 200 tools.
1378+
</p>
1379+
1380+
<label class="block text-sm font-medium dark:text-gray-300" for="tools_json">JSON Data</label>
1381+
<textarea id="tools_json" name="tools_json" rows="8"
1382+
class="mt-1 block w-full rounded-md border border-gray-300 font-mono text-sm shadow-sm
1383+
focus:border-indigo-500 focus:ring-indigo-500 dark:border-gray-700 dark:bg-gray-900 dark:text-gray-300"
1384+
placeholder='[{"name":"tool_name","url":"https://...","integration_type":"REST","request_type":"GET"}]'></textarea>
1385+
1386+
<div>
1387+
<label class="block text-sm font-medium dark:text-gray-300" for="tools_file">Or upload JSON file</label>
1388+
<input id="tools_file" name="tools_file" type="file" accept="application/json,.json"
1389+
class="mt-1 block w-full text-sm dark:text-gray-300" />
1390+
</div>
1391+
1392+
<div class="flex items-center gap-3 pt-1">
1393+
<button type="submit"
1394+
class="inline-flex justify-center rounded-md bg-indigo-600 px-4 py-2 text-sm font-medium text-white
1395+
hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500">
1396+
Import Tools
1397+
</button>
1398+
<span id="bulk-import-indicator" class="htmx-indicator flex items-center text-sm text-gray-500 dark:text-gray-400">
1399+
<svg class="mr-2 h-4 w-4 animate-spin" viewBox="0 0 24 24" fill="none">
1400+
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
1401+
<path class="opacity-75" fill="currentColor"
1402+
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/>
1403+
</svg>
1404+
Processing...
1405+
</span>
1406+
</div>
1407+
1408+
<div id="bulk-import-result" class="pt-2"></div>
1409+
</form>
1410+
</div>
1411+
</div>
13061412

1413+
13071414
<!-- Resources Panel -->
13081415
<div id="resources-panel" class="tab-panel hidden">
13091416
<div class="flex justify-between items-center mb-4">

tools.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[
2+
{
3+
"name": "list_users",
4+
"url": "https://api.example.com/users",
5+
"integration_type": "REST",
6+
"request_type": "GET"
7+
},
8+
{
9+
"name": "create_user",
10+
"url": "https://api.example.com/users",
11+
"integration_type": "REST",
12+
"request_type": "POST",
13+
"input_schema": {
14+
"type": "object",
15+
"properties": { "body": { "type": "object" } },
16+
"required": ["body"]
17+
}
18+
}
19+
]

0 commit comments

Comments
 (0)