Browse Source

Add Iceberg table details view

pull/8249/head
Chris Lu 3 days ago
parent
commit
d9e3fb2b8e
  1. 279
      weed/admin/dash/s3tables_management.go
  2. 53
      weed/admin/dash/types.go
  3. 28
      weed/admin/handlers/admin_handlers.go
  4. 198
      weed/admin/static/js/s3tables.js
  5. 178
      weed/admin/view/app/file_browser_templ.go
  6. 376
      weed/admin/view/app/iceberg_table_details.templ
  7. 618
      weed/admin/view/app/iceberg_table_details_templ.go

279
weed/admin/dash/s3tables_management.go

@ -7,6 +7,8 @@ import (
"fmt"
"io"
"net/http"
"sort"
"strconv"
"strings"
"time"
@ -218,6 +220,7 @@ func (s *AdminServer) GetIcebergNamespacesData(ctx context.Context, catalogName,
return IcebergNamespacesData{
CatalogName: catalogName,
BucketARN: bucketArn,
Namespaces: namespaces,
TotalNamespaces: len(namespaces),
LastUpdated: time.Now(),
@ -242,12 +245,288 @@ func (s *AdminServer) GetIcebergTablesData(ctx context.Context, catalogName, buc
return IcebergTablesData{
CatalogName: catalogName,
NamespaceName: namespace,
BucketARN: bucketArn,
Tables: tables,
TotalTables: len(tables),
LastUpdated: time.Now(),
}, nil
}
// GetIcebergTableDetailsData returns Iceberg table metadata and snapshot information.
func (s *AdminServer) GetIcebergTableDetailsData(ctx context.Context, catalogName, bucketArn, namespace, tableName string) (IcebergTableDetailsData, error) {
var resp s3tables.GetTableResponse
req := &s3tables.GetTableRequest{
TableBucketARN: bucketArn,
Namespace: []string{namespace},
Name: tableName,
}
if err := s.executeS3TablesOperation(ctx, "GetTable", req, &resp); err != nil {
return IcebergTableDetailsData{}, err
}
details := IcebergTableDetailsData{
CatalogName: catalogName,
NamespaceName: namespace,
TableName: resp.Name,
BucketARN: bucketArn,
TableARN: resp.TableARN,
Format: resp.Format,
CreatedAt: resp.CreatedAt,
ModifiedAt: resp.ModifiedAt,
MetadataLocation: resp.MetadataLocation,
}
applyIcebergMetadata(resp.Metadata, &details)
return details, nil
}
type icebergFullMetadata struct {
FormatVersion int `json:"format-version"`
TableUUID string `json:"table-uuid"`
Location string `json:"location"`
LastUpdatedMs int64 `json:"last-updated-ms"`
Schemas []icebergSchema `json:"schemas"`
Schema *icebergSchema `json:"schema"`
CurrentSchemaID int `json:"current-schema-id"`
PartitionSpecs []icebergPartitionSpec `json:"partition-specs"`
PartitionSpec *icebergPartitionSpec `json:"partition-spec"`
DefaultSpecID int `json:"default-spec-id"`
Properties map[string]string `json:"properties"`
Snapshots []icebergSnapshot `json:"snapshots"`
CurrentSnapshotID int64 `json:"current-snapshot-id"`
}
type icebergSchema struct {
SchemaID int `json:"schema-id"`
Fields []icebergSchemaField `json:"fields"`
}
type icebergSchemaField struct {
ID int `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Required bool `json:"required"`
}
type icebergPartitionSpec struct {
SpecID int `json:"spec-id"`
Fields []icebergPartitionField `json:"fields"`
}
type icebergPartitionField struct {
SourceID int `json:"source-id"`
FieldID int `json:"field-id"`
Name string `json:"name"`
Transform string `json:"transform"`
}
type icebergSnapshot struct {
SnapshotID int64 `json:"snapshot-id"`
TimestampMs int64 `json:"timestamp-ms"`
ManifestList string `json:"manifest-list"`
Summary map[string]string `json:"summary"`
}
func applyIcebergMetadata(metadata *s3tables.TableMetadata, details *IcebergTableDetailsData) {
if details == nil || metadata == nil {
return
}
if len(metadata.FullMetadata) == 0 {
details.SchemaFields = schemaFieldsFromIceberg(metadata.Iceberg)
return
}
var full icebergFullMetadata
if err := json.Unmarshal(metadata.FullMetadata, &full); err != nil {
glog.V(1).Infof("iceberg metadata parse failed: %v", err)
details.MetadataError = fmt.Sprintf("Failed to parse Iceberg metadata: %v", err)
details.SchemaFields = schemaFieldsFromIceberg(metadata.Iceberg)
return
}
details.TableLocation = full.Location
details.SchemaFields = schemaFieldsFromFullMetadata(full, metadata.Iceberg)
details.PartitionFields = partitionFieldsFromFullMetadata(full)
details.Properties = propertiesFromFullMetadata(full.Properties)
details.Snapshots = snapshotsFromFullMetadata(full.Snapshots)
details.SnapshotCount = len(full.Snapshots)
details.HasSnapshotCount = true
if metricsSnapshot := selectSnapshotForMetrics(full); metricsSnapshot != nil {
if value, ok := parseSummaryInt(metricsSnapshot.Summary, "total-data-files", "total-data-file-count", "total-files", "total-file-count"); ok {
details.DataFileCount = value
details.HasDataFileCount = true
}
if value, ok := parseSummaryInt(metricsSnapshot.Summary, "total-files-size", "total-data-files-size", "total-file-size", "total-data-file-size", "total-data-size", "total-size"); ok {
details.TotalSizeBytes = value
details.HasTotalSize = true
}
}
}
func schemaFieldsFromFullMetadata(full icebergFullMetadata, fallback *s3tables.IcebergMetadata) []IcebergSchemaFieldInfo {
if schema := selectSchema(full); schema != nil {
fields := make([]IcebergSchemaFieldInfo, 0, len(schema.Fields))
for _, field := range schema.Fields {
fields = append(fields, IcebergSchemaFieldInfo{
ID: field.ID,
Name: field.Name,
Type: field.Type,
Required: field.Required,
})
}
return fields
}
return schemaFieldsFromIceberg(fallback)
}
func schemaFieldsFromIceberg(metadata *s3tables.IcebergMetadata) []IcebergSchemaFieldInfo {
if metadata == nil {
return nil
}
fields := make([]IcebergSchemaFieldInfo, 0, len(metadata.Schema.Fields))
for _, field := range metadata.Schema.Fields {
fields = append(fields, IcebergSchemaFieldInfo{
Name: field.Name,
Type: field.Type,
Required: field.Required,
})
}
return fields
}
func selectSchema(full icebergFullMetadata) *icebergSchema {
if len(full.Schemas) == 0 && full.Schema == nil {
return nil
}
if len(full.Schemas) == 0 {
return full.Schema
}
if full.CurrentSchemaID != 0 {
for i := range full.Schemas {
if full.Schemas[i].SchemaID == full.CurrentSchemaID {
return &full.Schemas[i]
}
}
}
return &full.Schemas[0]
}
func partitionFieldsFromFullMetadata(full icebergFullMetadata) []IcebergPartitionFieldInfo {
var spec *icebergPartitionSpec
if len(full.PartitionSpecs) == 0 && full.PartitionSpec == nil {
return nil
}
if len(full.PartitionSpecs) == 0 {
spec = full.PartitionSpec
} else {
if full.DefaultSpecID != 0 {
for i := range full.PartitionSpecs {
if full.PartitionSpecs[i].SpecID == full.DefaultSpecID {
spec = &full.PartitionSpecs[i]
break
}
}
}
if spec == nil {
spec = &full.PartitionSpecs[0]
}
}
if spec == nil {
return nil
}
fields := make([]IcebergPartitionFieldInfo, 0, len(spec.Fields))
for _, field := range spec.Fields {
fields = append(fields, IcebergPartitionFieldInfo{
Name: field.Name,
Transform: field.Transform,
SourceID: field.SourceID,
FieldID: field.FieldID,
})
}
return fields
}
func propertiesFromFullMetadata(properties map[string]string) []IcebergPropertyInfo {
if len(properties) == 0 {
return nil
}
keys := make([]string, 0, len(properties))
for key := range properties {
keys = append(keys, key)
}
sort.Strings(keys)
entries := make([]IcebergPropertyInfo, 0, len(keys))
for _, key := range keys {
entries = append(entries, IcebergPropertyInfo{Key: key, Value: properties[key]})
}
return entries
}
func snapshotsFromFullMetadata(snapshots []icebergSnapshot) []IcebergSnapshotInfo {
if len(snapshots) == 0 {
return nil
}
sort.Slice(snapshots, func(i, j int) bool {
return snapshots[i].TimestampMs > snapshots[j].TimestampMs
})
info := make([]IcebergSnapshotInfo, 0, len(snapshots))
for _, snapshot := range snapshots {
operation := ""
if snapshot.Summary != nil {
operation = snapshot.Summary["operation"]
}
timestamp := time.Time{}
if snapshot.TimestampMs > 0 {
timestamp = time.Unix(0, snapshot.TimestampMs*int64(time.Millisecond))
}
info = append(info, IcebergSnapshotInfo{
SnapshotID: snapshot.SnapshotID,
Timestamp: timestamp,
Operation: operation,
ManifestList: snapshot.ManifestList,
})
}
return info
}
func selectSnapshotForMetrics(full icebergFullMetadata) *icebergSnapshot {
if len(full.Snapshots) == 0 {
return nil
}
if full.CurrentSnapshotID != 0 {
for i := range full.Snapshots {
if full.Snapshots[i].SnapshotID == full.CurrentSnapshotID {
return &full.Snapshots[i]
}
}
}
latest := full.Snapshots[0]
for _, snapshot := range full.Snapshots[1:] {
if snapshot.TimestampMs > latest.TimestampMs {
latest = snapshot
}
}
return &latest
}
func parseSummaryInt(summary map[string]string, keys ...string) (int64, bool) {
if len(summary) == 0 {
return 0, false
}
for _, key := range keys {
value, ok := summary[key]
if !ok || value == "" {
continue
}
parsed, err := strconv.ParseInt(value, 10, 64)
if err != nil {
continue
}
return parsed, true
}
return 0, false
}
// API handlers
func (s *AdminServer) ListS3TablesBucketsAPI(c *gin.Context) {

53
weed/admin/dash/types.go

@ -621,6 +621,7 @@ type IcebergNamespaceInfo struct {
type IcebergNamespacesData struct {
Username string `json:"username"`
CatalogName string `json:"catalog_name"`
BucketARN string `json:"bucket_arn"`
Namespaces []IcebergNamespaceInfo `json:"namespaces"`
TotalNamespaces int `json:"total_namespaces"`
LastUpdated time.Time `json:"last_updated"`
@ -635,7 +636,59 @@ type IcebergTablesData struct {
Username string `json:"username"`
CatalogName string `json:"catalog_name"`
NamespaceName string `json:"namespace_name"`
BucketARN string `json:"bucket_arn"`
Tables []IcebergTableInfo `json:"tables"`
TotalTables int `json:"total_tables"`
LastUpdated time.Time `json:"last_updated"`
}
type IcebergSchemaFieldInfo struct {
ID int `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Required bool `json:"required"`
}
type IcebergPartitionFieldInfo struct {
Name string `json:"name"`
Transform string `json:"transform"`
SourceID int `json:"source_id"`
FieldID int `json:"field_id"`
}
type IcebergPropertyInfo struct {
Key string `json:"key"`
Value string `json:"value"`
}
type IcebergSnapshotInfo struct {
SnapshotID int64 `json:"snapshot_id"`
Timestamp time.Time `json:"timestamp"`
Operation string `json:"operation"`
ManifestList string `json:"manifest_list"`
}
type IcebergTableDetailsData struct {
Username string `json:"username"`
CatalogName string `json:"catalog_name"`
NamespaceName string `json:"namespace_name"`
TableName string `json:"table_name"`
BucketARN string `json:"bucket_arn"`
TableARN string `json:"table_arn"`
Format string `json:"format"`
CreatedAt time.Time `json:"created_at"`
ModifiedAt time.Time `json:"modified_at"`
MetadataLocation string `json:"metadata_location"`
TableLocation string `json:"table_location"`
SchemaFields []IcebergSchemaFieldInfo `json:"schema_fields"`
PartitionFields []IcebergPartitionFieldInfo `json:"partition_fields"`
Properties []IcebergPropertyInfo `json:"properties"`
Snapshots []IcebergSnapshotInfo `json:"snapshots"`
SnapshotCount int `json:"snapshot_count"`
HasSnapshotCount bool `json:"has_snapshot_count"`
DataFileCount int64 `json:"data_file_count"`
HasDataFileCount bool `json:"has_data_file_count"`
TotalSizeBytes int64 `json:"total_size_bytes"`
HasTotalSize bool `json:"has_total_size"`
MetadataError string `json:"metadata_error,omitempty"`
}

28
weed/admin/handlers/admin_handlers.go

@ -94,6 +94,7 @@ func (h *AdminHandlers) SetupRoutes(r *gin.Engine, authRequired bool, adminUser,
protected.GET("/object-store/iceberg", h.ShowIcebergCatalog)
protected.GET("/object-store/iceberg/:catalog/namespaces", h.ShowIcebergNamespaces)
protected.GET("/object-store/iceberg/:catalog/namespaces/:namespace/tables", h.ShowIcebergTables)
protected.GET("/object-store/iceberg/:catalog/namespaces/:namespace/tables/:table", h.ShowIcebergTableDetails)
// File browser routes
protected.GET("/files", h.fileBrowserHandlers.ShowFileBrowser)
@ -265,6 +266,7 @@ func (h *AdminHandlers) SetupRoutes(r *gin.Engine, authRequired bool, adminUser,
r.GET("/object-store/iceberg", h.ShowIcebergCatalog)
r.GET("/object-store/iceberg/:catalog/namespaces", h.ShowIcebergNamespaces)
r.GET("/object-store/iceberg/:catalog/namespaces/:namespace/tables", h.ShowIcebergTables)
r.GET("/object-store/iceberg/:catalog/namespaces/:namespace/tables/:table", h.ShowIcebergTableDetails)
// File browser routes
r.GET("/files", h.fileBrowserHandlers.ShowFileBrowser)
@ -609,6 +611,32 @@ func (h *AdminHandlers) ShowIcebergTables(c *gin.Context) {
}
}
// ShowIcebergTableDetails renders the table metadata and snapshot details view.
func (h *AdminHandlers) ShowIcebergTableDetails(c *gin.Context) {
catalogName := c.Param("catalog")
namespace := c.Param("namespace")
tableName := c.Param("table")
arn, err := buildS3TablesBucketArn(catalogName)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
data, err := h.adminServer.GetIcebergTableDetailsData(c.Request.Context(), catalogName, arn, namespace, tableName)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get Iceberg table details: " + err.Error()})
return
}
data.Username = h.getUsername(c)
c.Header("Content-Type", "text/html")
component := app.IcebergTableDetails(data)
layoutComponent := layout.Layout(c, component)
if err := layoutComponent.Render(c.Request.Context(), c.Writer); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()})
}
}
// ShowBucketDetails returns detailed information about a specific bucket
func (h *AdminHandlers) ShowBucketDetails(c *gin.Context) {
bucketName := c.Param("bucket")

198
weed/admin/static/js/s3tables.js

@ -9,6 +9,7 @@ let s3tablesNamespaceDeleteModal = null;
let s3tablesTableDeleteModal = null;
let s3tablesTablePolicyModal = null;
let s3tablesTagsModal = null;
let icebergTableDeleteModal = null;
/**
* Initialize S3 Tables Buckets Page
@ -261,6 +262,173 @@ function initS3TablesTables() {
}
}
/**
* Initialize Iceberg Namespaces Page
*/
function initIcebergNamespaces() {
const container = document.getElementById('iceberg-namespaces-content');
if (!container) return;
const bucketArn = container.dataset.bucketArn || '';
const catalogName = container.dataset.catalogName || '';
const createForm = document.getElementById('createIcebergNamespaceForm');
if (createForm) {
createForm.addEventListener('submit', async function (e) {
e.preventDefault();
const name = document.getElementById('icebergNamespaceName').value.trim();
if (!name) {
alert('Namespace name is required');
return;
}
try {
const response = await fetch('/api/s3tables/namespaces', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ bucket_arn: bucketArn, name: name })
});
const data = await response.json();
if (!response.ok) {
alert(data.error || 'Failed to create namespace');
return;
}
alert('Namespace created');
location.reload();
} catch (error) {
alert('Failed to create namespace: ' + error.message);
}
});
}
initIcebergNamespaceTree(container, bucketArn, catalogName);
}
function initIcebergNamespaceTree(container, bucketArn, catalogName) {
const nodes = container.querySelectorAll('.iceberg-namespace-collapse');
nodes.forEach(node => {
node.addEventListener('show.bs.collapse', async function () {
if (node.dataset.loaded === 'true') return;
node.dataset.loaded = 'true';
node.innerHTML = '<div class="text-muted small">Loading...</div>';
await loadIcebergNamespaceTables(node, bucketArn, catalogName);
});
});
}
async function loadIcebergNamespaceTables(node, bucketArn, catalogName) {
const namespace = node.dataset.namespace || '';
if (!bucketArn || !namespace) {
node.innerHTML = '<div class="text-muted small">No namespace data available.</div>';
return;
}
try {
const query = new URLSearchParams({ bucket: bucketArn, namespace: namespace });
const response = await fetch(`/api/s3tables/tables?${query.toString()}`);
const data = await response.json();
if (!response.ok) {
node.innerHTML = `<div class="text-danger small">${data.error || 'Failed to load tables'}</div>`;
return;
}
const tables = data.tables || [];
if (tables.length === 0) {
node.innerHTML = '<div class="text-muted small ms-3">No tables found.</div>';
return;
}
const list = document.createElement('ul');
list.className = 'list-group list-group-flush ms-3';
tables.forEach(table => {
const item = document.createElement('li');
item.className = 'list-group-item py-1';
const link = document.createElement('a');
link.className = 'text-decoration-none';
link.href = `/object-store/iceberg/${encodeURIComponent(catalogName)}/namespaces/${encodeURIComponent(namespace)}/tables/${encodeURIComponent(table.name)}`;
link.innerHTML = `<i class="fas fa-table text-primary me-2"></i>${table.name}`;
item.appendChild(link);
list.appendChild(item);
});
node.innerHTML = '';
node.appendChild(list);
} catch (error) {
node.innerHTML = `<div class="text-danger small">${error.message}</div>`;
}
}
/**
* Initialize Iceberg Tables Page
*/
function initIcebergTables() {
const container = document.getElementById('iceberg-tables-content');
if (!container) return;
const bucketArn = container.dataset.bucketArn || '';
const namespace = container.dataset.namespace || '';
initIcebergDeleteModal();
const createForm = document.getElementById('createIcebergTableForm');
if (createForm) {
createForm.addEventListener('submit', async function (e) {
e.preventDefault();
const name = document.getElementById('icebergTableName').value.trim();
const format = document.getElementById('icebergTableFormat').value;
const metadataText = document.getElementById('icebergTableMetadata').value.trim();
const tagsInput = document.getElementById('icebergTableTags').value.trim();
const tags = parseTagsInput(tagsInput);
if (tags === null) return;
let metadata = null;
if (metadataText) {
try {
metadata = JSON.parse(metadataText);
} catch (error) {
alert('Invalid metadata JSON');
return;
}
}
const payload = { bucket_arn: bucketArn, namespace: namespace, name: name, format: format, tags: tags };
if (metadata) {
payload.metadata = metadata;
}
try {
const response = await fetch('/api/s3tables/tables', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await response.json();
if (!response.ok) {
alert(data.error || 'Failed to create table');
return;
}
alert('Table created');
location.reload();
} catch (error) {
alert('Failed to create table: ' + error.message);
}
});
}
}
/**
* Initialize Iceberg Table Details Page
*/
function initIcebergTableDetails() {
initIcebergDeleteModal();
}
function initIcebergDeleteModal() {
const modalEl = document.getElementById('deleteIcebergTableModal');
if (!modalEl) return;
icebergTableDeleteModal = new bootstrap.Modal(modalEl);
document.querySelectorAll('.iceberg-delete-table-btn').forEach(button => {
button.addEventListener('click', function () {
modalEl.dataset.bucketArn = this.dataset.bucketArn || '';
modalEl.dataset.namespace = this.dataset.namespace || '';
modalEl.dataset.tableName = this.dataset.tableName || '';
document.getElementById('deleteIcebergTableName').textContent = this.dataset.tableName || '';
document.getElementById('deleteIcebergTableVersion').value = '';
icebergTableDeleteModal.show();
});
});
}
// Global scope functions used by onclick handlers
async function deleteS3TablesBucket() {
@ -340,6 +508,36 @@ async function deleteS3TablesTable() {
}
}
async function deleteIcebergTable() {
const modalEl = document.getElementById('deleteIcebergTableModal');
if (!modalEl) return;
const bucketArn = modalEl.dataset.bucketArn || '';
const namespace = modalEl.dataset.namespace || '';
const tableName = modalEl.dataset.tableName || '';
const versionToken = document.getElementById('deleteIcebergTableVersion').value.trim();
if (!bucketArn || !namespace || !tableName) return;
const query = new URLSearchParams({
bucket: bucketArn,
namespace: namespace,
name: tableName
});
if (versionToken) {
query.set('version', versionToken);
}
try {
const response = await fetch(`/api/s3tables/tables?${query.toString()}`, { method: 'DELETE' });
const data = await response.json();
if (!response.ok) {
alert(data.error || 'Failed to drop table');
return;
}
alert('Table dropped');
location.reload();
} catch (error) {
alert('Failed to drop table: ' + error.message);
}
}
async function loadS3TablesTablePolicy(bucketArn, namespace, name) {
document.getElementById('s3tablesTablePolicyText').value = '';
if (!bucketArn || !namespace || !name) return;

178
weed/admin/view/app/file_browser_templ.go
File diff suppressed because it is too large
View File

376
weed/admin/view/app/iceberg_table_details.templ

@ -0,0 +1,376 @@
package app
import (
"fmt"
"github.com/seaweedfs/seaweedfs/weed/admin/dash"
)
templ IcebergTableDetails(data dash.IcebergTableDetailsData) {
<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">
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-0">
<li class="breadcrumb-item">
<a href="/object-store/iceberg">
<i class="fas fa-snowflake me-1"></i>Iceberg Catalog
</a>
</li>
<li class="breadcrumb-item">
<a href={ templ.SafeURL(fmt.Sprintf("/object-store/iceberg/%s/namespaces", data.CatalogName)) }>
{ data.CatalogName }
</a>
</li>
<li class="breadcrumb-item">
<a href={ templ.SafeURL(fmt.Sprintf("/object-store/iceberg/%s/namespaces/%s/tables", data.CatalogName, data.NamespaceName)) }>
{ data.NamespaceName }
</a>
</li>
<li class="breadcrumb-item active">{ data.TableName }</li>
</ol>
</nav>
</h1>
<div class="btn-toolbar mb-2 mb-md-0">
<div class="btn-group me-2">
<button type="button" class="btn btn-sm btn-danger iceberg-delete-table-btn" data-bucket-arn={ data.BucketARN } data-namespace={ data.NamespaceName } data-table-name={ data.TableName }>
<i class="fas fa-trash me-1"></i>Drop Table
</button>
</div>
</div>
</div>
if data.MetadataError != "" {
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>{ data.MetadataError }
</div>
}
<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">
Data Files
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">
if data.HasDataFileCount {
{ formatNumber(data.DataFileCount) }
} else {
-
}
</div>
</div>
<div class="col-auto">
<i class="fas fa-copy 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 Size
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">
if data.HasTotalSize {
{ formatBytes(data.TotalSizeBytes) }
} else {
-
}
</div>
</div>
<div class="col-auto">
<i class="fas fa-database 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">
Snapshots
</div>
<div class="h5 mb-0 font-weight-bold text-gray-800">
if data.HasSnapshotCount {
{ formatNumber(int64(data.SnapshotCount)) }
} else {
-
}
</div>
</div>
<div class="col-auto">
<i class="fas fa-history fa-2x text-gray-300"></i>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-lg-6 mb-4">
<div class="card shadow h-100">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-info-circle me-2"></i>Table Metadata
</h6>
</div>
<div class="card-body">
<table class="table table-sm">
<tbody>
<tr>
<th>Table ARN</th>
<td class="text-muted small">{ data.TableARN }</td>
</tr>
<tr>
<th>Format</th>
<td>{ data.Format }</td>
</tr>
<tr>
<th>Table Location</th>
<td>
if data.TableLocation != "" {
<code class="small">{ data.TableLocation }</code>
} else {
<span class="text-muted">-</span>
}
</td>
</tr>
<tr>
<th>Metadata Location</th>
<td>
if data.MetadataLocation != "" {
<code class="small">{ data.MetadataLocation }</code>
} else {
<span class="text-muted">-</span>
}
</td>
</tr>
<tr>
<th>Created</th>
<td>{ data.CreatedAt.Format("2006-01-02 15:04") }</td>
</tr>
<tr>
<th>Modified</th>
<td>{ data.ModifiedAt.Format("2006-01-02 15:04") }</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-lg-6 mb-4">
<div class="card shadow h-100">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-sliders-h me-2"></i>Properties
</h6>
</div>
<div class="card-body">
<table class="table table-sm">
<thead>
<tr>
<th>Key</th>
<th>Value</th>
</tr>
</thead>
<tbody>
for _, prop := range data.Properties {
<tr>
<td>{ prop.Key }</td>
<td>{ prop.Value }</td>
</tr>
}
if len(data.Properties) == 0 {
<tr>
<td colspan="2" class="text-center text-muted">No properties available.</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12 mb-4">
<div class="card shadow">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-list me-2"></i>Schema
</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Type</th>
<th>Required</th>
</tr>
</thead>
<tbody>
for _, field := range data.SchemaFields {
<tr>
<td>{ fmt.Sprintf("%d", field.ID) }</td>
<td>{ field.Name }</td>
<td><code>{ field.Type }</code></td>
<td>
if field.Required {
<span class="badge bg-success">Yes</span>
} else {
<span class="badge bg-secondary">No</span>
}
</td>
</tr>
}
if len(data.SchemaFields) == 0 {
<tr>
<td colspan="4" class="text-center text-muted">No schema available.</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12 mb-4">
<div class="card shadow">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-layer-group me-2"></i>Partitions
</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Transform</th>
<th>Source ID</th>
<th>Field ID</th>
</tr>
</thead>
<tbody>
for _, field := range data.PartitionFields {
<tr>
<td>{ field.Name }</td>
<td>{ field.Transform }</td>
<td>{ fmt.Sprintf("%d", field.SourceID) }</td>
<td>{ fmt.Sprintf("%d", field.FieldID) }</td>
</tr>
}
if len(data.PartitionFields) == 0 {
<tr>
<td colspan="4" class="text-center text-muted">No partitions defined.</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-12">
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">
<i class="fas fa-history me-2"></i>Snapshot History
</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover">
<thead>
<tr>
<th>Snapshot ID</th>
<th>Timestamp</th>
<th>Operation</th>
<th>Manifest List</th>
</tr>
</thead>
<tbody>
for _, snapshot := range data.Snapshots {
<tr>
<td>{ fmt.Sprintf("%d", snapshot.SnapshotID) }</td>
<td>
if snapshot.Timestamp.IsZero() {
<span class="text-muted">-</span>
} else {
{ snapshot.Timestamp.Format("2006-01-02 15:04") }
}
</td>
<td>
if snapshot.Operation != "" {
{ snapshot.Operation }
} else {
<span class="text-muted">-</span>
}
</td>
<td>
if snapshot.ManifestList != "" {
<code class="small">{ snapshot.ManifestList }</code>
} else {
<span class="text-muted">-</span>
}
</td>
</tr>
}
if len(data.Snapshots) == 0 {
<tr>
<td colspan="4" class="text-center text-muted">No snapshots available.</td>
</tr>
}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="deleteIcebergTableModal" tabindex="-1" aria-labelledby="deleteIcebergTableModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="deleteIcebergTableModalLabel">
<i class="fas fa-exclamation-triangle me-2 text-warning"></i>Drop 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 drop the table <strong id="deleteIcebergTableName"></strong>?</p>
<div class="mb-3">
<label for="deleteIcebergTableVersion" class="form-label">Version Token (optional)</label>
<input type="text" class="form-control" id="deleteIcebergTableVersion" 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="deleteIcebergTable()">
<i class="fas fa-trash me-1"></i>Drop Table
</button>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
initIcebergTableDetails();
});
</script>
}

618
weed/admin/view/app/iceberg_table_details_templ.go

@ -0,0 +1,618 @@
// Code generated by templ - DO NOT EDIT.
// templ: version: v0.3.977
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 IcebergTableDetails(data dash.IcebergTableDetailsData) 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\"><nav aria-label=\"breadcrumb\"><ol class=\"breadcrumb mb-0\"><li class=\"breadcrumb-item\"><a href=\"/object-store/iceberg\"><i class=\"fas fa-snowflake me-1\"></i>Iceberg Catalog</a></li><li class=\"breadcrumb-item\"><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/iceberg/%s/namespaces", data.CatalogName)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 20, 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, 2, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(data.CatalogName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 21, Col: 25}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "</a></li><li class=\"breadcrumb-item\"><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(fmt.Sprintf("/object-store/iceberg/%s/namespaces/%s/tables", data.CatalogName, data.NamespaceName)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 25, Col: 129}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var5 string
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.NamespaceName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 26, Col: 27}
}
_, 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, 5, "</a></li><li class=\"breadcrumb-item active\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var6 string
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(data.TableName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 29, Col: 56}
}
_, 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, 6, "</li></ol></nav></h1><div class=\"btn-toolbar mb-2 mb-md-0\"><div class=\"btn-group me-2\"><button type=\"button\" class=\"btn btn-sm btn-danger iceberg-delete-table-btn\" data-bucket-arn=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var7 string
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(data.BucketARN)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 35, Col: 113}
}
_, 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, 7, "\" data-namespace=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var8 string
templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(data.NamespaceName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 35, Col: 151}
}
_, 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, 8, "\" data-table-name=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var9 string
templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(data.TableName)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 35, Col: 186}
}
_, 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, 9, "\"><i class=\"fas fa-trash me-1\"></i>Drop Table</button></div></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.MetadataError != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<div class=\"alert alert-warning\"><i class=\"fas fa-exclamation-triangle me-2\"></i>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var10 string
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(data.MetadataError)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 43, Col: 71}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "</div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "<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\">Data Files</div><div class=\"h5 mb-0 font-weight-bold text-gray-800\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.HasDataFileCount {
var templ_7745c5c3_Var11 string
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(formatNumber(data.DataFileCount))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 57, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "-")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "</div></div><div class=\"col-auto\"><i class=\"fas fa-copy 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 Size</div><div class=\"h5 mb-0 font-weight-bold text-gray-800\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.HasTotalSize {
var templ_7745c5c3_Var12 string
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(data.TotalSizeBytes))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 80, Col: 43}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "-")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "</div></div><div class=\"col-auto\"><i class=\"fas fa-database 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\">Snapshots</div><div class=\"h5 mb-0 font-weight-bold text-gray-800\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.HasSnapshotCount {
var templ_7745c5c3_Var13 string
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(formatNumber(int64(data.SnapshotCount)))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 103, Col: 50}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "-")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "</div></div><div class=\"col-auto\"><i class=\"fas fa-history fa-2x text-gray-300\"></i></div></div></div></div></div></div><div class=\"row\"><div class=\"col-lg-6 mb-4\"><div class=\"card shadow h-100\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\"><i class=\"fas fa-info-circle me-2\"></i>Table Metadata</h6></div><div class=\"card-body\"><table class=\"table table-sm\"><tbody><tr><th>Table ARN</th><td class=\"text-muted small\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var14 string
templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(data.TableARN)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 130, Col: 52}
}
_, 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, 19, "</td></tr><tr><th>Format</th><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(data.Format)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 134, Col: 25}
}
_, 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, 20, "</td></tr><tr><th>Table Location</th><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.TableLocation != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "<code class=\"small\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var16 string
templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(data.TableLocation)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 140, Col: 50}
}
_, 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, 22, "</code>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "<span class=\"text-muted\">-</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "</td></tr><tr><th>Metadata Location</th><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.MetadataLocation != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<code class=\"small\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var17 string
templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(data.MetadataLocation)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 150, Col: 53}
}
_, 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, 26, "</code>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<span class=\"text-muted\">-</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</td></tr><tr><th>Created</th><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var18 string
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(data.CreatedAt.Format("2006-01-02 15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 158, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "</td></tr><tr><th>Modified</th><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var19 string
templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(data.ModifiedAt.Format("2006-01-02 15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 162, Col: 56}
}
_, 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, 30, "</td></tr></tbody></table></div></div></div><div class=\"col-lg-6 mb-4\"><div class=\"card shadow h-100\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\"><i class=\"fas fa-sliders-h me-2\"></i>Properties</h6></div><div class=\"card-body\"><table class=\"table table-sm\"><thead><tr><th>Key</th><th>Value</th></tr></thead> <tbody>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, prop := range data.Properties {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "<tr><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var20 string
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(prop.Key)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 187, Col: 23}
}
_, 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, 32, "</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var21 string
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(prop.Value)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 188, Col: 25}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.Properties) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<tr><td colspan=\"2\" class=\"text-center text-muted\">No properties available.</td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</tbody></table></div></div></div></div><div class=\"row\"><div class=\"col-12 mb-4\"><div class=\"card shadow\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\"><i class=\"fas fa-list me-2\"></i>Schema</h6></div><div class=\"card-body\"><div class=\"table-responsive\"><table class=\"table table-hover\"><thead><tr><th>ID</th><th>Name</th><th>Type</th><th>Required</th></tr></thead> <tbody>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, field := range data.SchemaFields {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<tr><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var22 string
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", field.ID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 224, Col: 43}
}
_, 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, 37, "</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(field.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 225, Col: 26}
}
_, 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, 38, "</td><td><code>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(field.Type)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 226, Col: 32}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</code></td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if field.Required {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<span class=\"badge bg-success\">Yes</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<span class=\"badge bg-secondary\">No</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.SchemaFields) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "<tr><td colspan=\"4\" class=\"text-center text-muted\">No schema available.</td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</tbody></table></div></div></div></div></div><div class=\"row\"><div class=\"col-12 mb-4\"><div class=\"card shadow\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\"><i class=\"fas fa-layer-group me-2\"></i>Partitions</h6></div><div class=\"card-body\"><div class=\"table-responsive\"><table class=\"table table-hover\"><thead><tr><th>Name</th><th>Transform</th><th>Source ID</th><th>Field ID</th></tr></thead> <tbody>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, field := range data.PartitionFields {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<tr><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var25 string
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(field.Name)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 270, Col: 26}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
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
}
var templ_7745c5c3_Var26 string
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(field.Transform)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 271, Col: 31}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var27 string
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", field.SourceID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 272, Col: 49}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var28 string
templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", field.FieldID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 273, Col: 48}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.PartitionFields) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "<tr><td colspan=\"4\" class=\"text-center text-muted\">No partitions defined.</td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "</tbody></table></div></div></div></div></div><div class=\"row\"><div class=\"col-12\"><div class=\"card shadow mb-4\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\"><i class=\"fas fa-history me-2\"></i>Snapshot History</h6></div><div class=\"card-body\"><div class=\"table-responsive\"><table class=\"table table-hover\"><thead><tr><th>Snapshot ID</th><th>Timestamp</th><th>Operation</th><th>Manifest List</th></tr></thead> <tbody>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for _, snapshot := range data.Snapshots {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<tr><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var29 string
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", snapshot.SnapshotID))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 310, Col: 54}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if snapshot.Timestamp.IsZero() {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "<span class=\"text-muted\">-</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
var templ_7745c5c3_Var30 string
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(snapshot.Timestamp.Format("2006-01-02 15:04"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 315, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if snapshot.Operation != "" {
var templ_7745c5c3_Var31 string
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(snapshot.Operation)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 320, Col: 32}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "<span class=\"text-muted\">-</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</td><td>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if snapshot.ManifestList != "" {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "<code class=\"small\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var32 string
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(snapshot.ManifestList)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/iceberg_table_details.templ`, Line: 327, Col: 55}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "</code>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "<span class=\"text-muted\">-</span>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "</td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
if len(data.Snapshots) == 0 {
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<tr><td colspan=\"4\" class=\"text-center text-muted\">No snapshots available.</td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "</tbody></table></div></div></div></div></div><div class=\"modal fade\" id=\"deleteIcebergTableModal\" tabindex=\"-1\" aria-labelledby=\"deleteIcebergTableModalLabel\" aria-hidden=\"true\"><div class=\"modal-dialog\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\" id=\"deleteIcebergTableModalLabel\"><i class=\"fas fa-exclamation-triangle me-2 text-warning\"></i>Drop 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 drop the table <strong id=\"deleteIcebergTableName\"></strong>?</p><div class=\"mb-3\"><label for=\"deleteIcebergTableVersion\" class=\"form-label\">Version Token (optional)</label> <input type=\"text\" class=\"form-control\" id=\"deleteIcebergTableVersion\" 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=\"deleteIcebergTable()\"><i class=\"fas fa-trash me-1\"></i>Drop Table</button></div></div></div></div><script>\n\t\tdocument.addEventListener('DOMContentLoaded', function() {\n\t\t\tinitIcebergTableDetails();\n\t\t});\n\t</script>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
return nil
})
}
var _ = templruntime.GeneratedTemplate
Loading…
Cancel
Save