Skip to content

Commit 0c9f317

Browse files
committed
Latest admin changes - 01/03
Signed-off-by: Gabriel Costa <gabrielcg@proton.me>
1 parent 4c0a6e2 commit 0c9f317

15 files changed

Lines changed: 505 additions & 104 deletions

mcpgateway/admin_ui/a2aAgents.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { closeModal, openModal } from "./modals.js";
22
import { escapeHtml, validateInputName, validateUrl } from "./security.js";
3+
import { applyVisibilityRestrictions } from "./teams.js";
34
import { getAuthToken } from "./tokens.js";
45
import {
56
decodeHtml,
@@ -327,9 +328,9 @@ export const editA2AAgent = async function (agentId) {
327328
// ✅ Prefill visibility radios (consistent with server)
328329
const visibility = agent.visibility ? agent.visibility.toLowerCase() : null;
329330

330-
const publicRadio = safeGetElement("a2a-visibility-public-edit");
331-
const teamRadio = safeGetElement("a2a-visibility-team-edit");
332-
const privateRadio = safeGetElement("a2a-visibility-private-edit");
331+
const publicRadio = safeGetElement("edit-a2a-visibility-public");
332+
const teamRadio = safeGetElement("edit-a2a-visibility-team");
333+
const privateRadio = safeGetElement("edit-a2a-visibility-private");
333334

334335
// Clear all first
335336
if (publicRadio) {
@@ -546,6 +547,7 @@ export const editA2AAgent = async function (agentId) {
546547
}
547548

548549
openModal("a2a-edit-modal");
550+
applyVisibilityRestrictions(["edit-a2a-visibility"]); // Disable public radio if restricted, preserve checked state
549551
console.log("✓ A2A Agent edit modal loaded successfully");
550552
} catch (err) {
551553
console.error("Error loading A2A agent:", err);

mcpgateway/admin_ui/appState.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ export const AppState = {
2828
},
2929
_paginationQuerySetters: {},
3030
editServerSelections: {},
31+
/**
32+
* Fragment names differ from entity type names for some entities.
33+
* e.g. the "servers" toggle navigates to the #catalog tab.
34+
*/
35+
_TOGGLE_FRAGMENT_MAP: {
36+
servers: "catalog",
37+
},
3138

3239
// Track active modals to prevent multiple opens
3340
activeModals: new Set(),

mcpgateway/admin_ui/formFieldHandlers.js

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -482,13 +482,40 @@ const performTeamSelectorSearch = function (searchTerm) {
482482

483483
const url = `${window.ROOT_PATH || ""}/admin/teams/partial?${params.toString()}`;
484484

485-
// Use HTMX to load results
486-
if (window.htmx) {
487-
window.htmx.ajax("GET", url, {
488-
target: "#team-selector-items",
489-
swap: "innerHTML",
490-
});
485+
// Load results via fetch for reliable error handling; htmx.ajax() does not
486+
// reject on HTTP 5xx so we cannot detect backend failures with it.
487+
if (container) {
488+
container.innerHTML =
489+
'<div class="px-4 py-2 text-sm text-gray-500 dark:text-gray-400">Loading\u2026</div>';
491490
}
491+
492+
fetch(url, { credentials: "same-origin" })
493+
.then(function (resp) {
494+
if (!resp.ok) {
495+
throw new Error("HTTP " + resp.status);
496+
}
497+
return resp.text();
498+
})
499+
.then(function (html) {
500+
if (container) {
501+
container.innerHTML = html;
502+
container.dataset.loaded = "true";
503+
if (window.htmx) {
504+
window.htmx.process(container);
505+
}
506+
}
507+
})
508+
.catch(function () {
509+
if (container) {
510+
delete container.dataset.loaded;
511+
container.innerHTML =
512+
'<div class="px-4 py-2 text-sm text-red-600 dark:text-red-400">' +
513+
"Failed to load teams. " +
514+
'<button type="button" ' +
515+
"onclick=\"delete document.getElementById('team-selector-items').dataset.loaded; searchTeamSelector('');\" " +
516+
'class="underline font-medium">Retry</button></div>';
517+
}
518+
});
492519
}
493520

494521
/**

mcpgateway/admin_ui/formHandlers.js

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,56 @@
1-
import { injectCsrfTokenIntoForm } from "./security.js";
2-
import { isInactiveChecked } from "./utils.js";
1+
import { AppState } from "./appState.js";
2+
import { navigateAdmin } from "./navigation.js";
3+
import { getCookie, isInactiveChecked } from "./utils.js";
34

45
// ===================================================================
56
// INACTIVE ITEMS HANDLING
67
// ===================================================================
7-
export const handleToggleSubmit = function (event, type) {
8+
export const handleToggleSubmit = async function (event, type) {
89
event.preventDefault();
910

1011
const isInactiveCheckedBool = isInactiveChecked(type);
1112
const form = event.target;
12-
const hiddenField = document.createElement("input");
13-
hiddenField.type = "hidden";
14-
hiddenField.name = "is_inactive_checked";
15-
hiddenField.value = isInactiveCheckedBool;
13+
const teamId = new URL(window.location.href).searchParams.get("team_id");
1614

17-
form.appendChild(hiddenField);
15+
// Build FormData from current form state (captures any fields already
16+
// appended by handleDeleteSubmit such as purge_metrics).
17+
const formData = new FormData(form);
18+
formData.set("is_inactive_checked", String(isInactiveCheckedBool));
19+
if (teamId && !formData.has("team_id")) {
20+
formData.set("team_id", teamId);
21+
}
22+
const csrfToken =
23+
typeof getCookie === "function"
24+
? getCookie("mcpgateway_csrf_token") || ""
25+
: "";
26+
if (csrfToken) {
27+
formData.set("csrf_token", csrfToken);
28+
}
1829

19-
// Inject team_id from URL so backend preserves team scope in redirect
20-
const teamId = new URL(window.location.href).searchParams.get("team_id");
21-
if (teamId && !form.querySelector('input[name="team_id"]')) {
22-
const teamField = document.createElement("input");
23-
teamField.type = "hidden";
24-
teamField.name = "team_id";
25-
teamField.value = teamId;
26-
form.appendChild(teamField);
30+
try {
31+
// Use redirect:'manual' so the browser does not follow the 303
32+
// redirect to the backend-direct URL (which bypasses the proxy).
33+
await fetch(form.action, {
34+
method: "POST",
35+
body: formData,
36+
credentials: "include",
37+
redirect: "manual",
38+
});
39+
} catch (e) {
40+
// Network error — still navigate so the user sees refreshed state.
41+
console.error("Toggle submit error:", e);
2742
}
2843

29-
injectCsrfTokenIntoForm(form);
30-
form.submit();
44+
// Navigate using proxy-aware helper so proxy prefix is preserved.
45+
const fragment = AppState._TOGGLE_FRAGMENT_MAP[type] || type;
46+
const params = new URLSearchParams();
47+
if (isInactiveCheckedBool) {
48+
params.set("include_inactive", "true");
49+
}
50+
if (teamId) {
51+
params.set("team_id", teamId);
52+
}
53+
navigateAdmin(fragment, params);
3154
};
3255

3356
export const handleSubmitWithConfirmation = function (event, type) {

mcpgateway/admin_ui/formSubmitHandlers.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { HEADER_NAME_REGEX } from "./constants";
22
import { generateSchema } from "./formFieldHandlers";
3+
import { navigateAdmin } from "./navigation";
34
import {
45
safeParseJsonResponse,
56
validateInputName,
@@ -131,6 +132,8 @@ export const handleGatewayFormSubmit = async function (e) {
131132
searchParams.set("team_id", teamId);
132133
}
133134

135+
navigateAdmin("gateways", searchParams);
136+
134137
const queryString = searchParams.toString();
135138
const redirectUrl = `${window.ROOT_PATH}/admin${queryString ? `?${queryString}` : ""}#gateways`;
136139
window.location.href = redirectUrl;
@@ -213,6 +216,9 @@ export const handleResourceFormSubmit = async function (e) {
213216
if (teamId) {
214217
searchParams.set("team_id", teamId);
215218
}
219+
220+
navigateAdmin("gateways", searchParams);
221+
216222
const queryString = searchParams.toString();
217223
const redirectUrl = `${window.ROOT_PATH}/admin${queryString ? `?${queryString}` : ""}#resources`;
218224
window.location.href = redirectUrl;
@@ -280,6 +286,9 @@ export const handlePromptFormSubmit = async function (e) {
280286
if (teamId) {
281287
searchParams.set("team_id", teamId);
282288
}
289+
290+
navigateAdmin("gateways", searchParams);
291+
283292
const queryString = searchParams.toString();
284293
const redirectUrl = `${window.ROOT_PATH}/admin${queryString ? `?${queryString}` : ""}#prompts`;
285294
window.location.href = redirectUrl;
@@ -352,6 +361,9 @@ export const handleEditPromptFormSubmit = async function (e) {
352361
if (teamId) {
353362
searchParams.set("team_id", teamId);
354363
}
364+
365+
navigateAdmin("gateways", searchParams);
366+
355367
const queryString = searchParams.toString();
356368
const redirectUrl = `${window.ROOT_PATH}/admin${queryString ? `?${queryString}` : ""}#prompts`;
357369
window.location.href = redirectUrl;
@@ -462,6 +474,8 @@ export const handleServerFormSubmit = async function (e) {
462474
searchParams.set("team_id", teamId);
463475
}
464476

477+
navigateAdmin("gateways", searchParams);
478+
465479
const queryString = searchParams.toString();
466480
const redirectUrl = `${window.ROOT_PATH}/admin${queryString ? `?${queryString}` : ""}#catalog`;
467481
window.location.href = redirectUrl;
@@ -586,6 +600,9 @@ export const handleA2AFormSubmit = async function (e) {
586600
searchParams.set("team_id", teamId);
587601
}
588602

603+
navigateAdmin("gateways", searchParams);
604+
605+
589606
const queryString = searchParams.toString();
590607
const redirectUrl = `${window.ROOT_PATH}/admin${queryString ? `?${queryString}` : ""}#a2a-agents`;
591608
window.location.href = redirectUrl;
@@ -679,6 +696,9 @@ export const handleToolFormSubmit = async function (event) {
679696
if (teamId) {
680697
searchParams.set("team_id", teamId);
681698
}
699+
700+
navigateAdmin("gateways", searchParams);
701+
682702
const queryString = searchParams.toString();
683703
const redirectUrl = `${window.ROOT_PATH}/admin${queryString ? `?${queryString}` : ""}#tools`;
684704
window.location.href = redirectUrl;
@@ -745,6 +765,9 @@ export const handleEditToolFormSubmit = async function (event) {
745765
if (teamId) {
746766
searchParams.set("team_id", teamId);
747767
}
768+
769+
navigateAdmin("gateways", searchParams);
770+
748771
const queryString = searchParams.toString();
749772
const redirectUrl = `${window.ROOT_PATH}/admin${queryString ? `?${queryString}` : ""}#tools`;
750773
window.location.href = redirectUrl;
@@ -834,6 +857,9 @@ export const handleEditGatewayFormSubmit = async function (e) {
834857
if (teamId) {
835858
searchParams.set("team_id", teamId);
836859
}
860+
861+
navigateAdmin("gateways", searchParams);
862+
837863
const queryString = searchParams.toString();
838864
const redirectUrl = `${window.ROOT_PATH}/admin${queryString ? `?${queryString}` : ""}#gateways`;
839865
window.location.href = redirectUrl;
@@ -927,6 +953,9 @@ export const handleEditA2AAgentFormSubmit = async function (e) {
927953
if (teamId) {
928954
searchParams.set("team_id", teamId);
929955
}
956+
957+
navigateAdmin("gateways", searchParams);
958+
930959
const queryString = searchParams.toString();
931960
const redirectUrl = `${window.ROOT_PATH}/admin${queryString ? `?${queryString}` : ""}#a2a-agents`;
932961
window.location.href = redirectUrl;
@@ -1018,6 +1047,9 @@ export const handleEditServerFormSubmit = async function (e) {
10181047
if (teamId) {
10191048
searchParams.set("team_id", teamId);
10201049
}
1050+
1051+
navigateAdmin("gateways", searchParams);
1052+
10211053
const queryString = searchParams.toString();
10221054
const redirectUrl = `${window.ROOT_PATH}/admin${queryString ? `?${queryString}` : ""}#catalog`;
10231055
window.location.href = redirectUrl;
@@ -1091,6 +1123,9 @@ export const handleEditResFormSubmit = async function (e) {
10911123
if (teamId) {
10921124
searchParams.set("team_id", teamId);
10931125
}
1126+
1127+
navigateAdmin("gateways", searchParams);
1128+
10941129
const queryString = searchParams.toString();
10951130
const redirectUrl = `${window.ROOT_PATH}/admin${queryString ? `?${queryString}` : ""}#resources`;
10961131
window.location.href = redirectUrl;
@@ -1187,6 +1222,8 @@ export const handleGrpcServiceFormSubmit = async function (e) {
11871222
searchParams.set("team_id", teamId);
11881223
}
11891224

1225+
navigateAdmin("gateways", searchParams);
1226+
11901227
const queryString = searchParams.toString();
11911228
const redirectUrl = `${window.ROOT_PATH}/admin${queryString ? `?${queryString}` : ""}#grpc-services`;
11921229
window.location.href = redirectUrl;

0 commit comments

Comments
 (0)