Files
seaweedFS/weed/admin/view/app/s3tables_tables_templ.go
Chris Lu 79722bcf30 Add s3tables shell and admin UI (#8172)
* Add shared s3tables manager

* Add s3tables shell commands

* Add s3tables admin API

* Add s3tables admin UI

* Fix admin s3tables namespace create

* Rename table buckets menu

* Centralize s3tables tag validation

* Reuse s3tables manager in admin

* Extract s3tables list limit

* Add s3tables bucket ARN helper

* Remove write middleware from s3tables APIs

* Fix bucket link and policy hint

* Fix table tag parsing and nav link

* Disable namespace table link on invalid ARN

* Improve s3tables error decode

* Return flag parse errors for s3tables tag

* Accept query params for namespace create

* Bind namespace create form data

* Read s3tables JS data from DOM

* s3tables: allow empty region ARN

* shell: pass s3tables account id

* shell: require account for table buckets

* shell: use bucket name for namespaces

* shell: use bucket name for tables

* shell: use bucket name for tags

* admin: add table buckets links in file browser

* s3api: reuse s3tables tag validation

* admin: harden s3tables UI handlers

* fix admin list table buckets

* allow admin s3tables access

* validate s3tables bucket tags

* log s3tables bucket metadata errors

* rollback table bucket on owner failure

* show s3tables bucket owner

* add s3tables iam conditions

* Add s3tables user permissions UI

* Authorize s3tables using identity actions

* Add s3tables permissions to user modal

* Disambiguate bucket scope in user permissions

* Block table bucket names that match S3 buckets

* Pretty-print IAM identity JSON

* Include tags in s3tables permission context

* admin: refactor S3 Tables inline JavaScript into a separate file

* s3tables: extend IAM policy condition operators support

* shell: use LookupEntry wrapper for s3tables bucket conflict check

* admin: handle buildBucketPermissions validation in create/update flows
2026-01-30 22:57:05 -08:00

318 lines
32 KiB
Go

// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.960
package app
//lint:file-ignore SA4006 This context is only used if a nested component is present.
import "github.com/a-h/templ"
import templruntime "github.com/a-h/templ/runtime"
import (
"fmt"
"github.com/seaweedfs/seaweedfs/weed/admin/dash"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3tables"
)
func S3TablesTables(data dash.S3TablesTablesData) 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-table me-2\"></i>S3 Tables</h1><div class=\"btn-toolbar mb-2 mb-md-0\"><div class=\"btn-group me-2\"><button type=\"button\" class=\"btn btn-sm btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#createS3TablesTableModal\"><i class=\"fas fa-plus me-1\"></i>Create Table</button></div></div></div><div class=\"mb-3\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
bucketName, parseErr := s3tables.ParseBucketNameFromARN(data.BucketARN)
if parseErr == nil {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var2 templ.SafeURL
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(fmt.Sprintf("/object-store/s3tables/buckets/%s/namespaces", bucketName)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 26, Col: 99}
}
_, 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, 3, "\" class=\"btn btn-sm btn-outline-secondary\"><i class=\"fas fa-arrow-left me-1\"></i>Back to Namespaces</a> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "<button type=\"button\" class=\"btn btn-sm btn-outline-secondary\" disabled title=\"Invalid bucket ARN\"><i class=\"fas fa-arrow-left me-1\"></i>Back to Namespaces</button> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "<span class=\"text-muted ms-2\">Bucket ARN: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.BucketARN)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 34, Col: 60}
}
_, 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, 6, "</span> <span class=\"text-muted ms-2\">Namespace: ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(data.Namespace)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 35, Col: 59}
}
_, 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, 7, "</span> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if parseErr != nil {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<span class=\"text-danger ms-2\">Invalid bucket ARN</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "</div><div id=\"s3tables-tables-content\" data-bucket-arn=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.BucketARN)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 40, Col: 67}
}
_, 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, 10, "\" data-namespace=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(data.Namespace)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 40, Col: 101}
}
_, 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, 11, "\"><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 Tables</div><div class=\"h5 mb-0 font-weight-bold text-gray-800\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.TotalTables))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 51, Col: 46}
}
_, 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, 12, "</div></div><div class=\"col-auto\"><i class=\"fas fa-table 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-info shadow h-100 py-2\"><div class=\"card-body\"><div class=\"row no-gutters align-items-center\"><div class=\"col mr-2\"><div class=\"text-xs font-weight-bold text-info text-uppercase mb-1\">Last Updated</div><div class=\"h6 mb-0 font-weight-bold text-gray-800\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.LastUpdated.Format("15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 70, Col: 43}
}
_, 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, 13, "</div></div><div class=\"col-auto\"><i class=\"fas fa-clock fa-2x text-gray-300\"></i></div></div></div></div></div></div><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-table me-2\"></i>Tables</h6></div><div class=\"card-body\"><div class=\"table-responsive\"><table class=\"table table-hover\" width=\"100%\" cellspacing=\"0\" id=\"s3tablesTablesTable\"><thead><tr><th>Name</th><th>Table ARN</th><th>Created</th><th>Modified</th><th>Metadata</th><th>Actions</th></tr></thead> <tbody>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, table := range data.Tables {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
tableName := table.Name
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "<td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(tableName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 106, Col: 26}
}
_, 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, 16, "</td><td class=\"text-muted small\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(table.TableARN)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 107, Col: 56}
}
_, 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, 17, "</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(table.CreatedAt.Format("2006-01-02 15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 108, Col: 59}
}
_, 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, 18, "</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(table.ModifiedAt.Format("2006-01-02 15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 109, Col: 60}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if table.MetadataLocation != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "<span class=\"text-muted small\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(table.MetadataLocation)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 112, Col: 68}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "<span class=\"text-muted\">-</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "</td><td><div class=\"btn-group btn-group-sm\" role=\"group\"><button type=\"button\" class=\"btn btn-outline-success btn-sm s3tables-tags-btn\" data-resource-arn=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(table.TableARN)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 119, Col: 126}
}
_, 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, 24, "\" title=\"Tags\"><i class=\"fas fa-tags\"></i></button> <button type=\"button\" class=\"btn btn-outline-info btn-sm s3tables-table-policy-btn\" data-table-arn=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(table.TableARN)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 122, Col: 128}
}
_, 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, 25, "\" data-table-name=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(tableName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 122, Col: 158}
}
_, 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, 26, "\" title=\"Table Policy\"><i class=\"fas fa-shield-alt\"></i></button> <button type=\"button\" class=\"btn btn-outline-danger btn-sm s3tables-delete-table-btn\" data-table-name=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(tableName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/s3tables_tables.templ`, Line: 125, Col: 126}
}
_, 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, 27, "\" title=\"Delete\"><i class=\"fas fa-trash\"></i></button></div></td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.Tables) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<tr><td colspan=\"6\" class=\"text-center text-muted py-4\"><i class=\"fas fa-table fa-3x mb-3 text-muted\"></i><div><h5>No tables found</h5><p>Create your first table to start storing data.</p><button type=\"button\" class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#createS3TablesTableModal\"><i class=\"fas fa-plus me-1\"></i>Create Table</button></div></td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</tbody></table></div></div></div></div></div></div><div class=\"modal fade\" id=\"createS3TablesTableModal\" tabindex=\"-1\" aria-labelledby=\"createS3TablesTableModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog modal-lg\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"createS3TablesTableModalLabel\"><i class=\"fas fa-plus me-2\"></i>Create Table</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><form id=\"createS3TablesTableForm\"><div class=\"modal-body\"><div class=\"mb-3\"><label for=\"s3tablesTableName\" class=\"form-label\">Table Name</label> <input type=\"text\" class=\"form-control\" id=\"s3tablesTableName\" name=\"name\" required></div><div class=\"mb-3\"><label for=\"s3tablesTableFormat\" class=\"form-label\">Format</label> <select class=\"form-select\" id=\"s3tablesTableFormat\" name=\"format\"><option value=\"ICEBERG\" selected>ICEBERG</option></select></div><div class=\"mb-3\"><label for=\"s3tablesTableMetadata\" class=\"form-label\">Metadata JSON (optional)</label> <textarea class=\"form-control\" id=\"s3tablesTableMetadata\" name=\"metadata\" rows=\"6\" placeholder=\"{ }\"></textarea></div><div class=\"mb-3\"><label for=\"s3tablesTableTags\" class=\"form-label\">Tags</label> <input type=\"text\" class=\"form-control\" id=\"s3tablesTableTags\" name=\"tags\" placeholder=\"key1=value1,key2=value2\"></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</button></div></form></div></div></div><div class=\"modal fade\" id=\"deleteS3TablesTableModal\" tabindex=\"-1\" aria-labelledby=\"deleteS3TablesTableModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"deleteS3TablesTableModalLabel\"><i class=\"fas fa-exclamation-triangle me-2 text-warning\"></i>Delete Table</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 table <strong id=\"deleteS3TablesTableName\"></strong>?</p><div class=\"mb-3\"><label for=\"deleteS3TablesTableVersion\" class=\"form-label\">Version Token (optional)</label> <input type=\"text\" class=\"form-control\" id=\"deleteS3TablesTableVersion\" placeholder=\"Version token\"></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=\"deleteS3TablesTable()\"><i class=\"fas fa-trash me-1\"></i>Delete</button></div></div></div></div><div class=\"modal fade\" id=\"s3tablesTablePolicyModal\" tabindex=\"-1\" aria-labelledby=\"s3tablesTablePolicyModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog modal-lg\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"s3tablesTablePolicyModalLabel\"><i class=\"fas fa-shield-alt me-2\"></i>Table Policy</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><form id=\"s3tablesTablePolicyForm\"><div class=\"modal-body\"><input type=\"hidden\" id=\"s3tablesTablePolicyBucketArn\" name=\"bucket_arn\"> <input type=\"hidden\" id=\"s3tablesTablePolicyNamespace\" name=\"namespace\"> <input type=\"hidden\" id=\"s3tablesTablePolicyName\" name=\"name\"><div class=\"mb-3\"><label for=\"s3tablesTablePolicyText\" class=\"form-label\">Policy JSON</label> <textarea class=\"form-control\" id=\"s3tablesTablePolicyText\" name=\"policy\" rows=\"12\" placeholder=\"{ }\"></textarea></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button> <button type=\"button\" class=\"btn btn-outline-danger\" onclick=\"deleteS3TablesTablePolicy()\"><i class=\"fas fa-trash me-1\"></i>Delete Policy</button> <button type=\"submit\" class=\"btn btn-primary\"><i class=\"fas fa-save me-1\"></i>Save Policy</button></div></form></div></div></div><div class=\"modal fade\" id=\"s3tablesTagsModal\" tabindex=\"-1\" aria-labelledby=\"s3tablesTagsModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog modal-lg\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"s3tablesTagsModalLabel\"><i class=\"fas fa-tags me-2\"></i>Resource Tags</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\" aria-label=\"Close\"></button></div><form id=\"s3tablesTagsForm\"><div class=\"modal-body\"><input type=\"hidden\" id=\"s3tablesTagsResourceArn\" name=\"resource_arn\"><div class=\"mb-3\"><label class=\"form-label\">Existing Tags</label><pre class=\"bg-light p-3 border rounded\" id=\"s3tablesTagsList\">Loading...</pre></div><div class=\"mb-3\"><label for=\"s3tablesTagsInput\" class=\"form-label\">Add or Update Tags</label> <input type=\"text\" class=\"form-control\" id=\"s3tablesTagsInput\" placeholder=\"key1=value1,key2=value2\"></div><div class=\"mb-3\"><label for=\"s3tablesTagsDeleteInput\" class=\"form-label\">Remove Tag Keys</label> <input type=\"text\" class=\"form-control\" id=\"s3tablesTagsDeleteInput\" placeholder=\"key1,key2\"></div></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Close</button> <button type=\"button\" class=\"btn btn-outline-danger\" onclick=\"deleteS3TablesTags()\"><i class=\"fas fa-trash me-1\"></i>Remove Tags</button> <button type=\"submit\" class=\"btn btn-primary\"><i class=\"fas fa-save me-1\"></i>Update Tags</button></div></form></div></div></div><script>\n\t\tlet s3tablesTableDeleteModal = null;\n\t\tlet s3tablesTablePolicyModal = null;\n\t\tlet s3tablesTagsModal = null;\n\n\t\tdocument.addEventListener('DOMContentLoaded', function() {\n\t\t\ts3tablesTableDeleteModal = new bootstrap.Modal(document.getElementById('deleteS3TablesTableModal'));\n\t\t\ts3tablesTablePolicyModal = new bootstrap.Modal(document.getElementById('s3tablesTablePolicyModal'));\n\t\t\ts3tablesTagsModal = new bootstrap.Modal(document.getElementById('s3tablesTagsModal'));\n\n\t\t\tdocument.querySelectorAll('.s3tables-delete-table-btn').forEach(button => {\n\t\t\t\tbutton.addEventListener('click', function() {\n\t\t\t\t\tdocument.getElementById('deleteS3TablesTableName').textContent = this.dataset.tableName || '';\n\t\t\t\t\tdocument.getElementById('deleteS3TablesTableModal').dataset.tableName = this.dataset.tableName || '';\n\t\t\t\t\ts3tablesTableDeleteModal.show();\n\t\t\t\t});\n\t\t\t});\n\n\t\t\tdocument.querySelectorAll('.s3tables-table-policy-btn').forEach(button => {\n\t\t\t\tbutton.addEventListener('click', function() {\n\t\t\t\t\tdocument.getElementById('s3tablesTablePolicyBucketArn').value = dataBucketArn;\n\t\t\t\t\tdocument.getElementById('s3tablesTablePolicyNamespace').value = dataNamespace;\n\t\t\t\t\tdocument.getElementById('s3tablesTablePolicyName').value = this.dataset.tableName || '';\n\t\t\t\t\tloadS3TablesTablePolicy(dataBucketArn, dataNamespace, this.dataset.tableName || '');\n\t\t\t\t\ts3tablesTablePolicyModal.show();\n\t\t\t\t});\n\t\t\t});\n\n\t\t\tdocument.querySelectorAll('.s3tables-tags-btn').forEach(button => {\n\t\t\t\tbutton.addEventListener('click', function() {\n\t\t\t\t\tconst resourceArn = this.dataset.resourceArn || '';\n\t\t\t\t\topenS3TablesTags(resourceArn);\n\t\t\t\t});\n\t\t\t});\n\n\t\t\tdocument.getElementById('createS3TablesTableForm').addEventListener('submit', async function(e) {\n\t\t\t\te.preventDefault();\n\t\t\t\tconst name = document.getElementById('s3tablesTableName').value.trim();\n\t\t\t\tconst format = document.getElementById('s3tablesTableFormat').value;\n\t\t\t\tconst metadataText = document.getElementById('s3tablesTableMetadata').value.trim();\n\t\t\t\tconst tagsInput = document.getElementById('s3tablesTableTags').value.trim();\n\t\t\t\tconst tags = parseTagsInput(tagsInput);\n\t\t\t\tif (tags === null) return;\n\t\t\t\tlet metadata = null;\n\t\t\t\tif (metadataText) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tmetadata = JSON.parse(metadataText);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\talert('Invalid metadata JSON');\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tconst payload = { bucket_arn: dataBucketArn, namespace: dataNamespace, name: name, format: format, tags: tags };\n\t\t\t\tif (metadata) {\n\t\t\t\t\tpayload.metadata = metadata;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await fetch('/api/s3tables/tables', {\n\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\t\t\tbody: JSON.stringify(payload)\n\t\t\t\t\t});\n\t\t\t\t\tconst data = await response.json();\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\talert(data.error || 'Failed to create table');\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\talert('Table created');\n\t\t\t\t\tlocation.reload();\n\t\t\t\t} catch (error) {\n\t\t\t\t\talert('Failed to create table: ' + error.message);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tdocument.getElementById('s3tablesTablePolicyForm').addEventListener('submit', async function(e) {\n\t\t\t\te.preventDefault();\n\t\t\t\tconst policy = document.getElementById('s3tablesTablePolicyText').value.trim();\n\t\t\t\tif (!policy) {\n\t\t\t\t\talert('Policy JSON is required');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst response = await fetch('/api/s3tables/table-policy', {\n\t\t\t\t\t\tmethod: 'PUT',\n\t\t\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\t\t\tbody: JSON.stringify({ bucket_arn: dataBucketArn, namespace: dataNamespace, name: document.getElementById('s3tablesTablePolicyName').value, policy: policy })\n\t\t\t\t\t});\n\t\t\t\t\tconst data = await response.json();\n\t\t\t\t\tif (!response.ok) {\n\t\t\t\t\t\talert(data.error || 'Failed to update policy');\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\talert('Policy updated');\n\t\t\t\t\ts3tablesTablePolicyModal.hide();\n\t\t\t\t} catch (error) {\n\t\t\t\t\talert('Failed to update policy: ' + error.message);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tdocument.getElementById('s3tablesTagsForm').addEventListener('submit', async function(e) {\n\t\t\t\te.preventDefault();\n\t\t\t\tconst resourceArn = document.getElementById('s3tablesTagsResourceArn').value;\n\t\t\t\tconst tags = parseTagsInput(document.getElementById('s3tablesTagsInput').value.trim());\n\t\t\t\tif (tags === null || Object.keys(tags).length === 0) {\n\t\t\t\t\talert('Please provide tags to update');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tawait updateS3TablesTags(resourceArn, tags);\n\t\t\t});\n\t\t});\n\n\t\tconst dataContainer = document.getElementById('s3tables-tables-content');\n\t\tconst dataBucketArn = dataContainer.dataset.bucketArn || '';\n\t\tconst dataNamespace = dataContainer.dataset.namespace || '';\n\n\t\tasync function deleteS3TablesTable() {\n\t\t\tconst tableName = document.getElementById('deleteS3TablesTableModal').dataset.tableName;\n\t\t\tconst versionToken = document.getElementById('deleteS3TablesTableVersion').value.trim();\n\t\t\tif (!tableName) return;\n\t\t\tconst query = new URLSearchParams({\n\t\t\t\tbucket: dataBucketArn,\n\t\t\t\tnamespace: dataNamespace,\n\t\t\t\tname: tableName\n\t\t\t});\n\t\t\tif (versionToken) {\n\t\t\t\tquery.set('version', versionToken);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`/api/s3tables/tables?${query.toString()}`, { method: 'DELETE' });\n\t\t\t\tconst data = await response.json();\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\talert(data.error || 'Failed to delete table');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\talert('Table deleted');\n\t\t\t\tlocation.reload();\n\t\t\t} catch (error) {\n\t\t\t\talert('Failed to delete table: ' + error.message);\n\t\t\t}\n\t\t}\n\n\t\tasync function loadS3TablesTablePolicy(bucketArn, namespace, name) {\n\t\t\tdocument.getElementById('s3tablesTablePolicyText').value = '';\n\t\t\tif (!bucketArn || !namespace || !name) return;\n\t\t\tconst query = new URLSearchParams({ bucket: bucketArn, namespace: namespace, name: name });\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`/api/s3tables/table-policy?${query.toString()}`);\n\t\t\t\tconst data = await response.json();\n\t\t\t\tif (response.ok && data.policy) {\n\t\t\t\t\tdocument.getElementById('s3tablesTablePolicyText').value = data.policy;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Failed to load table policy', error);\n\t\t\t}\n\t\t}\n\n\t\tasync function deleteS3TablesTablePolicy() {\n\t\t\tconst query = new URLSearchParams({ bucket: dataBucketArn, namespace: dataNamespace, name: document.getElementById('s3tablesTablePolicyName').value });\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`/api/s3tables/table-policy?${query.toString()}`, { method: 'DELETE' });\n\t\t\t\tconst data = await response.json();\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\talert(data.error || 'Failed to delete policy');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\talert('Policy deleted');\n\t\t\t\tdocument.getElementById('s3tablesTablePolicyText').value = '';\n\t\t\t} catch (error) {\n\t\t\t\talert('Failed to delete policy: ' + error.message);\n\t\t\t}\n\t\t}\n\n\t\tfunction parseTagsInput(input) {\n\t\t\tif (!input) return {};\n\t\t\tconst tags = {};\n\t\t\tconst parts = input.split(',');\n\t\t\tfor (const part of parts) {\n\t\t\t\tif (!part.trim()) continue;\n\t\t\t\tconst idx = part.indexOf('=');\n\t\t\t\tif (idx === -1) {\n\t\t\t\t\talert('Invalid tag format. Use key=value');\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\tconst key = part.slice(0, idx);\n\t\t\t\tconst value = part.slice(idx + 1);\n\t\t\t\tif (!key || value === undefined) {\n\t\t\t\t\talert('Invalid tag format. Use key=value');\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t\ttags[key.trim()] = value.trim();\n\t\t\t}\n\t\t\treturn tags;\n\t\t}\n\n\t\tasync function openS3TablesTags(resourceArn) {\n\t\t\tif (!resourceArn) return;\n\t\t\tdocument.getElementById('s3tablesTagsResourceArn').value = resourceArn;\n\t\t\tdocument.getElementById('s3tablesTagsInput').value = '';\n\t\t\tdocument.getElementById('s3tablesTagsDeleteInput').value = '';\n\t\t\tdocument.getElementById('s3tablesTagsList').textContent = 'Loading...';\n\t\t\ts3tablesTagsModal.show();\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(`/api/s3tables/tags?arn=${encodeURIComponent(resourceArn)}`);\n\t\t\t\tconst data = await response.json();\n\t\t\t\tif (response.ok) {\n\t\t\t\t\tdocument.getElementById('s3tablesTagsList').textContent = JSON.stringify(data.tags || {}, null, 2);\n\t\t\t\t} else {\n\t\t\t\t\tdocument.getElementById('s3tablesTagsList').textContent = data.error || 'Failed to load tags';\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tdocument.getElementById('s3tablesTagsList').textContent = error.message;\n\t\t\t}\n\t\t}\n\n\t\tasync function updateS3TablesTags(resourceArn, tags) {\n\t\t\ttry {\n\t\t\t\tconst response = await fetch('/api/s3tables/tags', {\n\t\t\t\t\tmethod: 'PUT',\n\t\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify({ resource_arn: resourceArn, tags: tags })\n\t\t\t\t});\n\t\t\t\tconst data = await response.json();\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\talert(data.error || 'Failed to update tags');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\talert('Tags updated');\n\t\t\t\topenS3TablesTags(resourceArn);\n\t\t\t} catch (error) {\n\t\t\t\talert('Failed to update tags: ' + error.message);\n\t\t\t}\n\t\t}\n\n\t\tasync function deleteS3TablesTags() {\n\t\t\tconst resourceArn = document.getElementById('s3tablesTagsResourceArn').value;\n\t\t\tconst keysInput = document.getElementById('s3tablesTagsDeleteInput').value.trim();\n\t\t\tif (!resourceArn) return;\n\t\t\tconst tagKeys = keysInput.split(',').map(k => k.trim()).filter(k => k);\n\t\t\tif (tagKeys.length === 0) {\n\t\t\t\talert('Provide tag keys to remove');\n\t\t\t\treturn;\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst response = await fetch('/api/s3tables/tags', {\n\t\t\t\t\tmethod: 'DELETE',\n\t\t\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\t\t\tbody: JSON.stringify({ resource_arn: resourceArn, tag_keys: tagKeys })\n\t\t\t\t});\n\t\t\t\tconst data = await response.json();\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\talert(data.error || 'Failed to remove tags');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\talert('Tags removed');\n\t\t\t\topenS3TablesTags(resourceArn);\n\t\t\t} catch (error) {\n\t\t\t\talert('Failed to remove tags: ' + error.message);\n\t\t\t}\n\t\t}\n\t</script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate