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 { type ObjectStoreUsersData struct {
Username string `json:"username"` Username string `json:"username"`
Users []ObjectStoreUser `json:"users"` Users []ObjectStoreUser `json:"users"`
TotalUsers int `json:"total_users"` TotalUsers int `json:"total_users"`
LastUpdated time.Time `json:"last_updated"` HasAnonymousUser bool `json:"has_anonymous_user"`
LastUpdated time.Time `json:"last_updated"`
} }
// User management request structures // 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{ return dash.ObjectStoreUsersData{
Username: username, Username: username,
Users: users, Users: users,
TotalUsers: len(users), TotalUsers: len(users),
LastUpdated: time.Now(), HasAnonymousUser: hasAnonymous,
LastUpdated: time.Now(),
} }
} }

View File

@@ -141,10 +141,12 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
data-action="edit-user" data-username={ user.Username }> data-action="edit-user" data-username={ user.Username }>
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</button> </button>
<button type="button" class="btn btn-outline-secondary" if user.Username != "anonymous" {
data-action="manage-access-keys" data-username={ user.Username }> <button type="button" class="btn btn-outline-secondary"
<i class="fas fa-key"></i> data-action="manage-access-keys" data-username={ user.Username }>
</button> <i class="fas fa-key"></i>
</button>
}
<button type="button" class="btn btn-outline-danger" <button type="button" class="btn btn-outline-danger"
data-action="delete-user" data-username={ user.Username }> data-action="delete-user" data-username={ user.Username }>
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>
@@ -197,7 +199,17 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
<form id="createUserForm"> <form id="createUserForm">
<div class="mb-3"> <div class="mb-3">
<label for="username" class="form-label">Username *</label> <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>
<div class="mb-3"> <div class="mb-3">
<label for="email" class="form-label">Email</label> <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 // Populate bucket selection dropdowns
function populateBucketSelections() { function populateBucketSelections() {
const createSelect = document.getElementById('selectedBuckets'); const createSelect = document.getElementById('selectedBuckets');
@@ -1055,6 +1085,8 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
const modal = bootstrap.Modal.getInstance(document.getElementById('createUserModal')); const modal = bootstrap.Modal.getInstance(document.getElementById('createUserModal'));
modal.hide(); modal.hide();
form.reset(); form.reset();
document.getElementById('username').readOnly = false;
document.getElementById('generateKey').disabled = false;
setTimeout(() => window.location.reload(), 1000); setTimeout(() => window.location.reload(), 1000);
} else { } else {
const error = await response.json(); const error = await response.json();

File diff suppressed because one or more lines are too long