Files
seaweedFS/weed/admin/view/app/object_store_users_templ.go

206 lines
55 KiB
Go

// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.960
package app
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"fmt"
"github.com/seaweedfs/seaweedfs/weed/admin/dash"
)
func ObjectStoreUsers(data dash.ObjectStoreUsersData) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
return templ_7745c5c3_CtxErr
}
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
if !templ_7745c5c3_IsBuffer {
defer func() {
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
if templ_7745c5c3_Err == nil {
templ_7745c5c3_Err = templ_7745c5c3_BufErr
}
}()
}
ctx = templ.InitializeContext(ctx)
templ_7745c5c3_Var1 := templ.GetChildren(ctx)
if templ_7745c5c3_Var1 == nil {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<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\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.TotalUsers))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/object_store_users.templ`, Line: 38, Col: 71}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</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\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(data.Users)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/object_store_users.templ`, Line: 58, Col: 71}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</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\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.LastUpdated.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/object_store_users.templ`, Line: 78, Col: 69}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</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>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, user := range data.Users {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<tr><td><div class=\"d-flex align-items-center\"><i class=\"fas fa-user me-2 text-muted\"></i> <strong>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/object_store_users.templ`, Line: 127, Col: 74}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</strong></div></td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/object_store_users.templ`, Line: 130, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "</td><td><code class=\"text-muted\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(user.AccessKey)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/object_store_users.templ`, Line: 132, Col: 88}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "</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=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/object_store_users.templ`, Line: 137, Col: 121}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\"><i class=\"fas fa-info-circle\"></i></button> <button type=\"button\" class=\"btn btn-outline-primary\" data-action=\"edit-user\" data-username=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/object_store_users.templ`, Line: 141, Col: 113}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "\"><i class=\"fas fa-edit\"></i></button> <button type=\"button\" class=\"btn btn-outline-secondary\" data-action=\"manage-access-keys\" data-username=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/object_store_users.templ`, Line: 145, Col: 122}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\"><i class=\"fas fa-key\"></i></button> <button type=\"button\" class=\"btn btn-outline-danger\" data-action=\"delete-user\" data-username=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/object_store_users.templ`, Line: 149, Col: 115}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "\"><i class=\"fas fa-trash\"></i></button></div></td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.Users) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "<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>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</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: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(data.LastUpdated.Format("2006-01-02 15:04:05"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/object_store_users.templ`, Line: 180, Col: 81}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</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\"><label class=\"form-label\">Bucket Scope</label> <small class=\"form-text text-muted d-block mb-2\">Apply selected permissions to specific buckets or all buckets</small><div class=\"form-check mb-2\"><input class=\"form-check-input\" type=\"radio\" name=\"bucketScope\" id=\"allBuckets\" value=\"all\" checked onchange=\"toggleBucketList()\"> <label class=\"form-check-label\" for=\"allBuckets\">All Buckets</label></div><div class=\"form-check mb-2\"><input class=\"form-check-input\" type=\"radio\" name=\"bucketScope\" id=\"specificBuckets\" value=\"specific\" onchange=\"toggleBucketList()\"> <label class=\"form-check-label\" for=\"specificBuckets\">Specific Buckets</label></div><div id=\"bucketSelectionList\" class=\"mt-2\" style=\"display: none;\"><select multiple class=\"form-select\" id=\"selectedBuckets\" size=\"5\"><!-- Options loaded dynamically --></select> <small class=\"form-text text-muted\">Hold Ctrl/Cmd to select multiple buckets</small></div></div><div class=\"mb-3\"><label for=\"policies\" class=\"form-label\">Attached Policies</label> <select multiple class=\"form-control\" id=\"policies\" name=\"policies\" size=\"5\"><!-- Options loaded dynamically --></select> <small class=\"form-text text-muted\">Hold Ctrl/Cmd to select multiple policies</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><div class=\"mb-3\"><label class=\"form-label\">Bucket Scope</label> <small class=\"form-text text-muted d-block mb-2\">Apply selected permissions to specific buckets or all buckets</small><div class=\"form-check mb-2\"><input class=\"form-check-input\" type=\"radio\" name=\"editBucketScope\" id=\"editAllBuckets\" value=\"all\" checked onchange=\"toggleBucketList('edit')\"> <label class=\"form-check-label\" for=\"editAllBuckets\">All Buckets</label></div><div class=\"form-check mb-2\"><input class=\"form-check-input\" type=\"radio\" name=\"editBucketScope\" id=\"editSpecificBuckets\" value=\"specific\" onchange=\"toggleBucketList('edit')\"> <label class=\"form-check-label\" for=\"editSpecificBuckets\">Specific Buckets</label></div><div id=\"editBucketSelectionList\" class=\"mt-2\" style=\"display: none;\"><select multiple class=\"form-select\" id=\"editSelectedBuckets\" size=\"5\"><!-- Options loaded dynamically --></select> <small class=\"form-text text-muted\">Hold Ctrl/Cmd to select multiple buckets</small></div></div><div class=\"mb-3\"><label for=\"editPolicies\" class=\"form-label\">Attached Policies</label> <select multiple class=\"form-control\" id=\"editPolicies\" name=\"policies\" size=\"5\"><!-- Options loaded dynamically --></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>\n // Access key status constants\n const STATUS_ACTIVE = 'Active';\n const STATUS_INACTIVE = 'Inactive';\n\n document.addEventListener('DOMContentLoaded', function() {\n \n // Event delegation for user action buttons\n document.addEventListener('click', function(e) {\n const button = e.target.closest('[data-action]');\n if (!button) return;\n \n const action = button.getAttribute('data-action');\n const username = button.getAttribute('data-username');\n \n switch (action) {\n case 'show-user-details':\n showUserDetails(username);\n break;\n case 'edit-user':\n editUser(username);\n break;\n case 'manage-access-keys':\n manageAccessKeys(username);\n break;\n case 'delete-user':\n deleteUser(username);\n break;\n }\n });\n\n // Event delegation for delete access key buttons\n document.addEventListener('click', function(e) {\n const deleteBtn = e.target.closest('.delete-access-key-btn');\n if (deleteBtn) {\n const username = deleteBtn.getAttribute('data-username');\n const accessKey = deleteBtn.getAttribute('data-access-key');\n deleteAccessKey(username, accessKey);\n }\n });\n\n // Event delegation for access key status changes\n document.addEventListener('change', function(e) {\n const statusSelect = e.target.closest('.access-key-status-select');\n if (statusSelect) {\n const username = statusSelect.getAttribute('data-username');\n const accessKey = statusSelect.getAttribute('data-access-key');\n const newStatus = statusSelect.value;\n updateAccessKeyStatus(username, accessKey, newStatus);\n }\n });\n\n // Load policies for dropdowns\n loadPolicies();\n \n // Load buckets for bucket permissions\n loadBuckets();\n });\n\n // Global variable to store available buckets\n var availableBuckets = [];\n var bucketPermissionCounter = 0;\n\n // Load buckets\n async function loadBuckets() {\n try {\n const response = await fetch('/api/s3/buckets');\n if (response.ok) {\n const data = await response.json();\n availableBuckets = data.buckets || [];\n console.log('Loaded', availableBuckets.length, 'buckets');\n // Populate bucket selection dropdowns\n populateBucketSelections();\n } else {\n console.warn('Failed to load buckets');\n availableBuckets = [];\n }\n } catch (error) {\n console.error('Error loading buckets:', error);\n availableBuckets = [];\n }\n }\n\n // Load policies\n async function loadPolicies() {\n try {\n const response = await fetch('/api/object-store/policies');\n if (response.ok) {\n const data = await response.json();\n const policies = data.policies || [];\n \n const createSelect = document.getElementById('policies');\n const editSelect = document.getElementById('editPolicies');\n \n // Check if elements exist\n if (!createSelect || !editSelect) {\n console.warn('Policy select elements not found');\n return;\n }\n \n // Clear existing options\n createSelect.innerHTML = '';\n editSelect.innerHTML = '';\n \n if (policies && policies.length > 0) {\n policies.forEach(policy => {\n const option = document.createElement('option');\n option.value = policy.name;\n option.textContent = policy.name;\n createSelect.appendChild(option);\n editSelect.appendChild(option.cloneNode(true));\n });\n } else {\n const emptyOption = document.createElement('option');\n emptyOption.disabled = true;\n emptyOption.textContent = 'No policies available';\n createSelect.appendChild(emptyOption);\n editSelect.appendChild(emptyOption.cloneNode(true));\n }\n } else {\n const error = await response.json().catch(() => ({}));\n showErrorMessage('Failed to load policies: ' + (error.error || 'Unknown error'));\n }\n } catch (error) {\n console.error('Error loading policies:', error);\n showErrorMessage('Failed to load policies: ' + error.message);\n }\n }\n\n // Toggle bucket permission fields when Admin checkbox changes\n function toggleBucketPermissionFields(mode) {\n mode = mode || 'create';\n const adminCheckbox = document.getElementById(mode === 'edit' ? 'editBucketAdmin' : 'bucketAdmin');\n const permissionFields = document.getElementById(mode === 'edit' ? 'editBucketPermissionFields' : 'bucketPermissionFields');\n \n if (adminCheckbox && permissionFields) {\n permissionFields.style.display = adminCheckbox.checked ? 'none' : 'block';\n }\n }\n\n // Toggle bucket list visibility when bucket scope changes\n function toggleBucketList(mode) {\n mode = mode || 'create';\n const specificRadio = document.getElementById(mode === 'edit' ? 'editSpecificBuckets' : 'specificBuckets');\n const bucketList = document.getElementById(mode === 'edit' ? 'editBucketSelectionList' : 'bucketSelectionList');\n \n if (specificRadio && bucketList) {\n bucketList.style.display = specificRadio.checked ? 'block' : 'none';\n }\n }\n\n // Populate bucket selection dropdowns\n function populateBucketSelections() {\n const createSelect = document.getElementById('selectedBuckets');\n const editSelect = document.getElementById('editSelectedBuckets');\n \n [createSelect, editSelect].forEach(select => {\n if (select) {\n select.innerHTML = '';\n availableBuckets.forEach(bucket => {\n const option = document.createElement('option');\n option.value = bucket.name;\n option.textContent = bucket.name;\n select.appendChild(option);\n });\n }\n });\n }\n\n // Parse bucket permissions from actions array for new UI\n function parseBucketPermissions(actions) {\n const result = {\n isAdmin: false,\n permissions: [],\n applyToAll: false,\n specificBuckets: []\n };\n \n // Check if user has Admin permission\n if (actions.includes('Admin')) {\n result.isAdmin = true;\n return result;\n }\n \n // Separate bucket-scoped from global actions\n const bucketActions = [];\n const globalBucketPerms = [];\n \n actions.forEach(action => {\n if (action.includes(':')) {\n const parts = action.split(':');\n const perm = parts[0];\n const bucket = parts.slice(1).join(':').replace(/\\/\\*$/, '');\n bucketActions.push({ permission: perm, bucket: bucket });\n } else {\n globalBucketPerms.push(action);\n }\n });\n \n // If we have global bucket permissions (no colon), they apply to all buckets\n if (globalBucketPerms.length > 0) {\n result.permissions = globalBucketPerms;\n result.applyToAll = true;\n } else if (bucketActions.length > 0) {\n // Get unique permissions and buckets\n const perms = [...new Set(bucketActions.map(ba => ba.permission))];\n const buckets = [...new Set(bucketActions.map(ba => ba.bucket))];\n \n result.permissions = perms;\n result.applyToAll = false;\n result.specificBuckets = buckets;\n }\n \n return result;\n }\n\n // Build bucket permission action strings using original permissions dropdown\n /**\n * Builds bucket permission strings based on selected permissions and bucket scope.\n * @param {string} mode - The operation mode, either 'create' or 'edit'.\n * @returns {string[]|null} Array of permission strings (e.g., ['Read:bucket1']) or null if validation fails (specific scope selected but no buckets).\n */\n function buildBucketPermissions(mode) {\n mode = mode || 'create';\n const selectId = mode === 'edit' ? 'editActions' : 'actions';\n const permSelect = document.getElementById(selectId);\n \n if (!permSelect) return [];\n \n // Get selected permissions from the original multi-select\n const selectedPerms = Array.from(permSelect.selectedOptions).map(opt => opt.value);\n \n // If Admin is selected, return just Admin (it overrides everything)\n if (selectedPerms.includes('Admin')) {\n return ['Admin'];\n }\n \n if (selectedPerms.length === 0) {\n return [];\n }\n \n // Check if applying to all buckets or specific ones\n // Use querySelector to find the checked radio button by name group\n const scopeName = mode === 'edit' ? 'editBucketScope' : 'bucketScope';\n \n // Try multiple methods to find the checked radio\n let checkedRadio = document.querySelector(`input[name=\"${scopeName}\"]:checked`);\n \n // Fallback: check both radio buttons explicitly\n if (!checkedRadio) {\n const allBucketsId = mode === 'edit' ? 'editAllBuckets' : 'allBuckets';\n const specificBucketsId = mode === 'edit' ? 'editSpecificBuckets' : 'specificBuckets';\n \n const allBucketsRadio = document.getElementById(allBucketsId);\n const specificBucketsRadio = document.getElementById(specificBucketsId);\n \n if (specificBucketsRadio && specificBucketsRadio.checked) {\n checkedRadio = specificBucketsRadio;\n } else if (allBucketsRadio && allBucketsRadio.checked) {\n checkedRadio = allBucketsRadio;\n }\n }\n\n // Default to 'all' if nothing is checked (shouldn't happen) or if 'all' is checked\n const applyToAll = !checkedRadio || checkedRadio.value === 'all';\n\n if (applyToAll) {\n // Return global permissions (no bucket specification)\n return selectedPerms;\n } else {\n // Get selected specific buckets\n const bucketSelect = document.getElementById(mode === 'edit' ? 'editSelectedBuckets' : 'selectedBuckets');\n if (!bucketSelect) return null;\n \n const selectedBuckets = Array.from(bucketSelect.selectedOptions).map(opt => opt.value);\n \n // Return null to signal validation failure if no buckets selected\n if (selectedBuckets.length === 0) {\n return null;\n }\n \n // Build bucket-scoped permissions\n const actions = [];\n selectedPerms.forEach(perm => {\n selectedBuckets.forEach(bucket => {\n actions.push(perm + ':' + bucket);\n });\n });\n \n return actions;\n }\n }\n\n // Show user details modal\n async function showUserDetails(username) {\n try {\n const response = await fetch(`/api/users/${username}`);\n if (response.ok) {\n const user = await response.json();\n document.getElementById('userDetailsContent').innerHTML = createUserDetailsContent(user);\n const modal = new bootstrap.Modal(document.getElementById('userDetailsModal'));\n modal.show();\n } else {\n showErrorMessage('Failed to load user details');\n }\n } catch (error) {\n console.error('Error loading user details:', error);\n showErrorMessage('Failed to load user details');\n }\n }\n\n // Edit user function\n async function editUser(username) {\n try {\n const response = await fetch(`/api/users/${username}`);\n if (response.ok) {\n const user = await response.json();\n \n // Populate edit form\n document.getElementById('editUsername').value = username;\n document.getElementById('editEmail').value = user.email || '';\n \n // Set selected actions\n const actionsSelect = document.getElementById('editActions');\n Array.from(actionsSelect.options).forEach(option => {\n option.selected = user.actions && user.actions.includes(option.value);\n });\n\n // Set selected policies\n const policiesSelect = document.getElementById('editPolicies');\n if (policiesSelect) {\n Array.from(policiesSelect.options).forEach(option => {\n option.selected = user.policy_names && user.policy_names.includes(option.value);\n });\n }\n \n // Populate bucket permissions using original permissions dropdown\n if (user.actions && user.actions.length > 0) {\n const bucketPerms = parseBucketPermissions(user.actions);\n \n // Set permissions in the original multi-select\n const actionsSelect = document.getElementById('editActions');\n if (actionsSelect) {\n Array.from(actionsSelect.options).forEach(option => {\n if (bucketPerms.isAdmin && option.value === 'Admin') {\n option.selected = true;\n } else if (!bucketPerms.isAdmin && bucketPerms.permissions.includes(option.value)) {\n option.selected = true;\n }\n });\n }\n \n // Set bucket scope (all or specific)\n const allBucketsRadio = document.getElementById('editAllBuckets');\n const specificBucketsRadio = document.getElementById('editSpecificBuckets');\n \n if (!bucketPerms.isAdmin) {\n if (bucketPerms.applyToAll) {\n if (allBucketsRadio) allBucketsRadio.checked = true;\n } else if (bucketPerms.specificBuckets.length > 0) {\n if (specificBucketsRadio) specificBucketsRadio.checked = true;\n toggleBucketList('edit');\n \n // Select specific buckets\n const bucketSelect = document.getElementById('editSelectedBuckets');\n if (bucketSelect) {\n Array.from(bucketSelect.options).forEach(option => {\n option.selected = bucketPerms.specificBuckets.includes(option.value);\n });\n }\n }\n }\n }\n \n // Show modal\n const modal = new bootstrap.Modal(document.getElementById('editUserModal'));\n modal.show();\n } else {\n showErrorMessage('Failed to load user details');\n }\n } catch (error) {\n console.error('Error loading user:', error);\n showErrorMessage('Failed to load user details');\n }\n }\n\n // Manage access keys function\n async function manageAccessKeys(username) {\n try {\n const response = await fetch(`/api/users/${username}`);\n if (response.ok) {\n const user = await response.json();\n document.getElementById('accessKeysUsername').textContent = username;\n document.getElementById('accessKeysContent').innerHTML = createAccessKeysContent(user);\n const modal = new bootstrap.Modal(document.getElementById('accessKeysModal'));\n modal.show();\n } else {\n showErrorMessage('Failed to load access keys');\n }\n } catch (error) {\n console.error('Error loading access keys:', error);\n showErrorMessage('Failed to load access keys');\n }\n }\n\n // Delete user function\n async function deleteUser(username) {\n showDeleteConfirm(username, async function() {\n try {\n const response = await fetch(`/api/users/${username}`, {\n method: 'DELETE'\n });\n \n if (response.ok) {\n showSuccessMessage('User deleted successfully');\n setTimeout(() => window.location.reload(), 1000);\n } else {\n const error = await response.json();\n showErrorMessage('Failed to delete user: ' + (error.error || 'Unknown error'));\n }\n } catch (error) {\n console.error('Error deleting user:', error);\n showErrorMessage('Failed to delete user: ' + error.message);\n }\n }, 'Are you sure you want to delete this user? This action cannot be undone.');\n }\n\n // Handle create user form submission\n async function handleCreateUser() {\n const form = document.getElementById('createUserForm');\n const formData = new FormData(form);\n \n // Get permissions with bucket scope applied\n const allActions = buildBucketPermissions('create');\n \n const userData = {\n username: formData.get('username'),\n email: formData.get('email'),\n actions: allActions,\n policy_names: Array.from(document.getElementById('policies').selectedOptions).map(option => option.value),\n generate_key: document.getElementById('generateKey').checked\n };\n \n try {\n const response = await fetch('/api/users', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(userData)\n });\n \n if (response.ok) {\n const result = await response.json();\n showSuccessMessage('User created successfully');\n \n // Show the created access key if generated\n if (result.user && result.user.access_key) {\n showNewAccessKeyModal(result.user);\n }\n \n // Close modal and refresh page\n const modal = bootstrap.Modal.getInstance(document.getElementById('createUserModal'));\n modal.hide();\n form.reset();\n setTimeout(() => window.location.reload(), 1000);\n } else {\n const error = await response.json();\n showErrorMessage('Failed to create user: ' + (error.error || 'Unknown error'));\n }\n } catch (error) {\n console.error('Error creating user:', error);\n showErrorMessage('Failed to create user: ' + error.message);\n }\n }\n\n\n // Handle update user form submission\n async function handleUpdateUser() {\n const username = document.getElementById('editUsername').value;\n if (!username) {\n showErrorMessage('Username is required');\n return;\n }\n \n // Get permissions with bucket scope applied\n const allActions = buildBucketPermissions('edit');\n \n // Validate that permissions are not empty\n if (!allActions || allActions.length === 0) {\n showErrorMessage('At least one permission must be selected');\n return;\n }\n \n // Check for null (validation failure from buildBucketPermissionsNew)\n if (allActions === null) {\n showErrorMessage('Please select at least one bucket when using specific bucket permissions');\n return;\n }\n \n const userData = {\n email: document.getElementById('editEmail').value,\n actions: allActions,\n policy_names: Array.from(document.getElementById('editPolicies').selectedOptions).map(option => option.value)\n };\n \n try {\n const response = await fetch(`/api/users/${username}`, {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(userData)\n });\n \n if (response.ok) {\n showSuccessMessage('User updated successfully');\n \n // Close modal and refresh page\n const modal = bootstrap.Modal.getInstance(document.getElementById('editUserModal'));\n modal.hide();\n setTimeout(() => window.location.reload(), 1000);\n } else {\n const error = await response.json();\n showErrorMessage('Failed to update user: ' + (error.error || 'Unknown error'));\n }\n } catch (error) {\n console.error('Error updating user:', error);\n showErrorMessage('Failed to update user: ' + error.message);\n }\n }\n\n\n // Create user details content\n function createUserDetailsContent(user) {\n var detailsHtml = '<div class=\"row\">';\n detailsHtml += '<div class=\"col-md-6\">';\n detailsHtml += '<h6 class=\"text-muted\">Basic Information</h6>';\n detailsHtml += '<table class=\"table table-sm\">';\n detailsHtml += '<tr><td><strong>Username:</strong></td><td>' + escapeHtml(user.username) + '</td></tr>';\n detailsHtml += '<tr><td><strong>Email:</strong></td><td>' + escapeHtml(user.email || 'Not set') + '</td></tr>';\n detailsHtml += '</table>';\n detailsHtml += '</div>';\n detailsHtml += '<div class=\"col-md-6\">';\n detailsHtml += '<h6 class=\"text-muted\">Permissions</h6>';\n detailsHtml += '<div class=\"mb-3\">';\n if (user.actions && user.actions.length > 0) {\n detailsHtml += user.actions.map(function(action) {\n return '<span class=\"badge bg-info me-1\">' + escapeHtml(action) + '</span>';\n }).join('');\n } else {\n detailsHtml += '<span class=\"text-muted\">No permissions assigned</span>';\n }\n detailsHtml += '</div>';\n detailsHtml += '<h6 class=\"text-muted\">Attached Policy Names</h6>';\n detailsHtml += '<div class=\"mb-3\">';\n if (user.policy_names && user.policy_names.length > 0) {\n detailsHtml += user.policy_names.map(function(policy) {\n return '<span class=\"badge bg-secondary me-1\">' + escapeHtml(policy) + '</span>';\n }).join('');\n } else {\n detailsHtml += '<span class=\"text-muted\">No policies attached</span>';\n }\n detailsHtml += '</div>';\n detailsHtml += '<h6 class=\"text-muted\">Access Keys</h6>';\n if (user.access_keys && user.access_keys.length > 0) {\n detailsHtml += '<div class=\"mb-2\">';\n user.access_keys.forEach(function(key) {\n detailsHtml += '<div><code class=\"text-muted\">' + escapeHtml(key.access_key) + '</code></div>';\n });\n detailsHtml += '</div>';\n } else {\n detailsHtml += '<p class=\"text-muted\">No access keys</p>';\n }\n detailsHtml += '</div>';\n detailsHtml += '</div>';\n return detailsHtml;\n }\n\n // Create access keys content\n function createAccessKeysContent(user) {\n if (!user.access_keys || user.access_keys.length === 0) {\n return '<p class=\"text-muted\">No access keys available</p>';\n }\n \n var keysHtml = '<div class=\"table-responsive\">';\n keysHtml += '<table class=\"table table-sm\">';\n keysHtml += '<thead><tr><th>Access Key</th><th>Status</th><th>Actions</th></tr></thead>';\n keysHtml += '<tbody>';\n \n user.access_keys.forEach(function(key) {\n keysHtml += '<tr>';\n keysHtml += '<td><code>' + escapeHtml(key.access_key) + '</code></td>';\n keysHtml += '<td>';\n keysHtml += '<select class=\"form-select form-select-sm access-key-status-select\" data-username=\"' + escapeHtml(user.username) + '\" data-access-key=\"' + escapeHtml(key.access_key) + '\" style=\"width: 110px;\">';\n keysHtml += '<option value=\"' + STATUS_ACTIVE + '\" ' + (key.status === STATUS_ACTIVE || !key.status ? 'selected' : '') + '>' + STATUS_ACTIVE + '</option>';\n keysHtml += '<option value=\"' + STATUS_INACTIVE + '\" ' + (key.status === STATUS_INACTIVE ? 'selected' : '') + '>' + STATUS_INACTIVE + '</option>';\n keysHtml += '</select>';\n keysHtml += '</td>';\n keysHtml += '<td>';\n // Add \"View Secret\" button with data attributes\n keysHtml += '<button class=\"btn btn-outline-secondary btn-sm me-2 view-secret-btn\" data-access-key=\"' + escapeHtml(key.access_key) + '\" data-secret-key=\"' + escapeHtml(key.secret_key) + '\">';\n keysHtml += '<i class=\"fas fa-eye\"></i> View Secret';\n keysHtml += '</button>';\n // Delete button\n keysHtml += '<button class=\"btn btn-outline-danger btn-sm delete-access-key-btn\" data-username=\"' + escapeHtml(user.username) + '\" data-access-key=\"' + escapeHtml(key.access_key) + '\">';\n keysHtml += '<i class=\"fas fa-trash\"></i> Delete';\n keysHtml += '</button>';\n keysHtml += '</td>';\n keysHtml += '</tr>';\n });\n \n keysHtml += '</tbody>';\n keysHtml += '</table>';\n keysHtml += '</div>';\n \n // Add delegated event listener for view secret buttons\n setTimeout(() => {\n document.querySelectorAll('.view-secret-btn').forEach(btn => {\n btn.addEventListener('click', function() {\n const accessKey = this.getAttribute('data-access-key');\n const secretKey = this.getAttribute('data-secret-key');\n showSecretKey(accessKey, secretKey);\n });\n });\n }, 100);\n \n return keysHtml;\n }\n\n // Refresh access keys list content\n async function refreshAccessKeysList(username) {\n try {\n const response = await fetch(`/api/users/${username}`);\n if (response.ok) {\n const user = await response.json();\n document.getElementById('accessKeysContent').innerHTML = createAccessKeysContent(user);\n }\n } catch (error) {\n console.error('Error refreshing access keys:', error);\n }\n }\n\n // Update access key status\n async function updateAccessKeyStatus(username, accessKey, status) {\n try {\n const response = await fetch(`/api/users/${encodeURIComponent(username)}/access-keys/${encodeURIComponent(accessKey)}/status`, {\n method: 'PUT',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({ status: status })\n });\n\n if (response.ok) {\n showSuccessMessage('Access key status updated successfully');\n // Refresh access keys display without toggling modal\n refreshAccessKeysList(username);\n } else {\n const error = await response.json();\n showErrorMessage('Failed to update access key status: ' + (error.error || 'Unknown error'));\n refreshAccessKeysList(username);\n }\n } catch (error) {\n console.error('Error updating access key status:', error);\n showErrorMessage('Failed to update access key status: ' + error.message);\n refreshAccessKeysList(username);\n }\n }\n\n // Create new access key\n async function createAccessKey() {\n const username = document.getElementById('accessKeysUsername').textContent;\n \n try {\n const response = await fetch(`/api/users/${username}/access-keys`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({})\n });\n \n if (response.ok) {\n const result = await response.json();\n \n // Show the new access key details (IMPORTANT: secret key is only shown once!)\n if (result.access_key) {\n showNewAccessKeyModal(result.access_key);\n }\n \n showSuccessMessage('Access key created successfully');\n \n // Refresh access keys display\n refreshAccessKeysList(username);\n } else {\n const error = await response.json();\n showErrorMessage('Failed to create access key: ' + (error.error || 'Unknown error'));\n }\n } catch (error) {\n console.error('Error creating access key:', error);\n showErrorMessage('Failed to create access key: ' + error.message);\n }\n }\n\n // Delete access key\n async function deleteAccessKey(username, accessKey) {\n showDeleteConfirm(accessKey, async function() {\n try {\n const response = await fetch(`/api/users/${username}/access-keys/${accessKey}`, {\n method: 'DELETE'\n });\n \n if (response.ok) {\n showSuccessMessage('Access key deleted successfully');\n \n // Refresh access keys display\n refreshAccessKeysList(username);\n } else {\n const error = await response.json();\n showErrorMessage('Failed to delete access key: ' + (error.error || 'Unknown error'));\n }\n } catch (error) {\n console.error('Error deleting access key:', error);\n showErrorMessage('Failed to delete access key: ' + error.message);\n }\n }, 'Are you sure you want to delete this access key?');\n }\n\n\n // Utility functions\n function showSuccessMessage(message) {\n // Simple implementation - could be enhanced with toast notifications\n alert('Success: ' + message);\n }\n\n function showErrorMessage(message) {\n // Simple implementation - could be enhanced with toast notifications\n alert('Error: ' + message);\n }\n\n function escapeHtml(text) {\n if (!text) return '';\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n }\n </script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
// Helper functions for template
var _ = templruntime.GeneratedTemplate