admin UI: add anonymous user creation checkbox (#8773)

Add an "Anonymous" checkbox next to the username field in the Create User
modal. When checked, the username is set to "anonymous" and the credential
generation checkbox is disabled since anonymous users do not need keys.

The checkbox is only shown when no anonymous user exists yet. The
manage-access-keys button in the users table is hidden for the anonymous
user.
This commit is contained in:
Chris Lu
2026-03-25 21:24:10 -07:00
committed by GitHub
parent 94bfa2b340
commit 67a551fd62
4 changed files with 96 additions and 29 deletions

View File

@@ -48,10 +48,11 @@ type ObjectStoreUser struct {
}
type ObjectStoreUsersData struct {
Username string `json:"username"`
Users []ObjectStoreUser `json:"users"`
TotalUsers int `json:"total_users"`
LastUpdated time.Time `json:"last_updated"`
Username string `json:"username"`
Users []ObjectStoreUser `json:"users"`
TotalUsers int `json:"total_users"`
HasAnonymousUser bool `json:"has_anonymous_user"`
LastUpdated time.Time `json:"last_updated"`
}
// User management request structures

View File

@@ -311,10 +311,19 @@ func (h *UserHandlers) getObjectStoreUsersData(r *http.Request) dash.ObjectStore
}
}
hasAnonymous := false
for _, u := range users {
if u.Username == "anonymous" {
hasAnonymous = true
break
}
}
return dash.ObjectStoreUsersData{
Username: username,
Users: users,
TotalUsers: len(users),
LastUpdated: time.Now(),
Username: username,
Users: users,
TotalUsers: len(users),
HasAnonymousUser: hasAnonymous,
LastUpdated: time.Now(),
}
}

View File

@@ -141,10 +141,12 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
data-action="edit-user" data-username={ user.Username }>
<i class="fas fa-edit"></i>
</button>
<button type="button" class="btn btn-outline-secondary"
data-action="manage-access-keys" data-username={ user.Username }>
<i class="fas fa-key"></i>
</button>
if user.Username != "anonymous" {
<button type="button" class="btn btn-outline-secondary"
data-action="manage-access-keys" data-username={ user.Username }>
<i class="fas fa-key"></i>
</button>
}
<button type="button" class="btn btn-outline-danger"
data-action="delete-user" data-username={ user.Username }>
<i class="fas fa-trash"></i>
@@ -197,7 +199,17 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
<form id="createUserForm">
<div class="mb-3">
<label for="username" class="form-label">Username *</label>
<input type="text" class="form-control" id="username" name="username" required>
if !data.HasAnonymousUser {
<div class="input-group">
<input type="text" class="form-control" id="username" name="username" required>
<div class="input-group-text">
<input class="form-check-input mt-0 me-1" type="checkbox" id="anonymousCheck" onchange="toggleAnonymousUser()">
<label class="form-check-label small" for="anonymousCheck">Anonymous</label>
</div>
</div>
} else {
<input type="text" class="form-control" id="username" name="username" required>
}
</div>
<div class="mb-3">
<label for="email" class="form-label">Email</label>
@@ -669,6 +681,24 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
}
}
// Toggle anonymous user checkbox
function toggleAnonymousUser() {
const checkbox = document.getElementById('anonymousCheck');
const usernameInput = document.getElementById('username');
const generateKey = document.getElementById('generateKey');
if (checkbox.checked) {
usernameInput.value = 'anonymous';
usernameInput.readOnly = true;
generateKey.checked = false;
generateKey.disabled = true;
} else {
usernameInput.value = '';
usernameInput.readOnly = false;
generateKey.disabled = false;
generateKey.checked = true;
}
}
// Populate bucket selection dropdowns
function populateBucketSelections() {
const createSelect = document.getElementById('selectedBuckets');
@@ -1055,6 +1085,8 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
const modal = bootstrap.Modal.getInstance(document.getElementById('createUserModal'));
modal.hide();
form.reset();
document.getElementById('username').readOnly = false;
document.getElementById('generateKey').disabled = false;
setTimeout(() => window.location.reload(), 1000);
} else {
const error = await response.json();

File diff suppressed because one or more lines are too long