fix(admin): use basePath for API fetches when urlPrefix is set (#9197)

* fix(admin): use basePath for API fetches when urlPrefix is set

* fix(admin): drop duplicate iam-utils script on Groups page

* fix(admin): route topics page fetches through basePath

The Topics page missed two fetch() calls that still used root-relative
URLs, so create-topic and view-details still broke when -urlPrefix was
set.

---------

Co-authored-by: Maksim Babkou <maksim.babkou@innovatrics.com>
Co-authored-by: Chris Lu <chris.lu@gmail.com>
This commit is contained in:
faspix
2026-04-23 20:55:07 +02:00
committed by GitHub
parent 749430dceb
commit 0fcd5173be
12 changed files with 148 additions and 148 deletions
+7 -8
View File
@@ -231,7 +231,6 @@ templ Groups(data dash.GroupsPageData) {
</div>
</div>
<script src="/static/js/iam-utils.js"></script>
<script>
// Groups page JavaScript
let currentGroupName = '';
@@ -243,7 +242,7 @@ templ Groups(data dash.GroupsPageData) {
return;
}
try {
const response = await fetch('/api/groups', {
const response = await fetch(basePath('/api/groups'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: name })
@@ -269,7 +268,7 @@ templ Groups(data dash.GroupsPageData) {
async function refreshGroupDetails(requestedName) {
try {
const response = await fetch('/api/groups/' + encodeURIComponent(requestedName));
const response = await fetch(basePath('/api/groups/' + encodeURIComponent(requestedName)));
if (!response.ok) throw new Error('Failed to fetch group');
if (requestedName !== currentGroupName) return; // stale response
const group = await response.json();
@@ -339,7 +338,7 @@ templ Groups(data dash.GroupsPageData) {
const username = document.getElementById('addMemberSelect').value;
if (!username) return;
try {
const response = await fetch('/api/groups/' + encodeURIComponent(currentGroupName) + '/members', {
const response = await fetch(basePath('/api/groups/' + encodeURIComponent(currentGroupName) + '/members'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: username })
@@ -358,7 +357,7 @@ templ Groups(data dash.GroupsPageData) {
async function removeMember(username) {
try {
const response = await fetch('/api/groups/' + encodeURIComponent(currentGroupName) + '/members/' + encodeURIComponent(username), {
const response = await fetch(basePath('/api/groups/' + encodeURIComponent(currentGroupName) + '/members/' + encodeURIComponent(username)), {
method: 'DELETE'
});
if (response.ok) {
@@ -377,7 +376,7 @@ templ Groups(data dash.GroupsPageData) {
const policyName = document.getElementById('attachPolicySelect').value;
if (!policyName) return;
try {
const response = await fetch('/api/groups/' + encodeURIComponent(currentGroupName) + '/policies', {
const response = await fetch(basePath('/api/groups/' + encodeURIComponent(currentGroupName) + '/policies'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ policy_name: policyName })
@@ -396,7 +395,7 @@ templ Groups(data dash.GroupsPageData) {
async function detachPolicy(policyName) {
try {
const response = await fetch('/api/groups/' + encodeURIComponent(currentGroupName) + '/policies/' + encodeURIComponent(policyName), {
const response = await fetch(basePath('/api/groups/' + encodeURIComponent(currentGroupName) + '/policies/' + encodeURIComponent(policyName)), {
method: 'DELETE'
});
if (response.ok) {
@@ -414,7 +413,7 @@ templ Groups(data dash.GroupsPageData) {
async function toggleGroupStatus() {
const enabled = document.getElementById('groupEnabledSwitch').checked;
try {
const response = await fetch('/api/groups/' + encodeURIComponent(currentGroupName) + '/status', {
const response = await fetch(basePath('/api/groups/' + encodeURIComponent(currentGroupName) + '/status'), {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ enabled: enabled })
File diff suppressed because one or more lines are too long
+18 -17
View File
@@ -504,6 +504,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
const STATUS_ACTIVE = 'Active';
const STATUS_INACTIVE = 'Inactive';
// Use basePath() from admin.js for -urlPrefix; it is loaded in the layout footer before DOMContentLoaded fires.
document.addEventListener('DOMContentLoaded', function() {
// Event delegation for user action buttons
@@ -591,7 +592,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
// Load buckets
async function loadBuckets() {
try {
const response = await fetch('/api/s3/buckets');
const response = await fetch(basePath('/api/s3/buckets'));
if (response.ok) {
const data = await response.json();
availableBuckets = (data.buckets || []).map(bucket => ({ name: bucket.name, type: 's3' }));
@@ -605,7 +606,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
availableBuckets = [];
}
try {
const response = await fetch('/api/s3tables/buckets');
const response = await fetch(basePath('/api/s3tables/buckets'));
if (response.ok) {
const data = await response.json();
const tableBuckets = (data.buckets || data.tableBuckets || []).map(bucket => ({ name: bucket.name, type: 's3tables' }));
@@ -623,7 +624,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
// Load policies
async function loadPolicies() {
try {
const response = await fetch('/api/object-store/policies');
const response = await fetch(basePath('/api/object-store/policies'));
if (response.ok) {
const data = await response.json();
const policies = data.policies || [];
@@ -910,7 +911,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
async function showUserDetails(username) {
try {
const encodedUsername = encodeURIComponent(username);
const response = await fetch(`/api/users/${encodedUsername}`);
const response = await fetch(basePath(`/api/users/${encodedUsername}`));
if (response.ok) {
const user = await response.json();
document.getElementById('userDetailsContent').innerHTML = createUserDetailsContent(user);
@@ -929,7 +930,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
async function editUser(username) {
try {
const encodedUsername = encodeURIComponent(username);
const response = await fetch(`/api/users/${encodedUsername}`);
const response = await fetch(basePath(`/api/users/${encodedUsername}`));
if (response.ok) {
const user = await response.json();
@@ -1008,7 +1009,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
async function manageAccessKeys(username) {
try {
const encodedUsername = encodeURIComponent(username);
const response = await fetch(`/api/users/${encodedUsername}`);
const response = await fetch(basePath(`/api/users/${encodedUsername}`));
if (response.ok) {
const user = await response.json();
document.getElementById('accessKeysUsername').textContent = username;
@@ -1029,7 +1030,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
showDeleteConfirm(username, async function() {
try {
const encodedUsername = encodeURIComponent(username);
const response = await fetch(`/api/users/${encodedUsername}`, {
const response = await fetch(basePath(`/api/users/${encodedUsername}`), {
method: 'DELETE'
});
@@ -1074,7 +1075,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
};
try {
const response = await fetch('/api/users', {
const response = await fetch(basePath('/api/users'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -1118,13 +1119,13 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
try {
// Fetch all groups
const groupsResp = await fetch('/api/groups');
const groupsResp = await fetch(basePath('/api/groups'));
if (!groupsResp.ok) return;
const groupsData = await groupsResp.json();
const allGroups = groupsData.groups || [];
// Fetch user details to get current groups
const userResp = await fetch(`/api/users/${encodeURIComponent(username)}`);
const userResp = await fetch(basePath(`/api/users/${encodeURIComponent(username)}`));
if (!userResp.ok) return;
const user = await userResp.json();
const userGroups = user.groups || [];
@@ -1172,7 +1173,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
if (!groupName) return;
try {
const response = await fetch(`/api/groups/${encodeURIComponent(groupName)}/members`, {
const response = await fetch(basePath(`/api/groups/${encodeURIComponent(groupName)}/members`), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: username })
@@ -1192,7 +1193,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
async function removeUserFromGroupInEdit(groupName) {
const username = document.getElementById('editUsername').value;
try {
const response = await fetch(`/api/groups/${encodeURIComponent(groupName)}/members/${encodeURIComponent(username)}`, {
const response = await fetch(basePath(`/api/groups/${encodeURIComponent(groupName)}/members/${encodeURIComponent(username)}`), {
method: 'DELETE'
});
if (response.ok) {
@@ -1237,7 +1238,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
try {
const encodedUsername = encodeURIComponent(username);
const response = await fetch(`/api/users/${encodedUsername}`, {
const response = await fetch(basePath(`/api/users/${encodedUsername}`), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
@@ -1374,7 +1375,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
async function refreshAccessKeysList(username) {
try {
const encodedUsername = encodeURIComponent(username);
const response = await fetch(`/api/users/${encodedUsername}`);
const response = await fetch(basePath(`/api/users/${encodedUsername}`));
if (response.ok) {
const user = await response.json();
document.getElementById('accessKeysContent').innerHTML = createAccessKeysContent(user);
@@ -1387,7 +1388,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
// Update access key status
async function updateAccessKeyStatus(username, accessKey, status) {
try {
const response = await fetch(`/api/users/${encodeURIComponent(username)}/access-keys/${encodeURIComponent(accessKey)}/status`, {
const response = await fetch(basePath(`/api/users/${encodeURIComponent(username)}/access-keys/${encodeURIComponent(accessKey)}/status`), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
@@ -1473,7 +1474,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
try {
const encodedUsername = encodeURIComponent(username);
const response = await fetch(`/api/users/${encodedUsername}/access-keys`, {
const response = await fetch(basePath(`/api/users/${encodedUsername}/access-keys`), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -1514,7 +1515,7 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
try {
const encodedUsername = encodeURIComponent(username);
const encodedAccessKey = encodeURIComponent(accessKey);
const response = await fetch(`/api/users/${encodedUsername}/access-keys/${encodedAccessKey}`, {
const response = await fetch(basePath(`/api/users/${encodedUsername}/access-keys/${encodedAccessKey}`), {
method: 'DELETE'
});
File diff suppressed because one or more lines are too long
+5 -5
View File
@@ -368,7 +368,7 @@ templ Policies(data dash.PoliciesData) {
document: policyDocument
};
fetch('/api/object-store/policies', {
fetch(basePath('/api/object-store/policies'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -408,7 +408,7 @@ templ Policies(data dash.PoliciesData) {
`;
// Fetch policy data
fetch('/api/object-store/policies/' + encodeURIComponent(policyName))
fetch(basePath('/api/object-store/policies/' + encodeURIComponent(policyName)))
.then(response => {
if (!response.ok) {
throw new Error('Policy not found');
@@ -494,7 +494,7 @@ templ Policies(data dash.PoliciesData) {
document.getElementById('editPolicyDocument').value = 'Loading...';
// Fetch policy data
fetch('/api/object-store/policies/' + encodeURIComponent(policyName))
fetch(basePath('/api/object-store/policies/' + encodeURIComponent(policyName)))
.then(response => {
if (!response.ok) {
throw new Error('Policy not found');
@@ -534,7 +534,7 @@ templ Policies(data dash.PoliciesData) {
document: policyDocument
};
fetch('/api/object-store/policies/' + encodeURIComponent(policyName), {
fetch(basePath('/api/object-store/policies/' + encodeURIComponent(policyName)), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
@@ -636,7 +636,7 @@ templ Policies(data dash.PoliciesData) {
function deletePolicy(policyName) {
showDeleteConfirm(policyName, function() {
fetch('/api/object-store/policies/' + encodeURIComponent(policyName), {
fetch(basePath('/api/object-store/policies/' + encodeURIComponent(policyName)), {
method: 'DELETE'
})
.then(response => response.json())
File diff suppressed because one or more lines are too long
+8 -8
View File
@@ -712,7 +712,7 @@ templ S3Buckets(data dash.S3BucketsData) {
// Only fetch if not already populated
if (ownerSelect.options.length <= 1) {
try {
const response = await fetch('/api/users');
const response = await fetch(basePath('/api/users'));
const data = await response.json();
const users = data.users || [];
@@ -756,7 +756,7 @@ templ S3Buckets(data dash.S3BucketsData) {
return;
}
fetch('/api/s3/buckets', {
fetch(basePath('/api/s3/buckets'), {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -846,7 +846,7 @@ templ S3Buckets(data dash.S3BucketsData) {
quota_enabled: enabled
};
fetch(`/api/s3/buckets/${bucketName}/quota`, {
fetch(basePath(`/api/s3/buckets/${bucketName}/quota`), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
@@ -897,7 +897,7 @@ templ S3Buckets(data dash.S3BucketsData) {
// Fetch users if not cached
try {
if (!cachedUsers) {
const response = await fetch('/api/users');
const response = await fetch(basePath('/api/users'));
const data = await response.json();
cachedUsers = data.users || [];
}
@@ -941,7 +941,7 @@ templ S3Buckets(data dash.S3BucketsData) {
const owner = document.getElementById('bucketOwnerSelect').value;
const data = { owner: owner };
fetch(`/api/s3/buckets/${bucketName}/owner`, {
fetch(basePath(`/api/s3/buckets/${bucketName}/owner`), {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
@@ -988,7 +988,7 @@ templ S3Buckets(data dash.S3BucketsData) {
detailsModalInstance.show();
// Fetch bucket details
fetch('/api/s3/buckets/' + bucketName)
fetch(basePath('/api/s3/buckets/' + bucketName))
.then(response => response.json())
.then(data => {
if (data.error) {
@@ -1017,7 +1017,7 @@ templ S3Buckets(data dash.S3BucketsData) {
const bucketName = document.getElementById('deleteBucketModal').dataset.bucketName;
if (!bucketName) return;
fetch(`/api/s3/buckets/${bucketName}`, {
fetch(basePath(`/api/s3/buckets/${bucketName}`), {
method: 'DELETE'
})
.then(response => response.json())
@@ -1172,7 +1172,7 @@ function displayBucketDetails(data) {
}
// Fetch all buckets from the API (not just the current page)
fetch('/api/s3/buckets')
fetch(basePath('/api/s3/buckets'))
.then(response => response.json())
.then(data => {
if (data.error) {
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -336,7 +336,7 @@ templ ServiceAccounts(data dash.ServiceAccountsData) {
async function showSADetails(id) {
try {
const encodedId = encodeURIComponent(id);
const response = await fetch(`/api/service-accounts/${encodedId}`);
const response = await fetch(basePath(`/api/service-accounts/${encodedId}`));
if (response.ok) {
const sa = await response.json();
document.getElementById('saDetailsContent').innerHTML = createSADetailsContent(sa);
@@ -355,7 +355,7 @@ templ ServiceAccounts(data dash.ServiceAccountsData) {
const newStatus = currentStatus === 'Active' ? 'Inactive' : 'Active';
try {
const encodedId = encodeURIComponent(id);
const response = await fetch(`/api/service-accounts/${encodedId}`, {
const response = await fetch(basePath(`/api/service-accounts/${encodedId}`), {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: newStatus })
@@ -378,7 +378,7 @@ templ ServiceAccounts(data dash.ServiceAccountsData) {
showDeleteConfirm(id, async function() {
try {
const encodedId = encodeURIComponent(id);
const response = await fetch(`/api/service-accounts/${encodedId}`, {
const response = await fetch(basePath(`/api/service-accounts/${encodedId}`), {
method: 'DELETE'
});
@@ -427,7 +427,7 @@ templ ServiceAccounts(data dash.ServiceAccountsData) {
}
try {
const response = await fetch('/api/service-accounts', {
const response = await fetch(basePath('/api/service-accounts'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(saData)
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -273,7 +273,7 @@ templ Topics(data dash.TopicsData) {
};
// Create the topic
fetch('/api/mq/topics/create', {
fetch(basePath('/api/mq/topics/create'), {
method: 'POST',
headers: {
'Content-Type': 'application/json'
@@ -354,7 +354,7 @@ templ Topics(data dash.TopicsData) {
var detailsRow = document.getElementById('details-' + topicName.replace(/\./g, '_'));
var contentDiv = detailsRow.querySelector('.topic-details-content');
fetch('/admin/topics/' + encodeURIComponent(topicName) + '/details')
fetch(basePath('/admin/topics/' + encodeURIComponent(topicName) + '/details'))
.then(response => response.json())
.then(data => {
var html = '<div class="row">';
File diff suppressed because one or more lines are too long