// Code generated by templ - DO NOT EDIT.

// templ: version: v0.3.1020
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 S3Buckets(data dash.S3BucketsData) 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=\"d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom\"><h1 class=\"h2\"><i class=\"fas fa-cube me-2\"></i>Object Store Buckets</h1><div class=\"btn-toolbar mb-2 mb-md-0\"><div class=\"btn-group me-2\"><select class=\"form-select form-select-sm me-2\" id=\"pageSizeSelect\" onchange=\"changePageSize()\" style=\"width: auto;\"><option value=\"50\"")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		if data.PageSize == 50 {
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " selected=\"selected\"")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, ">50 per page</option> <option value=\"100\"")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		if data.PageSize == 100 {
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " selected=\"selected\"")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, ">100 per page</option> <option value=\"200\"")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		if data.PageSize == 200 {
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " selected=\"selected\"")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, ">200 per page</option> <option value=\"500\"")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		if data.PageSize == 500 {
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " selected=\"selected\"")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, ">500 per page</option></select> <button type=\"button\" class=\"btn btn-sm btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#createBucketModal\"><i class=\"fas fa-plus me-1\"></i>Create Bucket</button></div></div></div><div id=\"s3-buckets-content\"><!-- Summary Cards --><div class=\"row mb-4\"><div class=\"col-xl-4 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 Buckets</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.TotalBuckets))
		if templ_7745c5c3_Err != nil {
			return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 42, Col: 73}
		}
		_, 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, 10, "</div></div><div class=\"col-auto\"><i class=\"fas fa-cube fa-2x text-gray-300\"></i></div></div></div></div></div><div class=\"col-xl-4 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 Storage</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(formatBytes(data.TotalSize))
		if templ_7745c5c3_Err != nil {
			return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 62, Col: 64}
		}
		_, 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, 11, "</div></div><div class=\"col-auto\"><i class=\"fas fa-hdd fa-2x text-gray-300\"></i></div></div></div></div></div><div class=\"col-xl-4 col-md-6 mb-4\"><div class=\"card border-left-warning 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-warning 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:05"))
		if templ_7745c5c3_Err != nil {
			return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 83, Col: 72}
		}
		_, 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, 12, "</div></div><div class=\"col-auto\"><i class=\"fas fa-clock fa-2x text-gray-300\"></i></div></div></div></div></div></div><!-- Buckets 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-cube me-2\"></i>Object Store Buckets</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=\"exportBucketList()\"><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=\"bucketsTable\"><thead><tr><th><a href=\"#\" onclick=\"sortTable('name')\" class=\"text-decoration-none text-dark\">Name")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = getSortIcon("name", data.SortBy, data.SortOrder).Render(ctx, templ_7745c5c3_Buffer)
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</a></th><th><a href=\"#\" onclick=\"sortTable('owner')\" class=\"text-decoration-none text-dark\">Owner")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = getSortIcon("owner", data.SortBy, data.SortOrder).Render(ctx, templ_7745c5c3_Buffer)
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</a></th><th><a href=\"#\" onclick=\"sortTable('created')\" class=\"text-decoration-none text-dark\">Created")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = getSortIcon("created", data.SortBy, data.SortOrder).Render(ctx, templ_7745c5c3_Buffer)
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "</a></th><th><a href=\"#\" onclick=\"sortTable('logical_size')\" class=\"text-decoration-none text-dark\">Logical Size")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = getSortIcon("logical_size", data.SortBy, data.SortOrder).Render(ctx, templ_7745c5c3_Buffer)
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</a></th><th><a href=\"#\" onclick=\"sortTable('physical_size')\" class=\"text-decoration-none text-dark\">Physical Size")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = getSortIcon("physical_size", data.SortBy, data.SortOrder).Render(ctx, templ_7745c5c3_Buffer)
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "</a></th><th>Quota</th><th>Versioning</th><th>Object Lock</th><th>Lifecycle</th><th>Policy</th><th>Actions</th></tr></thead> <tbody>")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		for _, bucket := range data.Buckets {
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "<tr><td><a href=\"")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var5 templ.SafeURL
			templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fmt.Sprintf("/files?path=/buckets/%s", bucket.Name)))
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 162, Col: 124}
			}
			_, 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, 19, "\" class=\"text-decoration-none\"><i class=\"fas fa-cube me-2\"></i> ")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var6 string
			templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(bucket.Name)
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 165, Col: 64}
			}
			_, 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, 20, "</a> ")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			if bucket.ReadOnly {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<span class=\"badge bg-danger ms-2\" title=\"Bucket is read-only (quota enforcement or manual lock); writes are rejected\"><i class=\"fas fa-lock me-1\"></i>Read-only</span>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "</td><td>")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			if bucket.Owner != "" {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<span class=\"badge bg-info\"><i class=\"fas fa-user me-1\"></i>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				var templ_7745c5c3_Var7 string
				templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(bucket.Owner)
				if templ_7745c5c3_Err != nil {
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 176, Col: 101}
				}
				_, 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, 24, "</span>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			} else {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<span class=\"text-muted small\">No owner</span>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</td><td>")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var8 string
			templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(bucket.CreatedAt.Format("2006-01-02 15:04"))
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 182, Col: 92}
			}
			_, 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, 27, "</td><td><div>")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var9 string
			templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(bucket.LogicalSize))
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 184, Col: 85}
			}
			_, 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, 28, "</div>")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			if bucket.PhysicalSize > 0 && bucket.LogicalSize > 0 && bucket.PhysicalSize > bucket.LogicalSize {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<div class=\"small text-muted\">")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				var templ_7745c5c3_Var10 string
				templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1fx overhead", float64(bucket.PhysicalSize)/float64(bucket.LogicalSize)))
				if templ_7745c5c3_Err != nil {
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 187, Col: 144}
				}
				_, 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, 30, "</div>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</td><td>")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var11 string
			templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(bucket.PhysicalSize))
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 191, Col: 81}
			}
			_, 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, 32, "</td><td>")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			if bucket.Quota > 0 {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<div>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				var templ_7745c5c3_Var12 = []any{fmt.Sprintf("badge bg-%s", getQuotaStatusColor(bucket.LogicalSize, bucket.Quota, bucket.QuotaEnabled))}
				templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var12...)
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<span class=\"")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				var templ_7745c5c3_Var13 string
				templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.ResolveAttributeValue(templ.CSSClasses(templ_7745c5c3_Var12).String())
				if templ_7745c5c3_Err != nil {
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 1, Col: 0}
				}
				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13)
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\">")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				var templ_7745c5c3_Var14 string
				templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(bucket.Quota))
				if templ_7745c5c3_Err != nil {
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 196, Col: 86}
				}
				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14))
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</span> ")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				if bucket.QuotaEnabled {
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<div class=\"small text-muted\">")
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
					var templ_7745c5c3_Var15 string
					templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f%% used", float64(bucket.LogicalSize)/float64(bucket.Quota)*100))
					if templ_7745c5c3_Err != nil {
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 200, Col: 146}
					}
					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15))
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</div>")
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
				} else {
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<div class=\"small text-muted\">Disabled</div>")
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
				}
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</div>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			} else {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<span class=\"text-muted\">No quota</span>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</td><td>")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			if bucket.VersioningStatus == "Enabled" {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<span class=\"badge bg-success\"><i class=\"fas fa-check me-1\"></i>Enabled</span>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			} else if bucket.VersioningStatus == "Suspended" {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<span class=\"badge bg-warning\"><i class=\"fas fa-pause me-1\"></i>Suspended</span>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			} else {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<span class=\"text-muted\">Not configured</span>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</td><td>")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			if bucket.ObjectLockEnabled {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<div><span class=\"badge bg-warning\"><i class=\"fas fa-lock me-1\"></i>Enabled</span><div class=\"small text-muted\">")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				var templ_7745c5c3_Var16 string
				templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(bucket.ObjectLockMode)
				if templ_7745c5c3_Err != nil {
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 230, Col: 82}
				}
				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16))
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, " • ")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				var templ_7745c5c3_Var17 string
				templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d days", bucket.ObjectLockDuration))
				if templ_7745c5c3_Err != nil {
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 230, Col: 138}
				}
				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17))
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</div></div>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			} else {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<span class=\"text-muted\">Not configured</span>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "</td><td>")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			if bucket.LifecycleRuleCount > 0 {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<div><button type=\"button\" class=\"badge bg-primary border-0 lifecycle-btn\" data-bucket-name=\"")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				var templ_7745c5c3_Var18 string
				templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.ResolveAttributeValue(bucket.Name)
				if templ_7745c5c3_Err != nil {
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 241, Col: 93}
				}
				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var18)
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\" title=\"View Lifecycle Rules\">")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				if bucket.LifecycleRuleCount == 1 {
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "1 rule")
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
				} else {
					var templ_7745c5c3_Var19 string
					templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d rules", bucket.LifecycleRuleCount))
					if templ_7745c5c3_Err != nil {
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 246, Col: 115}
					}
					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
				}
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</button> ")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				if bucket.LifecycleEnabledCount < bucket.LifecycleRuleCount {
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<div class=\"small text-muted\">")
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
					var templ_7745c5c3_Var20 string
					templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d enabled", bucket.LifecycleEnabledCount))
					if templ_7745c5c3_Err != nil {
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 251, Col: 120}
					}
					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</div>")
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
				}
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</div>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			} else {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "<span class=\"text-muted\">Not configured</span>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</td><td>")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			if bucket.PolicyStatementCount > 0 {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<button type=\"button\" class=\"badge bg-primary border-0 policy-btn\" data-bucket-name=\"")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				var templ_7745c5c3_Var21 string
				templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.ResolveAttributeValue(bucket.Name)
				if templ_7745c5c3_Err != nil {
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 262, Col: 89}
				}
				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var21)
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "\" title=\"View Bucket Policy\">")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				if bucket.PolicyStatementCount == 1 {
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "1 statement")
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
				} else {
					var templ_7745c5c3_Var22 string
					templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d statements", bucket.PolicyStatementCount))
					if templ_7745c5c3_Err != nil {
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 267, Col: 118}
					}
					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
				}
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</button>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			} else {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "<span class=\"text-muted\">Not configured</span>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "</td><td><div class=\"btn-group btn-group-sm\" role=\"group\"><a href=\"")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var23 templ.SafeURL
			templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fmt.Sprintf("/files?path=/buckets/%s", bucket.Name)))
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 276, Col: 128}
			}
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "\" class=\"btn btn-outline-success btn-sm\" title=\"Browse Files\"><i class=\"fas fa-folder-open\"></i></a> <button type=\"button\" class=\"btn btn-outline-primary btn-sm view-details-btn\" data-bucket-name=\"")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var24 string
			templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.ResolveAttributeValue(bucket.Name)
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 283, Col: 89}
			}
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var24)
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "\" title=\"View Details\"><i class=\"fas fa-eye\"></i></button> <button type=\"button\" class=\"btn btn-outline-secondary btn-sm lifecycle-btn\" data-bucket-name=\"")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var25 string
			templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.ResolveAttributeValue(bucket.Name)
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 289, Col: 89}
			}
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var25)
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "\" title=\"Lifecycle Rules\"><i class=\"fas fa-recycle\"></i></button> <button type=\"button\" class=\"btn btn-outline-info btn-sm policy-btn\" data-bucket-name=\"")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var26 string
			templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.ResolveAttributeValue(bucket.Name)
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 295, Col: 89}
			}
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var26)
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "\" title=\"Bucket Policy\"><i class=\"fas fa-shield-alt\"></i></button> <button type=\"button\" class=\"btn btn-outline-info btn-sm owner-btn\" data-bucket-name=\"")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var27 string
			templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.ResolveAttributeValue(bucket.Name)
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 301, Col: 89}
			}
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var27)
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "\" data-current-owner=\"")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var28 string
			templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.ResolveAttributeValue(bucket.Owner)
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 302, Col: 92}
			}
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var28)
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "\" title=\"Manage Owner\"><i class=\"fas fa-user-edit\"></i></button> <button type=\"button\" class=\"btn btn-outline-warning btn-sm quota-btn\" data-bucket-name=\"")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var29 string
			templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.ResolveAttributeValue(bucket.Name)
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 308, Col: 89}
			}
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var29)
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "\" data-current-quota=\"")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var30 string
			templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%d", getQuotaInMB(bucket.Quota)))
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 309, Col: 125}
			}
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var30)
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "\" data-quota-enabled=\"")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var31 string
			templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%t", bucket.QuotaEnabled))
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 310, Col: 118}
			}
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var31)
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "\" title=\"Manage Quota\"><i class=\"fas fa-tachometer-alt\"></i></button> <button type=\"button\" class=\"btn btn-outline-danger btn-sm delete-bucket-btn\" data-bucket-name=\"")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var32 string
			templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.ResolveAttributeValue(bucket.Name)
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 316, Col: 89}
			}
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var32)
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "\" title=\"Delete Bucket\"><i class=\"fas fa-trash\"></i></button></div></td></tr>")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
		}
		if len(data.Buckets) == 0 {
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "<tr><td colspan=\"11\" class=\"text-center text-muted py-4\"><i class=\"fas fa-cube fa-3x mb-3 text-muted\"></i><div><h5>No Object Store buckets found</h5><p>Create your first bucket to get started with S3 storage.</p><button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#createBucketModal\"><i class=\"fas fa-plus me-1\"></i>Create Bucket</button></div></td></tr>")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "</tbody></table></div><!-- Pagination Controls -->")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		if data.TotalPages > 1 {
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "<div class=\"d-flex justify-content-between align-items-center mt-3\"><small class=\"text-muted\">Showing ")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var33 string
			templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", (data.CurrentPage-1)*data.PageSize+1))
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 348, Col: 101}
			}
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, " to ")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var34 string
			templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", minInt(data.CurrentPage*data.PageSize, data.TotalBuckets)))
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 348, Col: 185}
			}
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, " of ")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			var templ_7745c5c3_Var35 string
			templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.TotalBuckets))
			if templ_7745c5c3_Err != nil {
				return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 348, Col: 229}
			}
			_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, " buckets</small><nav aria-label=\"Buckets pagination\"><ul class=\"pagination pagination-sm mb-0\"><!-- Previous Button -->")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			if data.CurrentPage > 1 {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "<li class=\"page-item\"><a class=\"page-link pagination-link\" href=\"#\" data-page=\"")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				var templ_7745c5c3_Var36 string
				templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%d", data.CurrentPage-1))
				if templ_7745c5c3_Err != nil {
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 355, Col: 142}
				}
				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var36)
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "\"><i class=\"fas fa-chevron-left\"></i></a></li>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			} else {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "<li class=\"page-item disabled\"><span class=\"page-link\"><i class=\"fas fa-chevron-left\"></i></span></li>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<!-- Page Numbers -->")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			for i := maxInt(1, data.CurrentPage-2); i <= minInt(data.TotalPages, data.CurrentPage+2); i++ {
				if i == data.CurrentPage {
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "<li class=\"page-item active\"><span class=\"page-link\">")
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
					var templ_7745c5c3_Var37 string
					templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i))
					if templ_7745c5c3_Err != nil {
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 371, Col: 97}
					}
					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "</span></li>")
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
				} else {
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "<li class=\"page-item\"><a class=\"page-link pagination-link\" href=\"#\" data-page=\"")
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
					var templ_7745c5c3_Var38 string
					templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%d", i))
					if templ_7745c5c3_Err != nil {
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 375, Col: 129}
					}
					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var38)
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "\">")
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
					var templ_7745c5c3_Var39 string
					templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i))
					if templ_7745c5c3_Err != nil {
						return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 375, Col: 152}
					}
					_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39))
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
					templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "</a></li>")
					if templ_7745c5c3_Err != nil {
						return templ_7745c5c3_Err
					}
				}
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "<!-- Next Button -->")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
			if data.CurrentPage < data.TotalPages {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "<li class=\"page-item\"><a class=\"page-link pagination-link\" href=\"#\" data-page=\"")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				var templ_7745c5c3_Var40 string
				templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.ResolveAttributeValue(fmt.Sprintf("%d", data.CurrentPage+1))
				if templ_7745c5c3_Err != nil {
					return templ.Error{Err: templ_7745c5c3_Err, FileName: `weed/admin/view/app/s3_buckets.templ`, Line: 383, Col: 142}
				}
				_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var40)
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "\"><i class=\"fas fa-chevron-right\"></i></a></li>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			} else {
				templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "<li class=\"page-item disabled\"><span class=\"page-link\"><i class=\"fas fa-chevron-right\"></i></span></li>")
				if templ_7745c5c3_Err != nil {
					return templ_7745c5c3_Err
				}
			}
			templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "</ul></nav></div>")
			if templ_7745c5c3_Err != nil {
				return templ_7745c5c3_Err
			}
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "</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_Var41 string
		templ_7745c5c3_Var41, 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: `weed/admin/view/app/s3_buckets.templ`, Line: 408, Col: 81}
		}
		_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41))
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "</small></div></div></div><!-- Create Bucket Modal --><div class=\"modal fade\" id=\"createBucketModal\" tabindex=\"-1\" aria-labelledby=\"createBucketModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"createBucketModalLabel\"><i class=\"fas fa-plus me-2\"></i>Create New S3 Bucket</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><form id=\"createBucketForm\"><div class=\"modal-body\"><div class=\"mb-3\"><label for=\"bucketName\" class=\"form-label\">Bucket Name</label> <input type=\"text\" class=\"form-control\" id=\"bucketName\" name=\"name\" placeholder=\"my-bucket-name\" required pattern=\"[a-z0-9.-]+\" title=\"Bucket name must contain only lowercase letters, numbers, dots, and hyphens\"><div class=\"form-text\">Bucket names must be between 3 and 63 characters, contain only lowercase letters, numbers, dots, and hyphens.</div></div><div class=\"mb-3\"><label for=\"bucketOwner\" class=\"form-label\">Owner (Optional)</label> <select class=\"form-select\" id=\"bucketOwner\" name=\"owner\"><option value=\"\">No owner (admin-only access)</option><!-- Options will be populated dynamically when modal opens --></select><div class=\"form-text\">The S3 identity that owns this bucket. Non-admin users can only access buckets they own.</div></div><div class=\"mb-3\"><div class=\"form-check\"><input class=\"form-check-input\" type=\"checkbox\" id=\"enableQuota\" name=\"quota_enabled\"> <label class=\"form-check-label\" for=\"enableQuota\">Enable Storage Quota</label></div></div><div class=\"mb-3\" id=\"quotaSettings\" style=\"display: none;\"><div class=\"row\"><div class=\"col-md-8\"><label for=\"quotaSize\" class=\"form-label\">Quota Size</label> <input type=\"number\" class=\"form-control\" id=\"quotaSize\" name=\"quota_size\" placeholder=\"1024\" min=\"1\" step=\"1\"></div><div class=\"col-md-4\"><label for=\"quotaUnit\" class=\"form-label\">Unit</label> <select class=\"form-select\" id=\"quotaUnit\" name=\"quota_unit\"><option value=\"MB\" selected>MB</option> <option value=\"GB\">GB</option> <option value=\"TB\">TB</option></select></div></div><div class=\"form-text\">Set the maximum storage size for this bucket.</div></div><div class=\"mb-3\"><div class=\"form-check\"><input class=\"form-check-input\" type=\"checkbox\" id=\"enableVersioning\" name=\"versioning_enabled\"> <label class=\"form-check-label\" for=\"enableVersioning\">Enable Object Versioning</label></div><div class=\"form-text\">Keep multiple versions of objects in this bucket.</div></div><div class=\"mb-3\"><div class=\"form-check\"><input class=\"form-check-input\" type=\"checkbox\" id=\"enableObjectLock\" name=\"object_lock_enabled\"> <label class=\"form-check-label\" for=\"enableObjectLock\">Enable Object Lock</label></div><div class=\"form-text\">Prevent objects from being deleted or overwritten for a specified period. Automatically enables versioning.</div></div><div class=\"mb-3\" id=\"objectLockSettings\" style=\"display: none;\"><div class=\"row\"><div class=\"col-md-6\"><label for=\"objectLockMode\" class=\"form-label\">Object Lock Mode</label> <select class=\"form-select\" id=\"objectLockMode\" name=\"object_lock_mode\"><option value=\"GOVERNANCE\" selected>Governance</option> <option value=\"COMPLIANCE\">Compliance</option></select><div class=\"form-text\">Governance allows override with special permissions, Compliance is immutable.</div></div><div class=\"col-md-6\"><div class=\"form-check mb-3\"><input class=\"form-check-input\" type=\"checkbox\" id=\"setDefaultRetention\" name=\"set_default_retention\"> <label class=\"form-check-label\" for=\"setDefaultRetention\">Set Default Retention</label><div class=\"form-text\">Apply default retention to all new objects in this bucket.</div></div><div id=\"defaultRetentionSettings\" style=\"display: none;\"><label for=\"objectLockDuration\" class=\"form-label\">Default Retention (days)</label> <input type=\"number\" class=\"form-control\" id=\"objectLockDuration\" name=\"object_lock_duration\" placeholder=\"30\" min=\"1\" max=\"36500\" step=\"1\"><div class=\"form-text\">Default retention period for new objects (1-36500 days).</div></div></div></div></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-primary\"><i class=\"fas fa-plus me-1\"></i>Create Bucket</button></div></form></div></div></div><!-- Delete Confirmation Modal --><div class=\"modal fade\" id=\"deleteBucketModal\" tabindex=\"-1\" aria-labelledby=\"deleteBucketModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"deleteBucketModalLabel\"><i class=\"fas fa-exclamation-triangle me-2 text-warning\"></i>Delete Bucket</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><div class=\"modal-body\"><p>Are you sure you want to delete the bucket <strong id=\"deleteBucketName\"></strong>?</p><div class=\"alert alert-warning\"><i class=\"fas fa-exclamation-triangle me-2\"></i> <strong>Warning:</strong> This action cannot be undone. All objects in the bucket will be permanently deleted.</div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"button\" class=\"btn btn-danger\" onclick=\"deleteBucket()\"><i class=\"fas fa-trash me-1\"></i>Delete Bucket</button></div></div></div></div><!-- Manage Quota Modal --><div class=\"modal fade\" id=\"manageQuotaModal\" tabindex=\"-1\" aria-labelledby=\"manageQuotaModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"manageQuotaModalLabel\"><i class=\"fas fa-tachometer-alt me-2\"></i>Manage Bucket Quota</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><form id=\"quotaForm\"><div class=\"modal-body\"><div class=\"mb-3\"><label class=\"form-label\">Bucket Name</label> <input type=\"text\" class=\"form-control\" id=\"quotaBucketName\" readonly></div><div class=\"mb-3\"><div class=\"form-check\"><input class=\"form-check-input\" type=\"checkbox\" id=\"quotaEnabled\" name=\"quota_enabled\"> <label class=\"form-check-label\" for=\"quotaEnabled\">Enable Storage Quota</label></div></div><div class=\"mb-3\" id=\"quotaSizeSettings\"><div class=\"row\"><div class=\"col-md-8\"><label for=\"quotaSizeMB\" class=\"form-label\">Quota Size</label> <input type=\"number\" class=\"form-control\" id=\"quotaSizeMB\" name=\"quota_size\" placeholder=\"1024\" min=\"0\" step=\"1\"></div><div class=\"col-md-4\"><label for=\"quotaUnitMB\" class=\"form-label\">Unit</label> <select class=\"form-select\" id=\"quotaUnitMB\" name=\"quota_unit\"><option value=\"MB\" selected>MB</option> <option value=\"GB\">GB</option> <option value=\"TB\">TB</option></select></div></div><div class=\"form-text\">Set the maximum storage size for this bucket. Set to 0 to remove quota.</div></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-warning\"><i class=\"fas fa-save me-1\"></i>Update Quota</button></div></form></div></div></div><!-- Bucket Details Modal --><div class=\"modal fade\" id=\"bucketDetailsModal\" tabindex=\"-1\" aria-labelledby=\"bucketDetailsModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog modal-lg\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"bucketDetailsModalLabel\"><i class=\"fas fa-cube me-2\"></i>Bucket Details</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><div class=\"modal-body\"><div id=\"bucketDetailsContent\"><div class=\"text-center py-4\"><div class=\"spinner-border text-primary\" role=\"status\"><span class=\"visually-hidden\">Loading...</span></div><div class=\"mt-2\">Loading bucket details...</div></div></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button></div></div></div></div><!-- Bucket Lifecycle Modal --><div class=\"modal fade\" id=\"bucketLifecycleModal\" tabindex=\"-1\" aria-labelledby=\"bucketLifecycleModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog modal-xl\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"bucketLifecycleModalLabel\"><i class=\"fas fa-recycle me-2\"></i>Lifecycle</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><div class=\"modal-body\"><div id=\"bucketLifecycleContent\"></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-outline-danger me-auto\" id=\"lifecycleDeleteAllBtn\"><i class=\"fas fa-trash me-1\"></i>Delete all rules</button> <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"button\" class=\"btn btn-primary\" id=\"lifecycleSaveAllBtn\"><i class=\"fas fa-save me-1\"></i>Save</button></div></div></div></div>")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = PolicyDatalists().Render(ctx, templ_7745c5c3_Buffer)
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "<!-- Bucket Policy Modal --><div class=\"modal fade\" id=\"bucketPolicyModal\" tabindex=\"-1\" aria-labelledby=\"bucketPolicyModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog modal-xl\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"bucketPolicyModalLabel\"><i class=\"fas fa-file-shield me-2\"></i>Bucket Policy</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><div class=\"modal-body\"><div id=\"bucketPolicyLoading\" class=\"text-center py-4\"><div class=\"spinner-border text-primary\" role=\"status\"><span class=\"visually-hidden\">Loading...</span></div><div class=\"mt-2\">Loading bucket policy...</div></div><div id=\"bucketPolicyError\" class=\"alert alert-danger\" style=\"display: none;\"></div><div id=\"bucketPolicyEditorWrapper\" style=\"display: none;\"><p class=\"text-muted small\">Every statement must specify a Principal, and every Resource must refer to this bucket. Leave the document empty and click Delete to remove the policy.</p><ul class=\"nav nav-tabs\" role=\"tablist\"><li class=\"nav-item\" role=\"presentation\"><button class=\"nav-link active\" id=\"bucketPolicyEditorTabBtn\" type=\"button\" data-bs-toggle=\"tab\" data-bs-target=\"#bucketPolicyEditorTab\" role=\"tab\" aria-controls=\"bucketPolicyEditorTab\" aria-selected=\"true\">Editor</button></li><li class=\"nav-item\" role=\"presentation\"><button class=\"nav-link\" id=\"bucketPolicyJsonTabBtn\" type=\"button\" data-bs-toggle=\"tab\" data-bs-target=\"#bucketPolicyJsonTab\" role=\"tab\" aria-controls=\"bucketPolicyJsonTab\" aria-selected=\"false\">JSON</button></li></ul><div class=\"tab-content border border-top-0 rounded-bottom p-3 mb-3\"><div class=\"tab-pane fade show active\" id=\"bucketPolicyEditorTab\" role=\"tabpanel\" aria-labelledby=\"bucketPolicyEditorTabBtn\"><div id=\"bucketPolicyEditorBody\"></div><button type=\"button\" class=\"btn btn-sm btn-outline-secondary\" id=\"bucketPolicyAddStatementBtn\"><i class=\"fas fa-plus me-1\"></i>Add statement</button></div><div class=\"tab-pane fade\" id=\"bucketPolicyJsonTab\" role=\"tabpanel\" aria-labelledby=\"bucketPolicyJsonTabBtn\"><textarea class=\"form-control\" id=\"bucketPolicyDocument\" rows=\"15\" style=\"font-family: monospace;\" spellcheck=\"false\" placeholder='{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":\"s3:GetObject\",\"Resource\":\"arn:aws:s3:::bucket-name/*\"}]}'></textarea><div class=\"form-text\">Enter the bucket policy document as JSON</div></div></div><div class=\"mb-3\"><button type=\"button\" class=\"btn btn-outline-info btn-sm\" id=\"bucketPolicyInsertSampleBtn\"><i class=\"fas fa-file-alt me-1\"></i>Use Sample Policy</button></div></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-outline-danger me-auto\" id=\"bucketPolicyDeleteBtn\"><i class=\"fas fa-trash me-1\"></i>Delete</button> <button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"button\" class=\"btn btn-primary\" id=\"bucketPolicySaveBtn\"><i class=\"fas fa-save me-1\"></i>Save</button></div></div></div></div><!-- Manage Owner Modal --><div class=\"modal fade\" id=\"manageOwnerModal\" tabindex=\"-1\" aria-labelledby=\"manageOwnerModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"manageOwnerModalLabel\"><i class=\"fas fa-user-edit me-2\"></i>Manage Bucket Owner</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><form id=\"ownerForm\"><div class=\"modal-body\"><div class=\"mb-3\"><label class=\"form-label\">Bucket Name</label> <input type=\"text\" class=\"form-control\" id=\"ownerBucketName\" readonly></div><div class=\"mb-3\"><label for=\"bucketOwnerSelect\" class=\"form-label\">Owner</label> <select class=\"form-select\" id=\"bucketOwnerSelect\" name=\"owner\"><option value=\"\">No owner (admin-only access)</option><!-- Options will be populated dynamically --></select><div class=\"form-text\">Select the S3 identity that owns this bucket. Non-admin users can only access buckets they own.</div></div><div id=\"ownerLoadingSpinner\" class=\"text-center py-2\" style=\"display: none;\"><div class=\"spinner-border spinner-border-sm text-primary\" role=\"status\"><span class=\"visually-hidden\">Loading users...</span></div><span class=\"ms-2\">Loading users...</span></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"submit\" class=\"btn btn-info\"><i class=\"fas fa-save me-1\"></i>Update Owner</button></div></form></div></div></div><!-- JavaScript for bucket management --><script>\n    // Global state (shared between DOMContentLoaded handlers and global functions)\n    let deleteModalInstance = null;\n    let quotaModalInstance = null;\n    let ownerModalInstance = null;\n    let detailsModalInstance = null;\n    let lifecycleModalInstance = null;\n    let lifecycleRequestSeq = 0;\n    let policyModalInstance = null;\n    let policyRequestSeq = 0;\n    let policyEditorBucket = null;\n    // True only once the bucket-policy GET for the currently open bucket has\n    // actually completed successfully. The Save button lives outside the\n    // (initially hidden) editor wrapper, so it stays clickable while a load\n    // is in flight or has failed; without this guard, Save could commit\n    // whatever the editor happened to hold - a stale previous bucket's\n    // statements, or a freshly-initialized empty state - onto\n    // policyEditorBucket before its own policy ever loaded.\n    let bucketPolicyLoaded = false;\n    // True while a Save (PUT) or Delete (DELETE) request for the bucket\n    // policy is in flight, so a double-click - or Save and Delete fired in\n    // quick succession - can't send two overlapping mutations for the same\n    // bucket. Cleared on failure so the user can retry; left set on success,\n    // since the modal hides and the page reloads shortly after anyway.\n    let bucketPolicyMutationInFlight = false;\n\n    function setBucketPolicyMutationInFlight(inFlight) {\n        bucketPolicyMutationInFlight = inFlight;\n        document.getElementById('bucketPolicySaveBtn').disabled = inFlight;\n        document.getElementById('bucketPolicyDeleteBtn').disabled = inFlight;\n    }\n\n    let cachedUsers = null;\n\n    // Working copy of the lifecycle rules currently shown in the modal.\n    // Edits (add/edit/delete rule) only mutate this local state; nothing is\n    // sent to the server until \"Save\" or \"Delete all rules\" is clicked.\n    let lifecycleEditor = { bucket: null, rules: [], editingIndex: null, editingRule: null, rawXml: '', hasTransition: false };\n\n    function escapeHtml(v) {\n        return String(v ?? '')\n            .replace(/&/g, '&amp;')\n            .replace(/</g, '&lt;')\n            .replace(/>/g, '&gt;')\n            .replace(/\"/g, '&quot;')\n            .replace(/'/g, '&#39;');\n    }\n\n    // Shadows admin.js's formatBytes for this page, so it keeps that\n    // signature and unit list; the extra guard is for a size the API left\n    // out, which the shared one renders as \"NaN undefined\".\n    function formatBytes(bytes, decimals = 2) {\n        if (!bytes) return '0 Bytes';\n        const k = 1024;\n        const dm = decimals < 0 ? 0 : decimals;\n        const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];\n        const i = Math.floor(Math.log(bytes) / Math.log(k));\n        return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];\n    }\n\n    function formatDays(n) {\n        return n + (n === 1 ? ' day' : ' days');\n    }\n\n    // csrfHeaders reads the token the layout stamps into every page\n    // (<meta name=\"csrf-token\">) so mutating lifecycle requests pass it the\n    // same way the newer admin pages (e.g. s3tables) do.\n    function csrfHeaders() {\n        const meta = document.querySelector('meta[name=\"csrf-token\"]');\n        const token = meta ? (meta.getAttribute('content') || '') : '';\n        const headers = { 'Content-Type': 'application/json' };\n        if (token) {\n            headers['X-CSRF-Token'] = token;\n        }\n        return headers;\n    }\n\n    // parseLifecycleResponse reads the body as text before parsing, so a\n    // non-JSON response (a proxy's HTML error page, an empty body, plain\n    // text from some middleware) surfaces the HTTP status instead of\n    // throwing a raw JSON syntax error out of response.json().\n    function parseLifecycleResponse(response) {\n        return response.text().then(text => {\n            let data = {};\n            if (text) {\n                try {\n                    data = JSON.parse(text);\n                } catch (e) {\n                    data = { error: response.statusText || ('HTTP ' + response.status) };\n                }\n            } else if (!response.ok) {\n                data = { error: response.statusText || ('HTTP ' + response.status) };\n            }\n            return { ok: response.ok, data: data };\n        });\n    }\n\n    document.addEventListener('DOMContentLoaded', function() {\n        // Add click handlers to pagination links\n        document.querySelectorAll('.pagination-link').forEach(link => {\n            link.addEventListener('click', function(e) {\n                e.preventDefault();\n                const page = this.getAttribute('data-page');\n                goToPage(page);\n            });\n        });\n\n        // Initialize modal instances once (reuse with show/hide)\n        deleteModalInstance = new bootstrap.Modal(document.getElementById('deleteBucketModal'));\n        quotaModalInstance = new bootstrap.Modal(document.getElementById('manageQuotaModal'));\n        ownerModalInstance = new bootstrap.Modal(document.getElementById('manageOwnerModal'));\n        detailsModalInstance = new bootstrap.Modal(document.getElementById('bucketDetailsModal'));\n        lifecycleModalInstance = new bootstrap.Modal(document.getElementById('bucketLifecycleModal'));\n        policyModalInstance = new bootstrap.Modal(document.getElementById('bucketPolicyModal'));\n\n        const quotaCheckbox = document.getElementById('enableQuota');\n        const quotaSettings = document.getElementById('quotaSettings');\n        const versioningCheckbox = document.getElementById('enableVersioning');\n        const objectLockCheckbox = document.getElementById('enableObjectLock');\n        const objectLockSettings = document.getElementById('objectLockSettings');\n        const setDefaultRetentionCheckbox = document.getElementById('setDefaultRetention');\n        const defaultRetentionSettings = document.getElementById('defaultRetentionSettings');\n        const createBucketForm = document.getElementById('createBucketForm');\n\n        // Toggle quota settings\n        quotaCheckbox.addEventListener('change', function() {\n            quotaSettings.style.display = this.checked ? 'block' : 'none';\n        });\n\n        // Toggle object lock settings and automatically enable versioning\n        objectLockCheckbox.addEventListener('change', function() {\n            objectLockSettings.style.display = this.checked ? 'block' : 'none';\n            if (this.checked) {\n                versioningCheckbox.checked = true;\n                versioningCheckbox.disabled = true;\n            } else {\n                versioningCheckbox.disabled = false;\n                // Reset default retention settings when object lock is disabled\n                setDefaultRetentionCheckbox.checked = false;\n                defaultRetentionSettings.style.display = 'none';\n            }\n        });\n\n        // Toggle default retention settings\n        setDefaultRetentionCheckbox.addEventListener('change', function() {\n            defaultRetentionSettings.style.display = this.checked ? 'block' : 'none';\n        });\n\n        // Populate owner dropdown when create bucket modal opens\n        document.getElementById('createBucketModal').addEventListener('show.bs.modal', async function() {\n            const ownerSelect = document.getElementById('bucketOwner');\n            \n            // Only fetch if not already populated\n            if (ownerSelect.options.length <= 1) {\n                try {\n                    const response = await fetch(basePath('/api/users'));\n                    const data = await response.json();\n                    const users = data.users || [];\n                    \n                    users.forEach(user => {\n                        const option = document.createElement('option');\n                        option.value = user.username;\n                        option.textContent = user.username;\n                        ownerSelect.appendChild(option);\n                    });\n                } catch (error) {\n                    console.error('Error fetching users for owner dropdown:', error);\n                    // Reset to default state on error - user can still create bucket without owner\n                    ownerSelect.innerHTML = '<option value=\"\">No owner (admin-only access)</option>';\n                    ownerSelect.selectedIndex = 0;\n                }\n            }\n        });\n\n        // Handle form submission\n        createBucketForm.addEventListener('submit', function(e) {\n            e.preventDefault();\n            \n            const formData = new FormData(this);\n            const data = {\n                name: formData.get('name'),\n                owner: formData.get('owner') || '',\n                region: formData.get('region') || '',\n                quota_size: quotaCheckbox.checked ? parseInt(formData.get('quota_size')) || 0 : 0,\n                quota_unit: formData.get('quota_unit') || 'MB',\n                quota_enabled: quotaCheckbox.checked,\n                versioning_enabled: versioningCheckbox.checked,\n                object_lock_enabled: objectLockCheckbox.checked,\n                object_lock_mode: formData.get('object_lock_mode') || 'GOVERNANCE',\n                set_default_retention: setDefaultRetentionCheckbox.checked,\n                object_lock_duration: setDefaultRetentionCheckbox.checked ? parseInt(formData.get('object_lock_duration')) || 30 : 0\n            };\n\n            // Validate object lock settings\n            if (data.object_lock_enabled && data.set_default_retention && data.object_lock_duration <= 0) {\n                alert('Please enter a valid retention duration for object lock.');\n                return;\n            }\n\n            fetch(basePath('/api/s3/buckets'), {\n                method: 'POST',\n                headers: {\n                    'Content-Type': 'application/json',\n                },\n                body: JSON.stringify(data)\n            })\n            .then(response => response.json())\n            .then(data => {\n                if (data.error) {\n                    alert('Error creating bucket: ' + data.error);\n                } else {\n                    alert('Bucket created successfully!');\n                    // Properly close the modal before reloading\n                    const createModal = bootstrap.Modal.getInstance(document.getElementById('createBucketModal'));\n                    if (createModal) {\n                        createModal.hide();\n                    }\n                    setTimeout(() => location.reload(), 500);\n                }\n            })\n            .catch(error => {\n                console.error('Error:', error);\n                alert('Error creating bucket: ' + error.message);\n            });\n        });\n\n        // Handle delete bucket\n        const deleteForm = document.getElementById('deleteBucketModal');\n        document.querySelectorAll('.delete-bucket-btn').forEach(button => {\n            button.addEventListener('click', function() {\n                const bucketName = this.dataset.bucketName;\n                document.getElementById('deleteBucketName').textContent = bucketName;\n                // Store bucket name on modal element instead of global window property\n                deleteForm.dataset.bucketName = bucketName;\n                deleteModalInstance.show();\n            });\n        });\n\n        // Handle quota management\n        const quotaForm = document.getElementById('quotaForm');\n        document.querySelectorAll('.quota-btn').forEach(button => {\n            button.addEventListener('click', function() {\n                const bucketName = this.dataset.bucketName;\n                const currentQuota = parseInt(this.dataset.currentQuota);\n                const quotaEnabled = this.dataset.quotaEnabled === 'true';\n                \n                document.getElementById('quotaBucketName').value = bucketName;\n                document.getElementById('quotaEnabled').checked = quotaEnabled;\n                document.getElementById('quotaSizeMB').value = currentQuota;\n                \n                // Toggle quota size settings\n                document.getElementById('quotaSizeSettings').style.display = quotaEnabled ? 'block' : 'none';\n                \n                // Store bucket name on form element instead of global window property\n                quotaForm.dataset.bucketName = bucketName;\n                quotaModalInstance.show();\n            });\n        });\n\n        // Add event listener to properly dispose of quota modal when hidden\n        document.getElementById('manageQuotaModal').addEventListener('hidden.bs.modal', function() {\n            if (quotaModalInstance) {\n                quotaModalInstance.dispose();\n                quotaModalInstance = null;\n            }\n            // Force remove any remaining backdrops\n            document.querySelectorAll('.modal-backdrop').forEach(backdrop => {\n                backdrop.remove();\n            });\n            // Ensure body classes are removed\n            document.body.classList.remove('modal-open');\n            document.body.style.removeProperty('padding-right');\n        });\n\n        // Handle quota form submission\n        document.getElementById('quotaForm').addEventListener('submit', function(e) {\n            e.preventDefault();\n            \n            const bucketName = this.dataset.bucketName;\n            if (!bucketName) return;\n\n            const formData = new FormData(this);\n            const enabled = document.getElementById('quotaEnabled').checked;\n            const data = {\n                quota_size: enabled ? parseInt(formData.get('quota_size')) || 0 : 0,\n                quota_unit: formData.get('quota_unit') || 'MB',\n                quota_enabled: enabled\n            };\n\n            fetch(basePath(`/api/s3/buckets/${bucketName}/quota`), {\n                method: 'PUT',\n                headers: {\n                    'Content-Type': 'application/json',\n                },\n                body: JSON.stringify(data)\n            })\n            .then(response => response.json())\n            .then(data => {\n                if (data.error) {\n                    alert('Error updating quota: ' + data.error);\n                } else {\n                    alert('Quota updated successfully!');\n                    // Properly close the modal before reloading\n                    if (quotaModalInstance) {\n                        quotaModalInstance.hide();\n                    }\n                    setTimeout(() => location.reload(), 500);\n                }\n            })\n            .catch(error => {\n                console.error('Error:', error);\n                alert('Error updating quota: ' + error.message);\n            });\n        });\n\n        // Handle quota enabled checkbox\n        document.getElementById('quotaEnabled').addEventListener('change', function() {\n            document.getElementById('quotaSizeSettings').style.display = this.checked ? 'block' : 'none';\n        });\n\n        // Handle owner management\n        const ownerForm = document.getElementById('ownerForm');\n        document.querySelectorAll('.owner-btn').forEach(button => {\n            button.addEventListener('click', async function() {\n                const bucketName = this.dataset.bucketName;\n                const currentOwner = this.dataset.currentOwner || '';\n                \n                document.getElementById('ownerBucketName').value = bucketName;\n                // Store bucket name on form element instead of global window property\n                ownerForm.dataset.bucketName = bucketName;\n                \n                // Show loading spinner\n                document.getElementById('ownerLoadingSpinner').style.display = 'block';\n                document.getElementById('bucketOwnerSelect').disabled = true;\n                \n                ownerModalInstance.show();\n                \n                // Fetch users if not cached\n                try {\n                    if (!cachedUsers) {\n                        const response = await fetch(basePath('/api/users'));\n                        const data = await response.json();\n                        cachedUsers = data.users || [];\n                    }\n                    \n                    // Populate the select dropdown\n                    const select = document.getElementById('bucketOwnerSelect');\n                    select.innerHTML = '<option value=\"\">No owner (admin-only access)</option>';\n                    \n                    cachedUsers.forEach(user => {\n                        const option = document.createElement('option');\n                        option.value = user.username;\n                        option.textContent = user.username;\n                        if (user.username === currentOwner) {\n                            option.selected = true;\n                        }\n                        select.appendChild(option);\n                    });\n                    \n                    select.disabled = false;\n                } catch (error) {\n                    console.error('Error fetching users:', error);\n                    alert('Error loading users: ' + error.message);\n                    // Re-enable select and reset to default on error\n                    const select = document.getElementById('bucketOwnerSelect');\n                    select.innerHTML = '<option value=\"\">No owner (admin-only access)</option>';\n                    select.selectedIndex = 0;\n                    select.disabled = false;\n                } finally {\n                    document.getElementById('ownerLoadingSpinner').style.display = 'none';\n                }\n            });\n        });\n\n        // Handle owner form submission\n        document.getElementById('ownerForm').addEventListener('submit', function(e) {\n            e.preventDefault();\n            \n            const bucketName = this.dataset.bucketName;\n            if (!bucketName) return;\n\n            const owner = document.getElementById('bucketOwnerSelect').value;\n            const data = { owner: owner };\n\n            fetch(basePath(`/api/s3/buckets/${bucketName}/owner`), {\n                method: 'PUT',\n                headers: {\n                    'Content-Type': 'application/json',\n                },\n                body: JSON.stringify(data)\n            })\n            .then(response => response.json())\n            .then(data => {\n                if (data.error) {\n                    alert('Error updating owner: ' + data.error);\n                } else {\n                    alert('Bucket owner updated successfully!');\n                    // Properly close the modal before reloading\n                    if (ownerModalInstance) {\n                        ownerModalInstance.hide();\n                    }\n                    setTimeout(() => location.reload(), 500);\n                }\n            })\n            .catch(error => {\n                console.error('Error:', error);\n                alert('Error updating owner: ' + error.message);\n            });\n        });\n\n        // Handle view details button\n        document.querySelectorAll('.view-details-btn').forEach(button => {\n            button.addEventListener('click', function() {\n                const bucketName = this.dataset.bucketName;\n                \n                // Update modal title\n                document.getElementById('bucketDetailsModalLabel').innerHTML = \n                    '<i class=\"fas fa-cube me-2\"></i>Bucket Details - ' + bucketName;\n                \n                // Show loading spinner\n                document.getElementById('bucketDetailsContent').innerHTML = \n                    '<div class=\"text-center py-4\">' +\n                    '<div class=\"spinner-border text-primary\" role=\"status\">' +\n                    '<span class=\"visually-hidden\">Loading...</span>' +\n                    '<\\\\/div>' +\n                    '<div class=\"mt-2\">Loading bucket details...</div>' +\n                    '<\\\\/div>';\n                \n                detailsModalInstance.show();\n                \n                // Fetch bucket details\n                fetch(basePath('/api/s3/buckets/' + bucketName))\n                    .then(response => response.json())\n                    .then(data => {\n                        if (data.error) {\n                            document.getElementById('bucketDetailsContent').innerHTML = \n                                '<div class=\"alert alert-danger\">' +\n                                '<i class=\"fas fa-exclamation-triangle me-2\"></i>' +\n                                'Error loading bucket details: ' + data.error +\n                                '<\\\\/div>';\n                        } else {\n                            displayBucketDetails(data);\n                        }\n                    })\n                    .catch(error => {\n                        console.error('Error fetching bucket details:', error);\n                        document.getElementById('bucketDetailsContent').innerHTML = \n                            '<div class=\"alert alert-danger\">' +\n                            '<i class=\"fas fa-exclamation-triangle me-2\"></i>' +\n                            'Error loading bucket details: ' + error.message +\n                            '<\\\\/div>';\n                    });\n            });\n        });\n\n        // Lifecycle buttons: column badge and action button\n        document.querySelectorAll('.lifecycle-btn').forEach(button => {\n            button.addEventListener('click', function() {\n                const bucketName = this.dataset.bucketName;\n\n                // Reset the working copy until the GET below repopulates it,\n                // so Save/Delete-all can't act on the previous bucket's rules.\n                lifecycleEditor = { bucket: null, rules: [], editingIndex: null, editingRule: null, rawXml: '', hasTransition: false };\n\n                document.getElementById('bucketLifecycleModalLabel').innerHTML =\n                    '<i class=\"fas fa-recycle me-2\"></i>Lifecycle - ' + bucketName;\n\n                document.getElementById('bucketLifecycleContent').innerHTML =\n                    '<div class=\"text-center py-4\">' +\n                    '<div class=\"spinner-border text-primary\" role=\"status\">' +\n                    '<span class=\"visually-hidden\">Loading...</span>' +\n                    '<\\/div>' +\n                    '<div class=\"mt-2\">Loading lifecycle rules...</div>' +\n                    '<\\/div>';\n\n                lifecycleModalInstance.show();\n\n                // Drop responses that arrive after another bucket was opened\n                const requestSeq = ++lifecycleRequestSeq;\n                fetch(basePath('/api/s3/buckets/' + bucketName + '/lifecycle'))\n                    .then(parseLifecycleResponse)\n                    .then(({ ok, data }) => {\n                        if (requestSeq !== lifecycleRequestSeq) return;\n                        if (!ok || data.error) {\n                            document.getElementById('bucketLifecycleContent').innerHTML =\n                                '<div class=\"alert alert-danger\">' +\n                                '<i class=\"fas fa-exclamation-triangle me-2\"></i>' +\n                                'Error loading lifecycle rules: ' + (data.error || 'unknown error') +\n                                '<\\/div>';\n                        } else {\n                            displayBucketLifecycle(data);\n                        }\n                    })\n                    .catch(error => {\n                        if (requestSeq !== lifecycleRequestSeq) return;\n                        console.error('Error fetching bucket lifecycle:', error);\n                        document.getElementById('bucketLifecycleContent').innerHTML =\n                            '<div class=\"alert alert-danger\">' +\n                            '<i class=\"fas fa-exclamation-triangle me-2\"></i>' +\n                            'Error loading lifecycle rules: ' + error.message +\n                            '<\\/div>';\n                    });\n            });\n        });\n\n        // Save the whole working rule set back to the bucket.\n        document.getElementById('lifecycleSaveAllBtn').addEventListener('click', function() {\n            if (!lifecycleEditor.bucket) return;\n            // Fold in any rule the user is still editing so Save doesn't\n            // silently discard it; abort if that form isn't valid yet.\n            if (!commitOpenLifecycleRuleForm()) return;\n            // An empty list clears the configuration, same as Delete all rules,\n            // so it gets the same prompt.\n            if (lifecycleEditor.rules.length === 0 &&\n                !confirm('No rules left: saving removes the lifecycle configuration for ' + lifecycleEditor.bucket + '. This cannot be undone.')) {\n                return;\n            }\n\n            fetch(basePath('/api/s3/buckets/' + lifecycleEditor.bucket + '/lifecycle'), {\n                method: 'PUT',\n                headers: csrfHeaders(),\n                body: JSON.stringify({ rules: lifecycleEditor.rules })\n            })\n                .then(parseLifecycleResponse)\n                .then(({ ok, data }) => {\n                    if (!ok || data.error) {\n                        alert('Error saving lifecycle rules: ' + (data.error || 'unknown error'));\n                        return;\n                    }\n                    if (lifecycleModalInstance) {\n                        lifecycleModalInstance.hide();\n                    }\n                    setTimeout(() => location.reload(), 500);\n                })\n                .catch(error => {\n                    console.error('Error:', error);\n                    alert('Error saving lifecycle rules: ' + error.message);\n                });\n        });\n\n        // Clear the lifecycle configuration entirely.\n        document.getElementById('lifecycleDeleteAllBtn').addEventListener('click', function() {\n            if (!lifecycleEditor.bucket) return;\n            if (!confirm('Delete all lifecycle rules for ' + lifecycleEditor.bucket + '? This cannot be undone.')) return;\n\n            fetch(basePath('/api/s3/buckets/' + lifecycleEditor.bucket + '/lifecycle'), {\n                method: 'DELETE',\n                headers: csrfHeaders()\n            })\n                .then(parseLifecycleResponse)\n                .then(({ ok, data }) => {\n                    if (!ok || data.error) {\n                        alert('Error deleting lifecycle rules: ' + (data.error || 'unknown error'));\n                        return;\n                    }\n                    if (lifecycleModalInstance) {\n                        lifecycleModalInstance.hide();\n                    }\n                    setTimeout(() => location.reload(), 500);\n                })\n                .catch(error => {\n                    console.error('Error:', error);\n                    alert('Error deleting lifecycle rules: ' + error.message);\n                });\n        });\n\n        // Policy buttons: column badge and action button. The visual editor\n        // (weed/admin/static/js/policy_editor.js) is set up once per page\n        // load; registerPolicyEditor is re-called on every open so the\n        // resource-ARN autocomplete and default Principal stay pinned to\n        // whichever bucket is currently open.\n        let bucketPolicyEditorSetUp = false;\n        document.getElementById('bucketPolicyInsertSampleBtn').addEventListener('click', function() {\n            if (!policyEditorBucket) return;\n            insertSamplePolicy('bucket', {\n                Version: '2012-10-17',\n                Statement: [{\n                    Effect: 'Allow',\n                    Principal: '*',\n                    Action: ['s3:GetObject'],\n                    Resource: ['arn:aws:s3:::' + policyEditorBucket + '/*']\n                }]\n            });\n        });\n\n        document.querySelectorAll('.policy-btn').forEach(button => {\n            button.addEventListener('click', function() {\n                const bucketName = this.dataset.bucketName;\n                policyEditorBucket = bucketName;\n                bucketPolicyLoaded = false;\n                // Deliberately not resetting bucketPolicyMutationInFlight\n                // here: if a Save/Delete for a previously open bucket is\n                // still in flight, Save/Delete for this bucket must stay\n                // blocked until that request settles (its own completion\n                // handler releases the flag once done, whether or not it's\n                // still the active bucket by then) - otherwise this bucket's\n                // mutation could race the abandoned one.\n                registerPolicyEditor('bucket', { requirePrincipal: true, bucket: bucketName });\n                if (!bucketPolicyEditorSetUp) {\n                    setupPolicyEditor('bucket');\n                    bucketPolicyEditorSetUp = true;\n                }\n\n                document.getElementById('bucketPolicyModalLabel').innerHTML =\n                    '<i class=\"fas fa-file-shield me-2\"></i>Bucket Policy - ' + escapeHtml(bucketName);\n                document.getElementById('bucketPolicyLoading').style.display = '';\n                document.getElementById('bucketPolicyError').style.display = 'none';\n                document.getElementById('bucketPolicyEditorWrapper').style.display = 'none';\n\n                policyModalInstance.show();\n\n                // Drop responses that arrive after another bucket was opened\n                const requestSeq = ++policyRequestSeq;\n                fetch(basePath('/api/s3/buckets/' + encodeURIComponent(bucketName) + '/policy'))\n                    .then(parseLifecycleResponse)\n                    .then(({ ok, data }) => {\n                        if (requestSeq !== policyRequestSeq) return;\n                        document.getElementById('bucketPolicyLoading').style.display = 'none';\n                        if (!ok || data.error) {\n                            document.getElementById('bucketPolicyError').textContent =\n                                'Error loading bucket policy: ' + (data.error || 'unknown error');\n                            document.getElementById('bucketPolicyError').style.display = '';\n                            return;\n                        }\n                        document.getElementById('bucketPolicyEditorWrapper').style.display = '';\n                        // policy_text carries stored bytes the server-side\n                        // decoder rejects; the JSON tab shows them so they\n                        // can be fixed or deleted.\n                        document.getElementById('bucketPolicyDocument').value =\n                            data.policy ? JSON.stringify(data.policy, null, 2) : (data.policy_text || '');\n                        bucketPolicyLoaded = true;\n                        loadPolicyTextareaIntoEditor('bucket');\n                    })\n                    .catch(error => {\n                        if (requestSeq !== policyRequestSeq) return;\n                        console.error('Error fetching bucket policy:', error);\n                        document.getElementById('bucketPolicyLoading').style.display = 'none';\n                        document.getElementById('bucketPolicyError').textContent =\n                            'Error loading bucket policy: ' + error.message;\n                        document.getElementById('bucketPolicyError').style.display = '';\n                    });\n            });\n        });\n\n        // Save the policy document currently shown (whichever tab is active).\n        document.getElementById('bucketPolicySaveBtn').addEventListener('click', function() {\n            if (!policyEditorBucket) return;\n            if (bucketPolicyMutationInFlight) return;\n            if (!bucketPolicyLoaded) {\n                alert('The current policy has not finished loading. Close and reopen this dialog before saving.');\n                return;\n            }\n\n            // Commit first: if the Editor tab is active, the textarea still\n            // holds whatever was there at load time until this runs, so a\n            // policy entered purely through the structured form (no tab\n            // switch) would otherwise read back as empty here.\n            if (!commitPolicyActiveTab('bucket')) return;\n\n            const raw = document.getElementById('bucketPolicyDocument').value.trim();\n            if (!raw || !policyTextHasStatements(raw)) {\n                alert('Add at least one statement, or use Delete to remove the bucket policy.');\n                return;\n            }\n\n            let policy;\n            try {\n                policy = JSON.parse(document.getElementById('bucketPolicyDocument').value);\n            } catch (e) {\n                alert('Invalid JSON: ' + e.message);\n                return;\n            }\n            const validationError = validatePolicyEditorDoc('bucket', policy);\n            if (validationError) {\n                alert(validationError);\n                return;\n            }\n            // The admin API decodes only Version and Statement, so warn\n            // before other top-level fields are silently dropped - the IAM\n            // policies page already does. (The S3 Tables dialogs don't:\n            // their backend stores the document verbatim.)\n            if (!confirmPolicyFieldDiscard('bucket')) return;\n\n            // Captured now: if the dialog moves on to a different bucket\n            // before this PUT resolves, the completion below must not hide\n            // or reload over whatever that other bucket is now showing.\n            const targetBucket = policyEditorBucket;\n            setBucketPolicyMutationInFlight(true);\n            fetch(basePath('/api/s3/buckets/' + encodeURIComponent(targetBucket) + '/policy'), {\n                method: 'PUT',\n                headers: csrfHeaders(),\n                body: JSON.stringify({ policy: policy })\n            })\n                .then(parseLifecycleResponse)\n                .then(({ ok, data }) => {\n                    setBucketPolicyMutationInFlight(false);\n                    const stillCurrent = targetBucket === policyEditorBucket;\n                    if (!ok || data.error) {\n                        if (stillCurrent) {\n                            alert('Error saving bucket policy: ' + (data.error || 'unknown error'));\n                        } else {\n                            console.error('Error saving policy for ' + targetBucket + ' (no longer the open bucket): ' + (data.error || 'unknown error'));\n                        }\n                        return;\n                    }\n                    if (!stillCurrent) return;\n                    if (policyModalInstance) {\n                        policyModalInstance.hide();\n                    }\n                    setTimeout(() => location.reload(), 500);\n                })\n                .catch(error => {\n                    setBucketPolicyMutationInFlight(false);\n                    console.error('Error:', error);\n                    if (targetBucket === policyEditorBucket) {\n                        alert('Error saving bucket policy: ' + error.message);\n                    }\n                });\n        });\n\n        // Clear the bucket policy entirely.\n        document.getElementById('bucketPolicyDeleteBtn').addEventListener('click', function() {\n            if (!policyEditorBucket) return;\n            if (bucketPolicyMutationInFlight) return;\n            if (!bucketPolicyLoaded) {\n                alert('The current policy has not finished loading. Close and reopen this dialog before deleting.');\n                return;\n            }\n            if (!confirm('Delete the bucket policy for ' + policyEditorBucket + '? This cannot be undone.')) return;\n\n            // Captured now: if the dialog moves on to a different bucket\n            // before this DELETE resolves, the completion below must not\n            // hide or reload over whatever that other bucket is now showing.\n            const targetBucket = policyEditorBucket;\n            setBucketPolicyMutationInFlight(true);\n            fetch(basePath('/api/s3/buckets/' + encodeURIComponent(targetBucket) + '/policy'), {\n                method: 'DELETE',\n                headers: csrfHeaders()\n            })\n                .then(parseLifecycleResponse)\n                .then(({ ok, data }) => {\n                    setBucketPolicyMutationInFlight(false);\n                    const stillCurrent = targetBucket === policyEditorBucket;\n                    if (!ok || data.error) {\n                        if (stillCurrent) {\n                            alert('Error deleting bucket policy: ' + (data.error || 'unknown error'));\n                        } else {\n                            console.error('Error deleting policy for ' + targetBucket + ' (no longer the open bucket): ' + (data.error || 'unknown error'));\n                        }\n                        return;\n                    }\n                    if (!stillCurrent) return;\n                    if (policyModalInstance) {\n                        policyModalInstance.hide();\n                    }\n                    setTimeout(() => location.reload(), 500);\n                })\n                .catch(error => {\n                    setBucketPolicyMutationInFlight(false);\n                    console.error('Error:', error);\n                    if (targetBucket === policyEditorBucket) {\n                        alert('Error deleting bucket policy: ' + error.message);\n                    }\n                });\n        });\n    });\n\n    function deleteBucket() {\n        const bucketName = document.getElementById('deleteBucketModal').dataset.bucketName;\n        if (!bucketName) return;\n\n        fetch(basePath(`/api/s3/buckets/${bucketName}`), {\n            method: 'DELETE'\n        })\n        .then(response => response.json())\n        .then(data => {\n            if (data.error) {\n                alert('Error deleting bucket: ' + data.error);\n            } else {\n                alert('Bucket deleted successfully!');\n                // Properly close the modal before reloading\n                if (deleteModalInstance) {\n                    deleteModalInstance.hide();\n                }\n                setTimeout(() => location.reload(), 500);\n            }\n        })\n        .catch(error => {\n            console.error('Error:', error);\n            alert('Error deleting bucket: ' + error.message);\n        });\n    }\n\nfunction displayBucketDetails(data) {\n    const bucket = data.bucket;\n\n    function formatDate(dateString) {\n        const date = new Date(dateString);\n        return date.toLocaleString();\n    }\n\n    let ownerHtml = '<span class=\"text-muted\">No owner (admin-only)</span>';\n    if (bucket.owner) {\n        ownerHtml = '<span class=\"badge bg-info\"><i class=\"fas fa-user me-1\"></i>' + escapeHtml(bucket.owner) + '</span>';\n    }\n\n    let usageHtml = '';\n    if (bucket.physical_size > 0 && bucket.logical_size > 0 && bucket.physical_size > bucket.logical_size) {\n        const overhead = (bucket.physical_size / bucket.logical_size).toFixed(1);\n        usageHtml = '<br><small class=\"text-muted\">' + overhead + 'x overhead<\\/small>';\n    }\n\n    let quotaHtml = '<span class=\"badge bg-secondary\">Disabled</span>';\n    if (bucket.quota_enabled) {\n        quotaHtml = '<span class=\"badge bg-success\">' + formatBytes(bucket.quota) + '</span>';\n    }\n\n    let versioningHtml = '<span class=\"text-muted\">Not configured</span>';\n    if (bucket.versioning_status === 'Enabled') {\n        versioningHtml = '<span class=\"badge bg-success\"><i class=\"fas fa-check me-1\"></i>Enabled</span>';\n    } else if (bucket.versioning_status === 'Suspended') {\n        versioningHtml = '<span class=\"badge bg-warning\"><i class=\"fas fa-pause me-1\"></i>Suspended</span>';\n    }\n\n    let statusHtml = '<span class=\"badge bg-success\">Writable</span>';\n    if (bucket.read_only) {\n        statusHtml = '<span class=\"badge bg-danger\"><i class=\"fas fa-lock me-1\"></i>Read-only</span>';\n    }\n\n    let objectLockHtml = '<span class=\"text-muted\">Not configured</span>';\n    if (bucket.object_lock_enabled) {\n        let details = '';\n        if (bucket.object_lock_mode && bucket.object_lock_duration > 0) {\n            details = '<br><small class=\"text-muted\">' + escapeHtml(bucket.object_lock_mode) + ' • ' + bucket.object_lock_duration + ' days<\\/small>';\n        }\n        objectLockHtml = '<span class=\"badge bg-warning\"><i class=\"fas fa-lock me-1\"></i>Enabled</span>' + details;\n    }\n\n    let policyHtml = '<span class=\"text-muted\">Not configured</span>';\n    if (bucket.policy_statement_count > 0) {\n        const label = bucket.policy_statement_count === 1 ? '1 statement' : bucket.policy_statement_count + ' statements';\n        policyHtml = '<span class=\"badge bg-primary\">' + label + '<\\/span>';\n    }\n\n    const rows = [\n        '<div class=\"row\">',\n        '<div class=\"col-md-6\">',\n        '<h6><i class=\"fas fa-info-circle me-2\"></i>Bucket Information</h6>',\n        '<table class=\"table table-sm\">',\n        '<tr><td><strong>Name:</strong></td><td>' + escapeHtml(bucket.name) + '<\\/td><\\/tr>',\n        '<tr><td><strong>Owner:</strong></td><td>' + ownerHtml + '<\\/td><\\/tr>',\n        '<tr><td><strong>Created:</strong></td><td>' + formatDate(bucket.created_at) + '<\\/td><\\/tr>',\n        '<tr><td><strong>Last Modified:</strong></td><td>' + formatDate(bucket.last_modified) + '<\\/td><\\/tr>',\n        '<tr><td><strong>Logical Size:</strong></td><td>' + formatBytes(bucket.logical_size) + '<\\/td><\\/tr>',\n        '<tr><td><strong>Physical Size:</strong></td><td>' + formatBytes(bucket.physical_size) + usageHtml + '<\\/td><\\/tr>',\n        '<\\/table>',\n        '<\\/div>',\n        '<div class=\"col-md-6\">',\n        '<h6><i class=\"fas fa-cogs me-2\"></i>Configuration</h6>',\n        '<table class=\"table table-sm\">',\n        '<tr><td><strong>Status:</strong></td><td>' + statusHtml + '<\\/td><\\/tr>',\n        '<tr><td><strong>Quota:</strong></td><td>' + quotaHtml + '<\\/td><\\/tr>',\n        '<tr><td><strong>Versioning:</strong></td><td>' + versioningHtml + '<\\/td><\\/tr>',\n        '<tr><td><strong>Object Lock:</strong></td><td>' + objectLockHtml + '<\\/td><\\/tr>',\n        '<tr><td><strong>Policy:</strong></td><td>' + policyHtml + '<\\/td><\\/tr>',\n        '<\\/table>',\n        '<\\/div>',\n        '<\\/div>'\n    ];\n\n    document.getElementById('bucketDetailsContent').innerHTML = rows.join('');\n}\n\n// displayBucketLifecycle loads the GET response into the local working copy\n// and renders the editor. Nothing is sent back to the server until Save or\n// Delete all rules is clicked.\nfunction displayBucketLifecycle(data) {\n    lifecycleEditor.bucket = data.bucket || lifecycleEditor.bucket;\n    lifecycleEditor.rules = (data.rules || []).map(cloneLifecycleRule);\n    lifecycleEditor.editingIndex = null;\n    lifecycleEditor.editingRule = null;\n    lifecycleEditor.rawXml = data.xml || '';\n    // SeaweedFS doesn't support Transition rules (PutBucketLifecycleConfiguration\n    // rejects them for Enabled rules), and the canonical rule shape the admin\n    // edits has no field for one, so a Transition set by another client would\n    // silently disappear on the next Save. Warn instead.\n    // Matches <Transition> and <NoncurrentVersionTransition> however they are\n    // spelled: with attributes, self-closed, or namespace-prefixed.\n    lifecycleEditor.hasTransition = /<(\\w+:)?(NoncurrentVersion)?Transition[\\s>\\/]/.test(data.xml || '');\n    renderLifecycleEditor();\n}\n\n// normalizeLifecycleStatus maps a stored status onto the two the editor can\n// show. The S3 API stores <Status> unvalidated, and the engine runs a rule\n// only on an exact \"Enabled\", so everything else is Disabled - collapsing it\n// here keeps an odd spelling from leaving both radios unchecked, which would\n// throw out of readLifecycleRuleFromForm and make the modal look dead.\nfunction normalizeLifecycleStatus(status) {\n    return status === 'Enabled' ? 'Enabled' : 'Disabled';\n}\n\nfunction cloneLifecycleRule(r) {\n    return {\n        id: r.id || '',\n        status: normalizeLifecycleStatus(r.status),\n        prefix: r.prefix || '',\n        tags: Object.assign({}, r.tags || {}),\n        size_greater_than: r.size_greater_than || null,\n        size_less_than: r.size_less_than || null,\n        expiration_days: r.expiration_days || null,\n        expiration_date: r.expiration_date || '',\n        expired_object_delete_marker: !!r.expired_object_delete_marker,\n        noncurrent_version_expiration_days: r.noncurrent_version_expiration_days || null,\n        newer_noncurrent_versions: r.newer_noncurrent_versions || null,\n        abort_multipart_days: r.abort_multipart_days || null,\n    };\n}\n\nfunction blankLifecycleRule() {\n    return {\n        id: '', status: 'Enabled', prefix: '', tags: {},\n        size_greater_than: null, size_less_than: null,\n        expiration_days: null, expiration_date: '',\n        expired_object_delete_marker: false,\n        noncurrent_version_expiration_days: null, newer_noncurrent_versions: null,\n        abort_multipart_days: null,\n    };\n}\n\nfunction describeLifecycleScope(rule) {\n    const parts = [];\n    if (rule.prefix) {\n        parts.push('<code>' + escapeHtml(rule.prefix) + '<\\/code>');\n    }\n    Object.entries(rule.tags || {}).forEach(([k, v]) => {\n        parts.push('<span class=\"badge bg-light text-dark border\">' + escapeHtml(k) + '=' + escapeHtml(v) + '<\\/span>');\n    });\n    if (rule.size_greater_than) {\n        parts.push('size &gt; ' + formatBytes(rule.size_greater_than));\n    }\n    if (rule.size_less_than) {\n        parts.push('size &lt; ' + formatBytes(rule.size_less_than));\n    }\n    return parts.length ? parts.join(' ') : '<span class=\"text-muted\">Whole bucket<\\/span>';\n}\n\nfunction describeLifecycleActions(rule) {\n    const actions = [];\n    if (rule.expiration_days) {\n        actions.push('Expire after ' + formatDays(rule.expiration_days));\n    }\n    if (rule.expiration_date) {\n        actions.push('Expire on ' + escapeHtml(rule.expiration_date));\n    }\n    if (rule.expired_object_delete_marker) {\n        actions.push('Remove expired object delete markers');\n    }\n    if (rule.noncurrent_version_expiration_days) {\n        let action = 'Expire noncurrent versions after ' + formatDays(rule.noncurrent_version_expiration_days);\n        if (rule.newer_noncurrent_versions) {\n            action += ', keep ' + rule.newer_noncurrent_versions + ' newest';\n        }\n        actions.push(action);\n    } else if (rule.newer_noncurrent_versions) {\n        // Standalone (no expiration_days): the engine still recognizes this\n        // as an action that expires noncurrent versions beyond the N newest.\n        actions.push('Keep ' + rule.newer_noncurrent_versions + ' newest noncurrent versions');\n    }\n    if (rule.abort_multipart_days) {\n        actions.push('Abort incomplete multipart uploads after ' + formatDays(rule.abort_multipart_days));\n    }\n    if (actions.length === 0) {\n        return '<span class=\"text-muted\">None<\\/span>';\n    }\n    return actions.join('<br>');\n}\n\nfunction lifecycleRuleRowHtml(rule, idx) {\n    const statusHtml = rule.status === 'Enabled'\n        ? '<span class=\"badge bg-success\">Enabled<\\/span>'\n        : '<span class=\"badge bg-secondary\">' + escapeHtml(rule.status) + '<\\/span>';\n    return '<tr>' +\n        '<td>' + (rule.id ? escapeHtml(rule.id) : '<span class=\"text-muted\">unnamed<\\/span>') + '<\\/td>' +\n        '<td>' + statusHtml + '<\\/td>' +\n        '<td>' + describeLifecycleScope(rule) + '<\\/td>' +\n        '<td>' + describeLifecycleActions(rule) + '<\\/td>' +\n        '<td class=\"text-end text-nowrap\">' +\n        '<button type=\"button\" class=\"btn btn-sm btn-outline-secondary lifecycle-edit-rule-btn\" data-index=\"' + idx + '\" title=\"Edit rule\"><i class=\"fas fa-pen\"><\\/i><\\/button> ' +\n        '<button type=\"button\" class=\"btn btn-sm btn-outline-danger lifecycle-delete-rule-btn\" data-index=\"' + idx + '\" title=\"Delete rule\"><i class=\"fas fa-trash\"><\\/i><\\/button>' +\n        '<\\/td>' +\n        '<\\/tr>';\n}\n\nfunction lifecycleRuleFormHtml() {\n    const rule = lifecycleEditor.editingRule;\n    const tagRowHtml = (k, v) =>\n        '<div class=\"input-group input-group-sm mb-1 lifecycle-tag-row\">' +\n        '<input type=\"text\" class=\"form-control lifecycle-tag-key\" placeholder=\"Key\" value=\"' + escapeHtml(k) + '\">' +\n        '<input type=\"text\" class=\"form-control lifecycle-tag-value\" placeholder=\"Value\" value=\"' + escapeHtml(v) + '\">' +\n        '<button type=\"button\" class=\"btn btn-outline-danger lifecycle-remove-tag-btn\"><i class=\"fas fa-times\"><\\/i><\\/button>' +\n        '<\\/div>';\n    const tagRows = Object.entries(rule.tags || {}).map(([k, v]) => tagRowHtml(k, v)).join('');\n\n    return '' +\n        '<div class=\"card mb-3\" id=\"lifecycleRuleForm\">' +\n        '<div class=\"card-body\">' +\n        '<h6 class=\"card-title\">' + (lifecycleEditor.editingIndex === 'new' ? 'Add rule' : 'Edit rule') + '<\\/h6>' +\n\n        '<div class=\"row g-2 mb-2\">' +\n        '<div class=\"col-md-6\">' +\n        '<label class=\"form-label small\">ID (optional)<\\/label>' +\n        '<input type=\"text\" class=\"form-control form-control-sm\" id=\"lifecycleRuleId\" value=\"' + escapeHtml(rule.id) + '\">' +\n        '<\\/div>' +\n        '<div class=\"col-md-6\">' +\n        '<label class=\"form-label small d-block\">Status<\\/label>' +\n        '<div class=\"form-check form-check-inline\">' +\n        '<input class=\"form-check-input\" type=\"radio\" name=\"lifecycleRuleStatus\" id=\"lifecycleStatusEnabled\" value=\"Enabled\"' + (rule.status === 'Enabled' ? ' checked' : '') + '>' +\n        '<label class=\"form-check-label\" for=\"lifecycleStatusEnabled\">Enabled<\\/label>' +\n        '<\\/div>' +\n        '<div class=\"form-check form-check-inline\">' +\n        '<input class=\"form-check-input\" type=\"radio\" name=\"lifecycleRuleStatus\" id=\"lifecycleStatusDisabled\" value=\"Disabled\"' + (rule.status === 'Disabled' ? ' checked' : '') + '>' +\n        '<label class=\"form-check-label\" for=\"lifecycleStatusDisabled\">Disabled<\\/label>' +\n        '<\\/div>' +\n        '<\\/div>' +\n        '<\\/div>' +\n\n        '<div class=\"mb-2\">' +\n        '<label class=\"form-label small\">Prefix<\\/label>' +\n        '<input type=\"text\" class=\"form-control form-control-sm\" id=\"lifecycleRulePrefix\" placeholder=\"logs/\" value=\"' + escapeHtml(rule.prefix) + '\">' +\n        '<\\/div>' +\n\n        '<div class=\"mb-2\">' +\n        '<label class=\"form-label small d-block\">Tags<\\/label>' +\n        '<div id=\"lifecycleTagRows\">' + tagRows + '<\\/div>' +\n        '<button type=\"button\" class=\"btn btn-sm btn-outline-secondary\" id=\"lifecycleAddTagBtn\"><i class=\"fas fa-plus me-1\"><\\/i>Add tag<\\/button>' +\n        '<\\/div>' +\n\n        '<div class=\"row g-2 mb-3\">' +\n        '<div class=\"col-md-6\">' +\n        '<label class=\"form-label small\">Size greater than (bytes)<\\/label>' +\n        '<input type=\"number\" class=\"form-control form-control-sm\" id=\"lifecycleRuleSizeGT\" min=\"0\" step=\"1\" value=\"' + (rule.size_greater_than || '') + '\">' +\n        '<\\/div>' +\n        '<div class=\"col-md-6\">' +\n        '<label class=\"form-label small\">Size less than (bytes)<\\/label>' +\n        '<input type=\"number\" class=\"form-control form-control-sm\" id=\"lifecycleRuleSizeLT\" min=\"0\" step=\"1\" value=\"' + (rule.size_less_than || '') + '\">' +\n        '<\\/div>' +\n        '<\\/div>' +\n\n        '<hr>' +\n        '<h6 class=\"small text-muted text-uppercase\">Actions<\\/h6>' +\n\n        '<div class=\"row g-2 align-items-center mb-2\">' +\n        '<div class=\"col-auto\">' +\n        '<div class=\"form-check\">' +\n        '<input class=\"form-check-input\" type=\"checkbox\" id=\"lifecycleExpireDaysEnabled\"' + (rule.expiration_days ? ' checked' : '') + '>' +\n        '<label class=\"form-check-label\" for=\"lifecycleExpireDaysEnabled\">Expire after<\\/label>' +\n        '<\\/div>' +\n        '<\\/div>' +\n        '<div class=\"col-auto\">' +\n        '<input type=\"number\" class=\"form-control form-control-sm\" id=\"lifecycleExpireDays\" min=\"1\" step=\"1\" style=\"width: 6rem;\" value=\"' + (rule.expiration_days || '') + '\"' + (rule.expiration_days ? '' : ' disabled') + '>' +\n        '<\\/div><div class=\"col-auto\">days<\\/div>' +\n        '<\\/div>' +\n\n        '<div class=\"row g-2 align-items-center mb-2\">' +\n        '<div class=\"col-auto\">' +\n        '<div class=\"form-check\">' +\n        '<input class=\"form-check-input\" type=\"checkbox\" id=\"lifecycleExpireDateEnabled\"' + (rule.expiration_date ? ' checked' : '') + '>' +\n        '<label class=\"form-check-label\" for=\"lifecycleExpireDateEnabled\">Expire on date<\\/label>' +\n        '<\\/div>' +\n        '<\\/div>' +\n        '<div class=\"col-auto\">' +\n        '<input type=\"date\" class=\"form-control form-control-sm\" id=\"lifecycleExpireDate\" value=\"' + escapeHtml(rule.expiration_date) + '\"' + (rule.expiration_date ? '' : ' disabled') + '>' +\n        '<\\/div>' +\n        '<\\/div>' +\n\n        '<div class=\"form-check mb-2\">' +\n        '<input class=\"form-check-input\" type=\"checkbox\" id=\"lifecycleDeleteMarker\"' + (rule.expired_object_delete_marker ? ' checked' : '') + '>' +\n        '<label class=\"form-check-label\" for=\"lifecycleDeleteMarker\">Remove expired object delete markers<\\/label>' +\n        '<\\/div>' +\n\n        (() => {\n            const noncurrentActive = !!(rule.noncurrent_version_expiration_days || rule.newer_noncurrent_versions);\n            return '<div class=\"row g-2 align-items-center mb-2\">' +\n            '<div class=\"col-auto\">' +\n            '<div class=\"form-check\">' +\n            '<input class=\"form-check-input\" type=\"checkbox\" id=\"lifecycleNoncurrentEnabled\"' + (noncurrentActive ? ' checked' : '') + '>' +\n            '<label class=\"form-check-label\" for=\"lifecycleNoncurrentEnabled\">Limit noncurrent versions<\\/label>' +\n            '<\\/div>' +\n            '<\\/div>' +\n            '<div class=\"col-auto\">after<\\/div>' +\n            '<div class=\"col-auto\">' +\n            '<input type=\"number\" class=\"form-control form-control-sm\" id=\"lifecycleNoncurrentDays\" min=\"1\" step=\"1\" style=\"width: 6rem;\" value=\"' + (rule.noncurrent_version_expiration_days || '') + '\"' + (noncurrentActive ? '' : ' disabled') + '>' +\n            '<\\/div><div class=\"col-auto\">days (optional), keep<\\/div>' +\n            '<div class=\"col-auto\">' +\n            '<input type=\"number\" class=\"form-control form-control-sm\" id=\"lifecycleNoncurrentKeep\" min=\"0\" step=\"1\" style=\"width: 6rem;\" value=\"' + (rule.newer_noncurrent_versions || '') + '\"' + (noncurrentActive ? '' : ' disabled') + '>' +\n            '<\\/div><div class=\"col-auto\">newest (optional)<\\/div>' +\n            '<\\/div>';\n        })() +\n\n        '<div class=\"row g-2 align-items-center mb-3\">' +\n        '<div class=\"col-auto\">' +\n        '<div class=\"form-check\">' +\n        '<input class=\"form-check-input\" type=\"checkbox\" id=\"lifecycleAbortMPUEnabled\"' + (rule.abort_multipart_days ? ' checked' : '') + '>' +\n        '<label class=\"form-check-label\" for=\"lifecycleAbortMPUEnabled\">Abort incomplete multipart uploads after<\\/label>' +\n        '<\\/div>' +\n        '<\\/div>' +\n        '<div class=\"col-auto\">' +\n        '<input type=\"number\" class=\"form-control form-control-sm\" id=\"lifecycleAbortMPUDays\" min=\"1\" step=\"1\" style=\"width: 6rem;\" value=\"' + (rule.abort_multipart_days || '') + '\"' + (rule.abort_multipart_days ? '' : ' disabled') + '>' +\n        '<\\/div><div class=\"col-auto\">days<\\/div>' +\n        '<\\/div>' +\n\n        '<div class=\"text-end\">' +\n        '<button type=\"button\" class=\"btn btn-sm btn-secondary\" id=\"lifecycleCancelRuleBtn\">Cancel<\\/button> ' +\n        '<button type=\"button\" class=\"btn btn-sm btn-primary\" id=\"lifecycleSaveRuleBtn\">Save rule<\\/button>' +\n        '<\\/div>' +\n        '<\\/div>' +\n        '<\\/div>';\n}\n\nfunction renderLifecycleEditor() {\n    const container = document.getElementById('bucketLifecycleContent');\n    const rowsHtml = lifecycleEditor.rules.map(lifecycleRuleRowHtml).join('') ||\n        '<tr><td colspan=\"5\" class=\"text-center text-muted py-3\">No lifecycle rules<\\/td><\\/tr>';\n\n    let html = '';\n    if (lifecycleEditor.hasTransition) {\n        html += '<div class=\"alert alert-warning small\">' +\n            '<i class=\"fas fa-triangle-exclamation me-1\"><\\/i>' +\n            'This bucket has a Transition rule set outside the admin (SeaweedFS does not support storage class transitions). Saving from here will drop it.' +\n            '<\\/div>';\n    }\n\n    html += '<div class=\"table-responsive mb-3\">' +\n        '<table class=\"table table-sm align-middle\">' +\n        '<thead><tr><th>Rule<\\/th><th>Status<\\/th><th>Scope<\\/th><th>Actions<\\/th><th><\\/th><\\/tr><\\/thead>' +\n        '<tbody>' + rowsHtml + '<\\/tbody>' +\n        '<\\/table>' +\n        '<\\/div>' +\n        '<button type=\"button\" class=\"btn btn-sm btn-outline-primary mb-3\" id=\"lifecycleAddRuleBtn\">' +\n        '<i class=\"fas fa-plus me-1\"><\\/i>Add rule' +\n        '<\\/button>';\n\n    if (lifecycleEditor.editingIndex !== null) {\n        html += lifecycleRuleFormHtml();\n    }\n\n    if (lifecycleEditor.rawXml) {\n        html += '<details class=\"mt-2\">' +\n            '<summary class=\"text-muted small\">Raw XML (as currently stored)<\\/summary>' +\n            '<pre class=\"bg-light border rounded p-2 mt-2 small mb-0\">' + escapeHtml(lifecycleEditor.rawXml) + '<\\/pre>' +\n            '<\\/details>';\n    }\n\n    container.innerHTML = html;\n    bindLifecycleEditorEvents();\n}\n\nfunction bindLifecycleToggle(checkboxId, fieldId, mutuallyExclusiveId) {\n    const checkbox = document.getElementById(checkboxId);\n    const field = document.getElementById(fieldId);\n    if (!checkbox || !field) return;\n    checkbox.addEventListener('change', function() {\n        field.disabled = !this.checked;\n        if (this.checked && mutuallyExclusiveId) {\n            const other = document.getElementById(mutuallyExclusiveId);\n            if (other && other.checked) {\n                other.checked = false;\n                other.dispatchEvent(new Event('change'));\n            }\n        }\n    });\n}\n\nfunction bindLifecycleEditorEvents() {\n    const addBtn = document.getElementById('lifecycleAddRuleBtn');\n    if (addBtn) {\n        addBtn.addEventListener('click', function() {\n            // Fold in any rule already being edited before switching forms,\n            // so an invalid in-progress draft isn't silently discarded.\n            if (!commitOpenLifecycleRuleForm()) return;\n            lifecycleEditor.editingIndex = 'new';\n            lifecycleEditor.editingRule = blankLifecycleRule();\n            renderLifecycleEditor();\n        });\n    }\n\n    document.querySelectorAll('.lifecycle-edit-rule-btn').forEach(btn => {\n        btn.addEventListener('click', function() {\n            const idx = parseInt(this.dataset.index, 10);\n            // Same as above: committing never shifts indices (it either\n            // pushes a new rule at the end or replaces one in place), so\n            // idx captured above is still valid after this call.\n            if (!commitOpenLifecycleRuleForm()) return;\n            lifecycleEditor.editingIndex = idx;\n            lifecycleEditor.editingRule = cloneLifecycleRule(lifecycleEditor.rules[idx]);\n            renderLifecycleEditor();\n        });\n    });\n\n    document.querySelectorAll('.lifecycle-delete-rule-btn').forEach(btn => {\n        btn.addEventListener('click', function() {\n            const idx = parseInt(this.dataset.index, 10);\n            // Fold in an open form first, like every other transition does:\n            // re-rendering after the splice would otherwise redraw it from the\n            // snapshot taken when editing began and lose what was typed.\n            if (lifecycleEditor.editingIndex !== null && lifecycleEditor.editingIndex !== idx) {\n                if (!commitOpenLifecycleRuleForm()) return;\n            }\n            lifecycleEditor.rules.splice(idx, 1);\n            // Deleting a rule shifts every later index down by one, so an\n            // open edit form must follow its rule rather than silently\n            // start targeting whatever now sits at its old index.\n            if (lifecycleEditor.editingIndex === idx) {\n                lifecycleEditor.editingIndex = null;\n                lifecycleEditor.editingRule = null;\n            } else if (typeof lifecycleEditor.editingIndex === 'number' && lifecycleEditor.editingIndex > idx) {\n                lifecycleEditor.editingIndex -= 1;\n            }\n            renderLifecycleEditor();\n        });\n    });\n\n    const cancelBtn = document.getElementById('lifecycleCancelRuleBtn');\n    if (cancelBtn) {\n        cancelBtn.addEventListener('click', function() {\n            lifecycleEditor.editingIndex = null;\n            lifecycleEditor.editingRule = null;\n            renderLifecycleEditor();\n        });\n    }\n\n    const addTagBtn = document.getElementById('lifecycleAddTagBtn');\n    if (addTagBtn) {\n        addTagBtn.addEventListener('click', function() {\n            const rows = document.getElementById('lifecycleTagRows');\n            const row = document.createElement('div');\n            row.className = 'input-group input-group-sm mb-1 lifecycle-tag-row';\n            row.innerHTML =\n                '<input type=\"text\" class=\"form-control lifecycle-tag-key\" placeholder=\"Key\">' +\n                '<input type=\"text\" class=\"form-control lifecycle-tag-value\" placeholder=\"Value\">' +\n                '<button type=\"button\" class=\"btn btn-outline-danger lifecycle-remove-tag-btn\"><i class=\"fas fa-times\"><\\/i><\\/button>';\n            rows.appendChild(row);\n            row.querySelector('.lifecycle-remove-tag-btn').addEventListener('click', function() {\n                row.remove();\n            });\n        });\n    }\n\n    document.querySelectorAll('.lifecycle-remove-tag-btn').forEach(btn => {\n        btn.addEventListener('click', function() {\n            this.closest('.lifecycle-tag-row').remove();\n        });\n    });\n\n    bindLifecycleToggle('lifecycleExpireDaysEnabled', 'lifecycleExpireDays', 'lifecycleExpireDateEnabled');\n    bindLifecycleToggle('lifecycleExpireDateEnabled', 'lifecycleExpireDate', 'lifecycleExpireDaysEnabled');\n    bindLifecycleToggle('lifecycleNoncurrentEnabled', 'lifecycleNoncurrentDays');\n    bindLifecycleToggle('lifecycleNoncurrentEnabled', 'lifecycleNoncurrentKeep');\n    bindLifecycleToggle('lifecycleAbortMPUEnabled', 'lifecycleAbortMPUDays');\n\n    const saveRuleBtn = document.getElementById('lifecycleSaveRuleBtn');\n    if (saveRuleBtn) {\n        saveRuleBtn.addEventListener('click', saveLifecycleRuleFromForm);\n    }\n}\n\n// readLifecycleRuleFromForm builds and validates a rule from the currently\n// open edit form's DOM inputs. Returns { rule: null, error: '...' } when the\n// form doesn't hold a valid rule, without mutating any state.\nfunction readLifecycleRuleFromForm() {\n    const id = document.getElementById('lifecycleRuleId').value.trim();\n    const status = document.querySelector('input[name=\"lifecycleRuleStatus\"]:checked').value;\n    const prefix = document.getElementById('lifecycleRulePrefix').value.trim();\n\n    const tags = {};\n    document.querySelectorAll('#lifecycleTagRows .lifecycle-tag-row').forEach(row => {\n        const key = row.querySelector('.lifecycle-tag-key').value.trim();\n        const value = row.querySelector('.lifecycle-tag-value').value.trim();\n        if (key) {\n            tags[key] = value;\n        }\n    });\n\n    const sizeGT = parseInt(document.getElementById('lifecycleRuleSizeGT').value, 10) || 0;\n    const sizeLT = parseInt(document.getElementById('lifecycleRuleSizeLT').value, 10) || 0;\n\n    const expireDaysEnabled = document.getElementById('lifecycleExpireDaysEnabled').checked;\n    const expireDateEnabled = document.getElementById('lifecycleExpireDateEnabled').checked;\n    const expirationDays = expireDaysEnabled ? (parseInt(document.getElementById('lifecycleExpireDays').value, 10) || 0) : 0;\n    const expirationDate = expireDateEnabled ? document.getElementById('lifecycleExpireDate').value : '';\n\n    const deleteMarker = document.getElementById('lifecycleDeleteMarker').checked;\n\n    const noncurrentEnabled = document.getElementById('lifecycleNoncurrentEnabled').checked;\n    const noncurrentDays = noncurrentEnabled ? (parseInt(document.getElementById('lifecycleNoncurrentDays').value, 10) || 0) : 0;\n    const noncurrentKeep = noncurrentEnabled ? (parseInt(document.getElementById('lifecycleNoncurrentKeep').value, 10) || 0) : 0;\n\n    const abortEnabled = document.getElementById('lifecycleAbortMPUEnabled').checked;\n    const abortDays = abortEnabled ? (parseInt(document.getElementById('lifecycleAbortMPUDays').value, 10) || 0) : 0;\n\n    if (expireDaysEnabled && expirationDays <= 0) {\n        return { rule: null, error: 'Enter a valid number of days for expiration.' };\n    }\n    if (expireDateEnabled && !expirationDate) {\n        return { rule: null, error: 'Enter a valid expiration date.' };\n    }\n    if (noncurrentEnabled && noncurrentDays <= 0 && noncurrentKeep <= 0) {\n        return { rule: null, error: 'Enter a number of days and/or a newest-versions-to-keep count for noncurrent version handling.' };\n    }\n    if (abortEnabled && abortDays <= 0) {\n        return { rule: null, error: 'Enter a valid number of days for aborting incomplete multipart uploads.' };\n    }\n    if (!expireDaysEnabled && !expireDateEnabled && !deleteMarker && !noncurrentEnabled && !abortEnabled) {\n        return { rule: null, error: 'Select at least one action for this rule.' };\n    }\n    // The server rejects this combination too; catching it here names the two\n    // fields instead of surfacing a 400 after the whole set is submitted.\n    if (deleteMarker && (expireDaysEnabled || expireDateEnabled)) {\n        return { rule: null, error: 'Removing expired object delete markers cannot be combined with an expiration.' };\n    }\n    if (sizeGT > 0 && sizeLT > 0 && sizeGT >= sizeLT) {\n        return { rule: null, error: '\"Size greater than\" must be less than \"size less than\".' };\n    }\n\n    return {\n        rule: {\n            id: id,\n            status: status,\n            prefix: prefix,\n            tags: tags,\n            size_greater_than: sizeGT > 0 ? sizeGT : null,\n            size_less_than: sizeLT > 0 ? sizeLT : null,\n            expiration_days: expirationDays > 0 ? expirationDays : null,\n            expiration_date: expirationDate || '',\n            expired_object_delete_marker: deleteMarker,\n            noncurrent_version_expiration_days: noncurrentDays > 0 ? noncurrentDays : null,\n            newer_noncurrent_versions: noncurrentKeep > 0 ? noncurrentKeep : null,\n            abort_multipart_days: abortDays > 0 ? abortDays : null,\n        },\n        error: null,\n    };\n}\n\nfunction saveLifecycleRuleFromForm() {\n    const { rule, error } = readLifecycleRuleFromForm();\n    if (error) {\n        alert(error);\n        return;\n    }\n\n    if (lifecycleEditor.editingIndex === 'new') {\n        lifecycleEditor.rules.push(rule);\n    } else {\n        lifecycleEditor.rules[lifecycleEditor.editingIndex] = rule;\n    }\n    lifecycleEditor.editingIndex = null;\n    lifecycleEditor.editingRule = null;\n    renderLifecycleEditor();\n}\n\n// commitOpenLifecycleRuleForm folds an in-progress add/edit form into\n// lifecycleEditor.rules before the whole rule set is sent to the server, so\n// clicking the modal's \"Save\" without first clicking \"Save rule\" doesn't\n// silently drop the open edit. Returns false (and leaves the form open) when\n// the open form doesn't hold a valid rule, so the caller can abort the save.\nfunction commitOpenLifecycleRuleForm() {\n    if (lifecycleEditor.editingIndex === null) {\n        return true;\n    }\n\n    const { rule, error } = readLifecycleRuleFromForm();\n    if (error) {\n        alert(error);\n        return false;\n    }\n\n    if (lifecycleEditor.editingIndex === 'new') {\n        lifecycleEditor.rules.push(rule);\n    } else {\n        lifecycleEditor.rules[lifecycleEditor.editingIndex] = rule;\n    }\n    lifecycleEditor.editingIndex = null;\n    lifecycleEditor.editingRule = null;\n    renderLifecycleEditor();\n    return true;\n}\n\n    function goToPage(page) {\n        const url = new URL(window.location);\n        url.searchParams.set('page', page);\n        window.location.href = url.toString();\n    }\n\n    function changePageSize() {\n        const pageSize = document.getElementById('pageSizeSelect').value;\n        const url = new URL(window.location);\n        url.searchParams.set('pageSize', pageSize);\n        url.searchParams.set('page', '1');\n        window.location.href = url.toString();\n    }\n\n    function sortTable(column) {\n        const url = new URL(window.location);\n        const currentSort = url.searchParams.get('sortBy');\n        const currentOrder = url.searchParams.get('sortOrder') || 'asc';\n\n        let newOrder = 'asc';\n        if (currentSort === column && currentOrder === 'asc') {\n            newOrder = 'desc';\n        }\n\n        url.searchParams.set('sortBy', column);\n        url.searchParams.set('sortOrder', newOrder);\n        url.searchParams.set('page', '1');\n        window.location.href = url.toString();\n    }\n\n    function exportBucketList() {\n        // RFC 4180 compliant CSV escaping: escape double quotes by doubling them\n        function escapeCsvField(value) {\n            const str = String(value ?? '');\n            if (str.includes(',') || str.includes('\"') || str.includes('\\n') || str.includes('\\r')) {\n                return '\"' + str.replace(/\"/g, '\"\"') + '\"';\n            }\n            return '\"' + str + '\"';\n        }\n\n        // Fetch all buckets from the API (not just the current page)\n        fetch(basePath('/api/s3/buckets'))\n            .then(response => response.json())\n            .then(data => {\n                if (data.error) {\n                    alert('Error exporting buckets: ' + data.error);\n                    return;\n                }\n\n                const buckets = data.buckets || [];\n                const csvContent = \"data:text/csv;charset=utf-8,\" +\n                    \"Name,Owner,Logical Size,Physical Size,Created,Quota,Versioning,Object Lock\\n\" +\n                    buckets.map(b => [\n                        escapeCsvField(b.name),\n                        escapeCsvField(b.owner),\n                        escapeCsvField(formatBytes(b.logical_size)),\n                        escapeCsvField(formatBytes(b.physical_size)),\n                        escapeCsvField(b.created_at),\n                        escapeCsvField(b.quota_enabled ? formatBytes(b.quota) : 'No quota'),\n                        escapeCsvField(b.versioning_status || 'Not configured'),\n                        escapeCsvField(b.object_lock_enabled ? 'Enabled' : 'Not configured')\n                    ].join(',')).join(\"\\n\");\n\n                const encodedUri = encodeURI(csvContent);\n                const link = document.createElement(\"a\");\n                link.setAttribute(\"href\", encodedUri);\n                link.setAttribute(\"download\", \"buckets.csv\");\n                document.body.appendChild(link);\n                link.click();\n                document.body.removeChild(link);\n            })\n            .catch(error => {\n                console.error('Error:', error);\n                alert('Error exporting buckets: ' + error.message);\n            });\n    }\n    </script>")
		if templ_7745c5c3_Err != nil {
			return templ_7745c5c3_Err
		}
		return nil
	})
}

// Helper functions for template
func getQuotaStatusColor(used, quota int64, enabled bool) string {
	if !enabled || quota <= 0 {
		return "secondary"
	}

	percentage := float64(used) / float64(quota) * 100
	if percentage >= 90 {
		return "danger"
	} else if percentage >= 75 {
		return "warning"
	} else {
		return "success"
	}
}

func getQuotaInMB(quotaBytes int64) int64 {
	if quotaBytes < 0 {
		quotaBytes = -quotaBytes // Handle disabled quotas (negative values)
	}
	return quotaBytes / (1024 * 1024)
}

var _ = templruntime.GeneratedTemplate
