| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691 |
- package app
- import (
- "fmt"
- "github.com/seaweedfs/seaweedfs/weed/admin/dash"
- )
- templ ObjectStoreUsers(data dash.ObjectStoreUsersData) {
- <div class="container-fluid">
- <!-- Page Header -->
- <div class="d-sm-flex align-items-center justify-content-between mb-4">
- <div>
- <h1 class="h3 mb-0 text-gray-800">
- <i class="fas fa-users me-2"></i>Object Store Users
- </h1>
- <p class="mb-0 text-muted">Manage S3 API users and their access credentials</p>
- </div>
- <div class="d-flex gap-2">
- <button type="button" class="btn btn-primary"
- data-bs-toggle="modal"
- data-bs-target="#createUserModal">
- <i class="fas fa-plus me-1"></i>Create User
- </button>
- </div>
- </div>
- <!-- Summary Cards -->
- <div class="row mb-4">
- <div class="col-xl-3 col-md-6 mb-4">
- <div class="card border-left-primary shadow h-100 py-2">
- <div class="card-body">
- <div class="row no-gutters align-items-center">
- <div class="col mr-2">
- <div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
- Total Users
- </div>
- <div class="h5 mb-0 font-weight-bold text-gray-800">
- {fmt.Sprintf("%d", data.TotalUsers)}
- </div>
- </div>
- <div class="col-auto">
- <i class="fas fa-users fa-2x text-gray-300"></i>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="col-xl-3 col-md-6 mb-4">
- <div class="card border-left-success shadow h-100 py-2">
- <div class="card-body">
- <div class="row no-gutters align-items-center">
- <div class="col mr-2">
- <div class="text-xs font-weight-bold text-success text-uppercase mb-1">
- Total Users
- </div>
- <div class="h5 mb-0 font-weight-bold text-gray-800">
- {fmt.Sprintf("%d", len(data.Users))}
- </div>
- </div>
- <div class="col-auto">
- <i class="fas fa-user-check fa-2x text-gray-300"></i>
- </div>
- </div>
- </div>
- </div>
- </div>
- <div class="col-xl-3 col-md-6 mb-4">
- <div class="card border-left-info shadow h-100 py-2">
- <div class="card-body">
- <div class="row no-gutters align-items-center">
- <div class="col mr-2">
- <div class="text-xs font-weight-bold text-info text-uppercase mb-1">
- Last Updated
- </div>
- <div class="h6 mb-0 font-weight-bold text-gray-800">
- {data.LastUpdated.Format("15:04")}
- </div>
- </div>
- <div class="col-auto">
- <i class="fas fa-clock fa-2x text-gray-300"></i>
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- Users Table -->
- <div class="row">
- <div class="col-12">
- <div class="card shadow mb-4">
- <div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
- <h6 class="m-0 font-weight-bold text-primary">
- <i class="fas fa-users me-2"></i>Object Store Users
- </h6>
- <div class="dropdown no-arrow">
- <a class="dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown">
- <i class="fas fa-ellipsis-v fa-sm fa-fw text-gray-400"></i>
- </a>
- <div class="dropdown-menu dropdown-menu-right shadow animated--fade-in">
- <div class="dropdown-header">Actions:</div>
- <a class="dropdown-item" href="#" onclick="exportUsers()">
- <i class="fas fa-download me-2"></i>Export List
- </a>
- </div>
- </div>
- </div>
- <div class="card-body">
- <div class="table-responsive">
- <table class="table table-hover" width="100%" cellspacing="0" id="usersTable">
- <thead>
- <tr>
- <th>Username</th>
- <th>Email</th>
- <th>Access Key</th>
- <th>Actions</th>
- </tr>
- </thead>
- <tbody>
- for _, user := range data.Users {
- <tr>
- <td>
- <div class="d-flex align-items-center">
- <i class="fas fa-user me-2 text-muted"></i>
- <strong>{user.Username}</strong>
- </div>
- </td>
- <td>{user.Email}</td>
- <td>
- <code class="text-muted">{user.AccessKey}</code>
- </td>
- <td>
- <div class="btn-group btn-group-sm" role="group">
- <button type="button" class="btn btn-outline-info"
- data-action="show-user-details" data-username={ user.Username }>
- <i class="fas fa-info-circle"></i>
- </button>
- <button type="button" class="btn btn-outline-primary"
- 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>
- <button type="button" class="btn btn-outline-danger"
- data-action="delete-user" data-username={ user.Username }>
- <i class="fas fa-trash"></i>
- </button>
- </div>
- </td>
- </tr>
- }
- if len(data.Users) == 0 {
- <tr>
- <td colspan="4" class="text-center text-muted py-4">
- <i class="fas fa-users fa-3x mb-3 text-muted"></i>
- <div>
- <h5>No users found</h5>
- <p>Create your first object store user to get started.</p>
- </div>
- </td>
- </tr>
- }
- </tbody>
- </table>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- Last Updated -->
- <div class="row">
- <div class="col-12">
- <small class="text-muted">
- <i class="fas fa-clock me-1"></i>
- Last updated: {data.LastUpdated.Format("2006-01-02 15:04:05")}
- </small>
- </div>
- </div>
- </div>
- <!-- Create User Modal -->
- <div class="modal fade" id="createUserModal" tabindex="-1" role="dialog">
- <div class="modal-dialog" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">
- <i class="fas fa-user-plus me-2"></i>Create New User
- </h5>
- <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
- </div>
- <div class="modal-body">
- <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>
- </div>
- <div class="mb-3">
- <label for="email" class="form-label">Email</label>
- <input type="email" class="form-control" id="email" name="email">
- </div>
- <div class="mb-3">
- <label for="actions" class="form-label">Permissions</label>
- <select multiple class="form-control" id="actions" name="actions" size="10">
- <option value="Admin">Admin (Full Access)</option>
- <option value="Read">Read</option>
- <option value="Write">Write</option>
- <option value="List">List</option>
- <option value="Tagging">Tagging</option>
- <optgroup label="Object Lock Permissions">
- <option value="BypassGovernanceRetention">Bypass Governance Retention</option>
- <option value="GetObjectRetention">Get Object Retention</option>
- <option value="PutObjectRetention">Put Object Retention</option>
- <option value="GetObjectLegalHold">Get Object Legal Hold</option>
- <option value="PutObjectLegalHold">Put Object Legal Hold</option>
- <option value="GetBucketObjectLockConfiguration">Get Bucket Object Lock Configuration</option>
- <option value="PutBucketObjectLockConfiguration">Put Bucket Object Lock Configuration</option>
- </optgroup>
- </select>
- <small class="form-text text-muted">Hold Ctrl/Cmd to select multiple permissions</small>
- </div>
- <div class="mb-3 form-check">
- <input type="checkbox" class="form-check-input" id="generateKey" name="generateKey" checked>
- <label class="form-check-label" for="generateKey">
- Generate access key automatically
- </label>
- </div>
- </form>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" onclick="handleCreateUser()">Create User</button>
- </div>
- </div>
- </div>
- </div>
- <!-- Edit User Modal -->
- <div class="modal fade" id="editUserModal" tabindex="-1" role="dialog">
- <div class="modal-dialog" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">
- <i class="fas fa-user-edit me-2"></i>Edit User
- </h5>
- <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
- </div>
- <div class="modal-body">
- <form id="editUserForm">
- <input type="hidden" id="editUsername" name="username">
- <div class="mb-3">
- <label for="editEmail" class="form-label">Email</label>
- <input type="email" class="form-control" id="editEmail" name="email">
- </div>
- <div class="mb-3">
- <label for="editActions" class="form-label">Permissions</label>
- <select multiple class="form-control" id="editActions" name="actions" size="10">
- <option value="Admin">Admin (Full Access)</option>
- <option value="Read">Read</option>
- <option value="Write">Write</option>
- <option value="List">List</option>
- <option value="Tagging">Tagging</option>
- <optgroup label="Object Lock Permissions">
- <option value="BypassGovernanceRetention">Bypass Governance Retention</option>
- <option value="GetObjectRetention">Get Object Retention</option>
- <option value="PutObjectRetention">Put Object Retention</option>
- <option value="GetObjectLegalHold">Get Object Legal Hold</option>
- <option value="PutObjectLegalHold">Put Object Legal Hold</option>
- <option value="GetBucketObjectLockConfiguration">Get Bucket Object Lock Configuration</option>
- <option value="PutBucketObjectLockConfiguration">Put Bucket Object Lock Configuration</option>
- </optgroup>
- </select>
- </div>
- </form>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
- <button type="button" class="btn btn-primary" onclick="handleUpdateUser()">Update User</button>
- </div>
- </div>
- </div>
- </div>
- <!-- User Details Modal -->
- <div class="modal fade" id="userDetailsModal" tabindex="-1" role="dialog">
- <div class="modal-dialog modal-lg" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">
- <i class="fas fa-user me-2"></i>User Details
- </h5>
- <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
- </div>
- <div class="modal-body" id="userDetailsContent">
- <!-- Content will be loaded dynamically -->
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
- </div>
- </div>
- </div>
- </div>
- <!-- Access Keys Management Modal -->
- <div class="modal fade" id="accessKeysModal" tabindex="-1" role="dialog">
- <div class="modal-dialog modal-lg" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">
- <i class="fas fa-key me-2"></i>Manage Access Keys
- </h5>
- <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
- </div>
- <div class="modal-body">
- <div class="d-flex justify-content-between align-items-center mb-3">
- <h6>Access Keys for <span id="accessKeysUsername"></span></h6>
- <button type="button" class="btn btn-primary btn-sm" onclick="createAccessKey()">
- <i class="fas fa-plus me-1"></i>Create New Key
- </button>
- </div>
- <div id="accessKeysContent">
- <!-- Content will be loaded dynamically -->
- </div>
- </div>
- <div class="modal-footer">
- <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
- </div>
- </div>
- </div>
- </div>
- <!-- JavaScript for user management -->
- <script>
- document.addEventListener('DOMContentLoaded', function() {
- // Event delegation for user action buttons
- document.addEventListener('click', function(e) {
- const button = e.target.closest('[data-action]');
- if (!button) return;
-
- const action = button.getAttribute('data-action');
- const username = button.getAttribute('data-username');
-
- switch (action) {
- case 'show-user-details':
- showUserDetails(username);
- break;
- case 'edit-user':
- editUser(username);
- break;
- case 'manage-access-keys':
- manageAccessKeys(username);
- break;
- case 'delete-user':
- deleteUser(username);
- break;
- }
- });
- });
- // Show user details modal
- async function showUserDetails(username) {
- try {
- const response = await fetch(`/api/users/${username}`);
- if (response.ok) {
- const user = await response.json();
- document.getElementById('userDetailsContent').innerHTML = createUserDetailsContent(user);
- const modal = new bootstrap.Modal(document.getElementById('userDetailsModal'));
- modal.show();
- } else {
- showErrorMessage('Failed to load user details');
- }
- } catch (error) {
- console.error('Error loading user details:', error);
- showErrorMessage('Failed to load user details');
- }
- }
- // Edit user function
- async function editUser(username) {
- try {
- const response = await fetch(`/api/users/${username}`);
- if (response.ok) {
- const user = await response.json();
-
- // Populate edit form
- document.getElementById('editUsername').value = username;
- document.getElementById('editEmail').value = user.email || '';
-
- // Set selected actions
- const actionsSelect = document.getElementById('editActions');
- Array.from(actionsSelect.options).forEach(option => {
- option.selected = user.actions && user.actions.includes(option.value);
- });
-
- // Show modal
- const modal = new bootstrap.Modal(document.getElementById('editUserModal'));
- modal.show();
- } else {
- showErrorMessage('Failed to load user details');
- }
- } catch (error) {
- console.error('Error loading user:', error);
- showErrorMessage('Failed to load user details');
- }
- }
- // Manage access keys function
- async function manageAccessKeys(username) {
- try {
- const response = await fetch(`/api/users/${username}`);
- if (response.ok) {
- const user = await response.json();
- document.getElementById('accessKeysUsername').textContent = username;
- document.getElementById('accessKeysContent').innerHTML = createAccessKeysContent(user);
- const modal = new bootstrap.Modal(document.getElementById('accessKeysModal'));
- modal.show();
- } else {
- showErrorMessage('Failed to load access keys');
- }
- } catch (error) {
- console.error('Error loading access keys:', error);
- showErrorMessage('Failed to load access keys');
- }
- }
- // Delete user function
- async function deleteUser(username) {
- if (confirm(`Are you sure you want to delete user "${username}"? This action cannot be undone.`)) {
- try {
- const response = await fetch(`/api/users/${username}`, {
- method: 'DELETE'
- });
-
- if (response.ok) {
- showSuccessMessage('User deleted successfully');
- setTimeout(() => window.location.reload(), 1000);
- } else {
- const error = await response.json();
- showErrorMessage('Failed to delete user: ' + (error.error || 'Unknown error'));
- }
- } catch (error) {
- console.error('Error deleting user:', error);
- showErrorMessage('Failed to delete user: ' + error.message);
- }
- }
- }
- // Handle create user form submission
- async function handleCreateUser() {
- const form = document.getElementById('createUserForm');
- const formData = new FormData(form);
-
- const userData = {
- username: formData.get('username'),
- email: formData.get('email'),
- actions: Array.from(document.getElementById('actions').selectedOptions).map(option => option.value),
- generate_key: document.getElementById('generateKey').checked
- };
-
- try {
- const response = await fetch('/api/users', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(userData)
- });
-
- if (response.ok) {
- const result = await response.json();
- showSuccessMessage('User created successfully');
-
- // Show the created access key if generated
- if (result.user && result.user.access_key) {
- showNewAccessKeyModal(result.user);
- }
-
- // Close modal and refresh page
- const modal = bootstrap.Modal.getInstance(document.getElementById('createUserModal'));
- modal.hide();
- form.reset();
- setTimeout(() => window.location.reload(), 1000);
- } else {
- const error = await response.json();
- showErrorMessage('Failed to create user: ' + (error.error || 'Unknown error'));
- }
- } catch (error) {
- console.error('Error creating user:', error);
- showErrorMessage('Failed to create user: ' + error.message);
- }
- }
- // Handle update user form submission
- async function handleUpdateUser() {
- const username = document.getElementById('editUsername').value;
- const formData = new FormData(document.getElementById('editUserForm'));
-
- const userData = {
- email: formData.get('email'),
- actions: Array.from(document.getElementById('editActions').selectedOptions).map(option => option.value)
- };
-
- try {
- const response = await fetch(`/api/users/${username}`, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(userData)
- });
-
- if (response.ok) {
- showSuccessMessage('User updated successfully');
-
- // Close modal and refresh page
- const modal = bootstrap.Modal.getInstance(document.getElementById('editUserModal'));
- modal.hide();
- setTimeout(() => window.location.reload(), 1000);
- } else {
- const error = await response.json();
- showErrorMessage('Failed to update user: ' + (error.error || 'Unknown error'));
- }
- } catch (error) {
- console.error('Error updating user:', error);
- showErrorMessage('Failed to update user: ' + error.message);
- }
- }
- // Create user details content
- function createUserDetailsContent(user) {
- var detailsHtml = '<div class="row">';
- detailsHtml += '<div class="col-md-6">';
- detailsHtml += '<h6 class="text-muted">Basic Information</h6>';
- detailsHtml += '<table class="table table-sm">';
- detailsHtml += '<tr><td><strong>Username:</strong></td><td>' + escapeHtml(user.username) + '</td></tr>';
- detailsHtml += '<tr><td><strong>Email:</strong></td><td>' + escapeHtml(user.email || 'Not set') + '</td></tr>';
- detailsHtml += '</table>';
- detailsHtml += '</div>';
- detailsHtml += '<div class="col-md-6">';
- detailsHtml += '<h6 class="text-muted">Permissions</h6>';
- detailsHtml += '<div class="mb-3">';
- if (user.actions && user.actions.length > 0) {
- detailsHtml += user.actions.map(function(action) {
- return '<span class="badge bg-info me-1">' + action + '</span>';
- }).join('');
- } else {
- detailsHtml += '<span class="text-muted">No permissions assigned</span>';
- }
- detailsHtml += '</div>';
- detailsHtml += '<h6 class="text-muted">Access Keys</h6>';
- if (user.access_keys && user.access_keys.length > 0) {
- detailsHtml += '<div class="mb-2">';
- user.access_keys.forEach(function(key) {
- detailsHtml += '<div><code class="text-muted">' + key.access_key + '</code></div>';
- });
- detailsHtml += '</div>';
- } else {
- detailsHtml += '<p class="text-muted">No access keys</p>';
- }
- detailsHtml += '</div>';
- detailsHtml += '</div>';
- return detailsHtml;
- }
- // Create access keys content
- function createAccessKeysContent(user) {
- if (!user.access_keys || user.access_keys.length === 0) {
- return '<p class="text-muted">No access keys available</p>';
- }
-
- var keysHtml = '<div class="table-responsive">';
- keysHtml += '<table class="table table-sm">';
- keysHtml += '<thead><tr><th>Access Key</th><th>Status</th><th>Actions</th></tr></thead>';
- keysHtml += '<tbody>';
-
- user.access_keys.forEach(function(key) {
- keysHtml += '<tr>';
- keysHtml += '<td><code>' + key.access_key + '</code></td>';
- keysHtml += '<td><span class="badge bg-success">Active</span></td>';
- keysHtml += '<td>';
- keysHtml += '<button class="btn btn-outline-danger btn-sm" onclick="deleteAccessKey(\'' + user.username + '\', \'' + key.access_key + '\')">';
- keysHtml += '<i class="fas fa-trash"></i> Delete';
- keysHtml += '</button>';
- keysHtml += '</td>';
- keysHtml += '</tr>';
- });
-
- keysHtml += '</tbody>';
- keysHtml += '</table>';
- keysHtml += '</div>';
- return keysHtml;
- }
- // Create new access key
- async function createAccessKey() {
- const username = document.getElementById('accessKeysUsername').textContent;
-
- try {
- const response = await fetch(`/api/users/${username}/access-keys`, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({})
- });
-
- if (response.ok) {
- const result = await response.json();
- showSuccessMessage('Access key created successfully');
-
- // Refresh access keys display
- const userResponse = await fetch(`/api/users/${username}`);
- if (userResponse.ok) {
- const user = await userResponse.json();
- document.getElementById('accessKeysContent').innerHTML = createAccessKeysContent(user);
- }
- } else {
- const error = await response.json();
- showErrorMessage('Failed to create access key: ' + (error.error || 'Unknown error'));
- }
- } catch (error) {
- console.error('Error creating access key:', error);
- showErrorMessage('Failed to create access key: ' + error.message);
- }
- }
- // Delete access key
- async function deleteAccessKey(username, accessKey) {
- if (confirm('Are you sure you want to delete this access key?')) {
- try {
- const response = await fetch(`/api/users/${username}/access-keys/${accessKey}`, {
- method: 'DELETE'
- });
-
- if (response.ok) {
- showSuccessMessage('Access key deleted successfully');
-
- // Refresh access keys display
- const userResponse = await fetch(`/api/users/${username}`);
- if (userResponse.ok) {
- const user = await userResponse.json();
- document.getElementById('accessKeysContent').innerHTML = createAccessKeysContent(user);
- }
- } else {
- const error = await response.json();
- showErrorMessage('Failed to delete access key: ' + (error.error || 'Unknown error'));
- }
- } catch (error) {
- console.error('Error deleting access key:', error);
- showErrorMessage('Failed to delete access key: ' + error.message);
- }
- }
- }
- // Show new access key modal (when user is created with generated key)
- function showNewAccessKeyModal(user) {
- // Create a simple alert for now - could be enhanced with a dedicated modal
- var message = 'New user created!\n\n';
- message += 'Username: ' + user.username + '\n';
- message += 'Access Key: ' + user.access_key + '\n';
- message += 'Secret Key: ' + user.secret_key + '\n\n';
- message += 'Please save these credentials securely.';
- alert(message);
- }
- // Utility functions
- function showSuccessMessage(message) {
- // Simple implementation - could be enhanced with toast notifications
- alert('Success: ' + message);
- }
- function showErrorMessage(message) {
- // Simple implementation - could be enhanced with toast notifications
- alert('Error: ' + message);
- }
- function escapeHtml(text) {
- if (!text) return '';
- const div = document.createElement('div');
- div.textContent = text;
- return div.innerHTML;
- }
- </script>
- }
- // Helper functions for template
-
|