diff options
Diffstat (limited to 'weed/admin')
| -rw-r--r-- | weed/admin/dash/admin_server.go | 5 | ||||
| -rw-r--r-- | weed/admin/dash/mq_management.go | 74 | ||||
| -rw-r--r-- | weed/admin/dash/types.go | 3 | ||||
| -rw-r--r-- | weed/admin/dash/volume_management.go | 7 | ||||
| -rw-r--r-- | weed/admin/handlers/cluster_handlers.go | 3 | ||||
| -rw-r--r-- | weed/admin/handlers/file_browser_handlers.go | 73 | ||||
| -rw-r--r-- | weed/admin/view/app/maintenance_workers.templ | 24 | ||||
| -rw-r--r-- | weed/admin/view/app/maintenance_workers_templ.go | 12 | ||||
| -rw-r--r-- | weed/admin/view/app/topic_details.templ | 22 | ||||
| -rw-r--r-- | weed/admin/view/app/topic_details_templ.go | 437 |
10 files changed, 407 insertions, 253 deletions
diff --git a/weed/admin/dash/admin_server.go b/weed/admin/dash/admin_server.go index 3f135ee1b..4a1dd592f 100644 --- a/weed/admin/dash/admin_server.go +++ b/weed/admin/dash/admin_server.go @@ -1766,8 +1766,9 @@ func (s *AdminServer) UpdateTopicRetention(namespace, name string, enabled bool, }, // Preserve existing partition count - this is critical! PartitionCount: currentConfig.PartitionCount, - // Preserve existing record type if it exists - RecordType: currentConfig.RecordType, + // Preserve existing schema if it exists + MessageRecordType: currentConfig.MessageRecordType, + KeyColumns: currentConfig.KeyColumns, } // Update only the retention configuration diff --git a/weed/admin/dash/mq_management.go b/weed/admin/dash/mq_management.go index 5e513af1e..3fd4aed85 100644 --- a/weed/admin/dash/mq_management.go +++ b/weed/admin/dash/mq_management.go @@ -181,7 +181,6 @@ func (s *AdminServer) GetTopicDetails(namespace, topicName string) (*TopicDetail Namespace: namespace, Name: topicName, Partitions: []PartitionInfo{}, - Schema: []SchemaFieldInfo{}, Publishers: []PublisherInfo{}, Subscribers: []TopicSubscriberInfo{}, ConsumerGroupOffsets: []ConsumerGroupOffsetInfo{}, @@ -214,9 +213,33 @@ func (s *AdminServer) GetTopicDetails(namespace, topicName string) (*TopicDetail } } - // Process schema from RecordType - if configResp.RecordType != nil { - topicDetails.Schema = convertRecordTypeToSchemaFields(configResp.RecordType) + // Process flat schema format + if configResp.MessageRecordType != nil { + for _, field := range configResp.MessageRecordType.Fields { + isKey := false + for _, keyCol := range configResp.KeyColumns { + if field.Name == keyCol { + isKey = true + break + } + } + + fieldType := "UNKNOWN" + if field.Type != nil && field.Type.Kind != nil { + fieldType = getFieldTypeName(field.Type) + } + + schemaField := SchemaFieldInfo{ + Name: field.Name, + Type: fieldType, + } + + if isKey { + topicDetails.KeySchema = append(topicDetails.KeySchema, schemaField) + } else { + topicDetails.ValueSchema = append(topicDetails.ValueSchema, schemaField) + } + } } // Get publishers information @@ -613,3 +636,46 @@ func convertTopicRetention(retention *mq_pb.TopicRetention) TopicRetentionInfo { DisplayUnit: displayUnit, } } + +// getFieldTypeName converts a schema_pb.Type to a human-readable type name +func getFieldTypeName(fieldType *schema_pb.Type) string { + if fieldType.Kind == nil { + return "UNKNOWN" + } + + switch kind := fieldType.Kind.(type) { + case *schema_pb.Type_ScalarType: + switch kind.ScalarType { + case schema_pb.ScalarType_BOOL: + return "BOOLEAN" + case schema_pb.ScalarType_INT32: + return "INT32" + case schema_pb.ScalarType_INT64: + return "INT64" + case schema_pb.ScalarType_FLOAT: + return "FLOAT" + case schema_pb.ScalarType_DOUBLE: + return "DOUBLE" + case schema_pb.ScalarType_BYTES: + return "BYTES" + case schema_pb.ScalarType_STRING: + return "STRING" + case schema_pb.ScalarType_TIMESTAMP: + return "TIMESTAMP" + case schema_pb.ScalarType_DATE: + return "DATE" + case schema_pb.ScalarType_TIME: + return "TIME" + case schema_pb.ScalarType_DECIMAL: + return "DECIMAL" + default: + return "SCALAR" + } + case *schema_pb.Type_ListType: + return "LIST" + case *schema_pb.Type_RecordType: + return "RECORD" + default: + return "UNKNOWN" + } +} diff --git a/weed/admin/dash/types.go b/weed/admin/dash/types.go index 18c46a48d..8b793bdbd 100644 --- a/weed/admin/dash/types.go +++ b/weed/admin/dash/types.go @@ -404,7 +404,8 @@ type TopicDetailsData struct { Namespace string `json:"namespace"` Name string `json:"name"` Partitions []PartitionInfo `json:"partitions"` - Schema []SchemaFieldInfo `json:"schema"` + KeySchema []SchemaFieldInfo `json:"key_schema"` // Schema fields for keys + ValueSchema []SchemaFieldInfo `json:"value_schema"` // Schema fields for values Publishers []PublisherInfo `json:"publishers"` Subscribers []TopicSubscriberInfo `json:"subscribers"` ConsumerGroupOffsets []ConsumerGroupOffsetInfo `json:"consumer_group_offsets"` diff --git a/weed/admin/dash/volume_management.go b/weed/admin/dash/volume_management.go index 38b1257a4..c0be958a9 100644 --- a/weed/admin/dash/volume_management.go +++ b/weed/admin/dash/volume_management.go @@ -3,6 +3,7 @@ package dash import ( "context" "fmt" + "math" "sort" "time" @@ -392,8 +393,14 @@ func (s *AdminServer) GetVolumeDetails(volumeID int, server string) (*VolumeDeta // VacuumVolume performs a vacuum operation on a specific volume func (s *AdminServer) VacuumVolume(volumeID int, server string) error { + // Validate volumeID range before converting to uint32 + if volumeID < 0 || uint64(volumeID) > math.MaxUint32 { + return fmt.Errorf("volume ID out of range: %d", volumeID) + } return s.WithMasterClient(func(client master_pb.SeaweedClient) error { _, err := client.VacuumVolume(context.Background(), &master_pb.VacuumVolumeRequest{ + // lgtm[go/incorrect-integer-conversion] + // Safe conversion: volumeID has been validated to be in range [0, 0xFFFFFFFF] above VolumeId: uint32(volumeID), GarbageThreshold: 0.0001, // A very low threshold to ensure all garbage is collected Collection: "", // Empty for all collections diff --git a/weed/admin/handlers/cluster_handlers.go b/weed/admin/handlers/cluster_handlers.go index ee6417954..1a58e919d 100644 --- a/weed/admin/handlers/cluster_handlers.go +++ b/weed/admin/handlers/cluster_handlers.go @@ -1,6 +1,7 @@ package handlers import ( + "math" "net/http" "strconv" @@ -256,7 +257,7 @@ func (h *ClusterHandlers) ShowEcVolumeDetails(c *gin.Context) { } // Check that volumeID is within uint32 range - if volumeID < 0 { + if volumeID < 0 || uint64(volumeID) > math.MaxUint32 { c.JSON(http.StatusBadRequest, gin.H{"error": "Volume ID out of range"}) return } diff --git a/weed/admin/handlers/file_browser_handlers.go b/weed/admin/handlers/file_browser_handlers.go index f19aa3e1b..a0427e39f 100644 --- a/weed/admin/handlers/file_browser_handlers.go +++ b/weed/admin/handlers/file_browser_handlers.go @@ -359,6 +359,9 @@ func (h *FileBrowserHandlers) uploadFileToFiler(filePath string, fileHeader *mul // Send request client := &http.Client{Timeout: 60 * time.Second} // Increased timeout for larger files + // lgtm[go/ssrf] + // Safe: filerAddress validated by validateFilerAddress() to match configured filer + // Safe: cleanFilePath validated and cleaned by validateAndCleanFilePath() to prevent path traversal resp, err := client.Do(req) if err != nil { return fmt.Errorf("failed to upload file: %w", err) @@ -380,6 +383,12 @@ func (h *FileBrowserHandlers) validateFilerAddress(address string) error { return fmt.Errorf("filer address cannot be empty") } + // CRITICAL: Only allow the configured filer address to prevent SSRF + configuredFiler := h.adminServer.GetFilerAddress() + if address != configuredFiler { + return fmt.Errorf("address does not match configured filer: got %s, expected %s", address, configuredFiler) + } + // Parse the address to validate it's a proper host:port format host, port, err := net.SplitHostPort(address) if err != nil { @@ -405,18 +414,6 @@ func (h *FileBrowserHandlers) validateFilerAddress(address string) error { return fmt.Errorf("port number must be between 1 and 65535") } - // Additional security: prevent private network access unless explicitly allowed - // This helps prevent SSRF attacks to internal services - ip := net.ParseIP(host) - if ip != nil { - // Check for localhost, private networks, and other dangerous addresses - if ip.IsLoopback() || ip.IsPrivate() || ip.IsUnspecified() { - // Only allow if it's the configured filer (trusted) - // In production, you might want to be more restrictive - glog.V(2).Infof("Allowing access to private/local address: %s (configured filer)", address) - } - } - return nil } @@ -565,29 +562,38 @@ func (h *FileBrowserHandlers) ViewFile(c *gin.Context) { // Get file content from filer filerAddress := h.adminServer.GetFilerAddress() if filerAddress != "" { - cleanFilePath, err := h.validateAndCleanFilePath(filePath) - if err == nil { - fileURL := fmt.Sprintf("http://%s%s", filerAddress, cleanFilePath) - - client := &http.Client{Timeout: 30 * time.Second} - resp, err := client.Get(fileURL) - if err == nil && resp.StatusCode == http.StatusOK { - defer resp.Body.Close() - contentBytes, err := io.ReadAll(resp.Body) - if err == nil { - content = string(contentBytes) - viewable = true + // Validate filer address to prevent SSRF + if err := h.validateFilerAddress(filerAddress); err != nil { + viewable = false + reason = "Invalid filer address configuration" + } else { + cleanFilePath, err := h.validateAndCleanFilePath(filePath) + if err == nil { + fileURL := fmt.Sprintf("http://%s%s", filerAddress, cleanFilePath) + + client := &http.Client{Timeout: 30 * time.Second} + // lgtm[go/ssrf] + // Safe: filerAddress validated by validateFilerAddress() to match configured filer + // Safe: cleanFilePath validated and cleaned by validateAndCleanFilePath() to prevent path traversal + resp, err := client.Get(fileURL) + if err == nil && resp.StatusCode == http.StatusOK { + defer resp.Body.Close() + contentBytes, err := io.ReadAll(resp.Body) + if err == nil { + content = string(contentBytes) + viewable = true + } else { + viewable = false + reason = "Failed to read file content" + } } else { viewable = false - reason = "Failed to read file content" + reason = "Failed to fetch file from filer" } } else { viewable = false - reason = "Failed to fetch file from filer" + reason = "Invalid file path" } - } else { - viewable = false - reason = "Invalid file path" } } else { viewable = false @@ -876,6 +882,12 @@ func (h *FileBrowserHandlers) isLikelyTextFile(filePath string, maxCheckSize int return false } + // Validate filer address to prevent SSRF + if err := h.validateFilerAddress(filerAddress); err != nil { + glog.Errorf("Invalid filer address: %v", err) + return false + } + cleanFilePath, err := h.validateAndCleanFilePath(filePath) if err != nil { return false @@ -884,6 +896,9 @@ func (h *FileBrowserHandlers) isLikelyTextFile(filePath string, maxCheckSize int fileURL := fmt.Sprintf("http://%s%s", filerAddress, cleanFilePath) client := &http.Client{Timeout: 10 * time.Second} + // lgtm[go/ssrf] + // Safe: filerAddress validated by validateFilerAddress() to match configured filer + // Safe: cleanFilePath validated and cleaned by validateAndCleanFilePath() to prevent path traversal resp, err := client.Get(fileURL) if err != nil || resp.StatusCode != http.StatusOK { return false diff --git a/weed/admin/view/app/maintenance_workers.templ b/weed/admin/view/app/maintenance_workers.templ index 37e1cb985..00748e550 100644 --- a/weed/admin/view/app/maintenance_workers.templ +++ b/weed/admin/view/app/maintenance_workers.templ @@ -115,11 +115,11 @@ templ MaintenanceWorkers(data *dash.MaintenanceWorkersData) { <div class="text-center py-4"> <i class="fas fa-users fa-3x text-gray-300 mb-3"></i> <h5 class="text-gray-600">No Workers Found</h5> - <p class="text-muted">No maintenance workers are currently registered.</p> - <div class="alert alert-info mt-3"> - <strong>💡 Tip:</strong> To start a worker, run: - <br><code>weed worker -admin=<admin_server> -capabilities=vacuum,ec,replication</code> - </div> + <p class="text-muted">No maintenance workers are currently registered.</p> + <div class="alert alert-info mt-3"> + <strong>Tip:</strong> To start a worker, run: + <br><code>weed worker -admin=<admin_server> -capabilities=vacuum,ec,replication</code> + </div> </div> } else { <div class="table-responsive"> @@ -180,13 +180,13 @@ templ MaintenanceWorkers(data *dash.MaintenanceWorkersData) { <td> { fmt.Sprintf("%d", len(worker.CurrentTasks)) } </td> - <td> - <small> - <div>✅ { fmt.Sprintf("%d", worker.Performance.TasksCompleted) }</div> - <div>❌ { fmt.Sprintf("%d", worker.Performance.TasksFailed) }</div> - <div>📊 { fmt.Sprintf("%.1f%%", worker.Performance.SuccessRate) }</div> - </small> - </td> + <td> + <small> + <div>Completed: { fmt.Sprintf("%d", worker.Performance.TasksCompleted) }</div> + <div>Failed: { fmt.Sprintf("%d", worker.Performance.TasksFailed) }</div> + <div>Success Rate: { fmt.Sprintf("%.1f%%", worker.Performance.SuccessRate) }</div> + </small> + </td> <td> if time.Since(worker.Worker.LastHeartbeat) < 2*time.Minute { <span class="text-success"> diff --git a/weed/admin/view/app/maintenance_workers_templ.go b/weed/admin/view/app/maintenance_workers_templ.go index 2be85bbc6..2d3b85a39 100644 --- a/weed/admin/view/app/maintenance_workers_templ.go +++ b/weed/admin/view/app/maintenance_workers_templ.go @@ -105,7 +105,7 @@ func MaintenanceWorkers(data *dash.MaintenanceWorkersData) templ.Component { return templ_7745c5c3_Err } if len(data.Workers) == 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"text-center py-4\"><i class=\"fas fa-users fa-3x text-gray-300 mb-3\"></i><h5 class=\"text-gray-600\">No Workers Found</h5><p class=\"text-muted\">No maintenance workers are currently registered.</p><div class=\"alert alert-info mt-3\"><strong>💡 Tip:</strong> To start a worker, run:<br><code>weed worker -admin=<admin_server> -capabilities=vacuum,ec,replication</code></div></div>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "<div class=\"text-center py-4\"><i class=\"fas fa-users fa-3x text-gray-300 mb-3\"></i><h5 class=\"text-gray-600\">No Workers Found</h5><p class=\"text-muted\">No maintenance workers are currently registered.</p><div class=\"alert alert-info mt-3\"><strong>Tip:</strong> To start a worker, run:<br><code>weed worker -admin=<admin_server> -capabilities=vacuum,ec,replication</code></div></div>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -264,20 +264,20 @@ func MaintenanceWorkers(data *dash.MaintenanceWorkersData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</td><td><small><div>✅ ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "</td><td><small><div>Completed: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", worker.Performance.TasksCompleted)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/maintenance_workers.templ`, Line: 185, Col: 119} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/maintenance_workers.templ`, Line: 185, Col: 122} } _, 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, 27, "</div><div>❌ ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "</div><div>Failed: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -290,14 +290,14 @@ func MaintenanceWorkers(data *dash.MaintenanceWorkersData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</div><div>📊 ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "</div><div>Success Rate: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f%%", worker.Performance.SuccessRate)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/maintenance_workers.templ`, Line: 187, Col: 121} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/maintenance_workers.templ`, Line: 187, Col: 126} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { diff --git a/weed/admin/view/app/topic_details.templ b/weed/admin/view/app/topic_details.templ index f82ba58a8..03a8af488 100644 --- a/weed/admin/view/app/topic_details.templ +++ b/weed/admin/view/app/topic_details.templ @@ -36,7 +36,7 @@ templ TopicDetails(data dash.TopicDetailsData) { <div class="card text-center"> <div class="card-body"> <h5 class="card-title">Schema Fields</h5> - <h3 class="text-info">{fmt.Sprintf("%d", len(data.Schema))}</h3> + <h3 class="text-info">{fmt.Sprintf("%d", len(data.KeySchema) + len(data.ValueSchema))}</h3> </div> </div> </div> @@ -152,7 +152,7 @@ templ TopicDetails(data dash.TopicDetailsData) { <h5 class="mb-0">Schema Definition</h5> </div> <div class="card-body"> - if len(data.Schema) == 0 { + if len(data.KeySchema) == 0 && len(data.ValueSchema) == 0 { <p class="text-muted">No schema information available</p> } else { <div class="table-responsive"> @@ -162,10 +162,11 @@ templ TopicDetails(data dash.TopicDetailsData) { <th>Field</th> <th>Type</th> <th>Required</th> + <th>Schema Part</th> </tr> </thead> <tbody> - for _, field := range data.Schema { + for _, field := range data.KeySchema { <tr> <td><code>{field.Name}</code></td> <td><span class="badge bg-secondary">{field.Type}</span></td> @@ -176,6 +177,21 @@ templ TopicDetails(data dash.TopicDetailsData) { <i class="fas fa-times text-muted"></i> } </td> + <td><span class="badge bg-primary">Key</span></td> + </tr> + } + for _, field := range data.ValueSchema { + <tr> + <td><code>{field.Name}</code></td> + <td><span class="badge bg-secondary">{field.Type}</span></td> + <td> + if field.Required { + <i class="fas fa-check text-success"></i> + } else { + <i class="fas fa-times text-muted"></i> + } + </td> + <td><span class="badge bg-info">Value</span></td> </tr> } </tbody> diff --git a/weed/admin/view/app/topic_details_templ.go b/weed/admin/view/app/topic_details_templ.go index 7d8394380..cdc7d50a2 100644 --- a/weed/admin/view/app/topic_details_templ.go +++ b/weed/admin/view/app/topic_details_templ.go @@ -90,9 +90,9 @@ func TopicDetails(data dash.TopicDetailsData) templ.Component { return templ_7745c5c3_Err } var templ_7745c5c3_Var6 string - templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(data.Schema))) + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(data.KeySchema)+len(data.ValueSchema))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 39, Col: 90} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 39, Col: 117} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -275,17 +275,17 @@ func TopicDetails(data dash.TopicDetailsData) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - if len(data.Schema) == 0 { + if len(data.KeySchema) == 0 && len(data.ValueSchema) == 0 { templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "<p class=\"text-muted\">No schema information available</p>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<div class=\"table-responsive\"><table class=\"table table-sm\"><thead><tr><th>Field</th><th>Type</th><th>Required</th></tr></thead> <tbody>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "<div class=\"table-responsive\"><table class=\"table table-sm\"><thead><tr><th>Field</th><th>Type</th><th>Required</th><th>Schema Part</th></tr></thead> <tbody>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - for _, field := range data.Schema { + for _, field := range data.KeySchema { templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<tr><td><code>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err @@ -293,7 +293,7 @@ func TopicDetails(data dash.TopicDetailsData) templ.Component { var templ_7745c5c3_Var18 string templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(field.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 170, Col: 77} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 171, Col: 77} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -306,7 +306,7 @@ func TopicDetails(data dash.TopicDetailsData) templ.Component { var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(field.Type) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 171, Col: 104} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 172, Col: 104} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { @@ -327,618 +327,665 @@ func TopicDetails(data dash.TopicDetailsData) templ.Component { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</td></tr>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</td><td><span class=\"badge bg-primary\">Key</span></td></tr>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "</tbody></table></div>") + for _, field := range data.ValueSchema { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "<tr><td><code>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var20 string + templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(field.Name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 185, Col: 77} + } + _, 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, 33, "</code></td><td><span class=\"badge bg-secondary\">") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(field.Type) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 186, Col: 104} + } + _, 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, 34, "</span></td><td>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if field.Required { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<i class=\"fas fa-check text-success\"></i>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<i class=\"fas fa-times text-muted\"></i>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</td><td><span class=\"badge bg-info\">Value</span></td></tr>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</tbody></table></div>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "</div></div></div></div><!-- Partitions Table --><div class=\"card\"><div class=\"card-header d-flex justify-content-between align-items-center\"><h5 class=\"mb-0\">Partitions</h5><div><button class=\"btn btn-sm btn-outline-secondary\" onclick=\"exportPartitionsCSV()\"><i class=\"fas fa-download me-1\"></i>Export CSV</button></div></div><div class=\"card-body\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</div></div></div></div><!-- Partitions Table --><div class=\"card\"><div class=\"card-header d-flex justify-content-between align-items-center\"><h5 class=\"mb-0\">Partitions</h5><div><button class=\"btn btn-sm btn-outline-secondary\" onclick=\"exportPartitionsCSV()\"><i class=\"fas fa-download me-1\"></i>Export CSV</button></div></div><div class=\"card-body\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if len(data.Partitions) == 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "<div class=\"text-center py-4\"><i class=\"fas fa-server fa-3x text-muted mb-3\"></i><h5>No Partitions Found</h5><p class=\"text-muted\">No partitions are configured for this topic.</p></div>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<div class=\"text-center py-4\"><i class=\"fas fa-server fa-3x text-muted mb-3\"></i><h5>No Partitions Found</h5><p class=\"text-muted\">No partitions are configured for this topic.</p></div>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<div class=\"table-responsive\"><table class=\"table table-striped\" id=\"partitionsTable\"><thead><tr><th>Partition ID</th><th>Leader Broker</th><th>Follower Broker</th><th>Messages</th><th>Size</th><th>Last Data Time</th><th>Created</th></tr></thead> <tbody>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<div class=\"table-responsive\"><table class=\"table table-striped\" id=\"partitionsTable\"><thead><tr><th>Partition ID</th><th>Leader Broker</th><th>Follower Broker</th><th>Messages</th><th>Size</th><th>Last Data Time</th><th>Created</th></tr></thead> <tbody>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, partition := range data.Partitions { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "<tr><td><span class=\"badge bg-primary\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "<tr><td><span class=\"badge bg-primary\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var20 string - templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", partition.ID)) + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", partition.ID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 225, Col: 115} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 241, Col: 115} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) + _, 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, "</span></td><td><strong>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</span></td><td><strong>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var21 string - templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(partition.LeaderBroker) + var templ_7745c5c3_Var23 string + templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(partition.LeaderBroker) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 228, Col: 83} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 244, Col: 83} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) + _, 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, "</strong></td><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "</strong></td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if partition.FollowerBroker != "" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<span class=\"text-muted\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<span class=\"text-muted\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var22 string - templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(partition.FollowerBroker) + var templ_7745c5c3_Var24 string + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(partition.FollowerBroker) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 232, Col: 106} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 248, Col: 106} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + _, 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, 40, "</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "<span class=\"text-muted\">None</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<span class=\"text-muted\">None</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "</td><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var23 string - templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", partition.MessageCount)) + var templ_7745c5c3_Var25 string + templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", partition.MessageCount)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 237, Col: 94} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 253, Col: 94} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) + _, 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, 43, "</td><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "</td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var24 string - templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(util.BytesToHumanReadable(uint64(partition.TotalSize))) + var templ_7745c5c3_Var26 string + templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(util.BytesToHumanReadable(uint64(partition.TotalSize))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 238, Col: 107} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 254, Col: 107} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) + _, 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, 44, "</td><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if !partition.LastDataTime.IsZero() { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 45, "<span class=\"text-muted\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "<span class=\"text-muted\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var25 string - templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(partition.LastDataTime.Format("2006-01-02 15:04:05")) + var templ_7745c5c3_Var27 string + templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(partition.LastDataTime.Format("2006-01-02 15:04:05")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 241, Col: 134} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 257, Col: 134} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) + _, 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, 46, "</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "<span class=\"text-muted\">Never</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<span class=\"text-muted\">Never</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "</td><td><span class=\"text-muted\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "</td><td><span class=\"text-muted\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var26 string - templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(partition.CreatedAt.Format("2006-01-02 15:04:05")) + var templ_7745c5c3_Var28 string + templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(partition.CreatedAt.Format("2006-01-02 15:04:05")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 247, Col: 127} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 263, Col: 127} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) + _, 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, "</span></td></tr>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "</span></td></tr>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "</tbody></table></div>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "</tbody></table></div>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "</div></div><!-- Publishers and Subscribers --><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"card\"><div class=\"card-header\"><h5 class=\"mb-0\">Active Publishers <span class=\"badge bg-success\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</div></div><!-- Publishers and Subscribers --><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"card\"><div class=\"card-header\"><h5 class=\"mb-0\">Active Publishers <span class=\"badge bg-success\">") 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", len(data.Publishers))) + var templ_7745c5c3_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(data.Publishers))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 263, Col: 138} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 279, Col: 138} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) + _, 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, 52, "</span></h5></div><div class=\"card-body\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "</span></h5></div><div class=\"card-body\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if len(data.Publishers) == 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "<div class=\"alert alert-info mb-0\"><i class=\"fas fa-info-circle\"></i> No active publishers found for this topic.</div>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "<div class=\"alert alert-info mb-0\"><i class=\"fas fa-info-circle\"></i> No active publishers found for this topic.</div>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "<div class=\"table-responsive\"><table class=\"table table-sm\"><thead><tr><th>Publisher</th><th>Partition</th><th>Broker</th><th>Status</th><th>Published</th><th>Acknowledged</th><th>Last Seen</th></tr></thead> <tbody>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "<div class=\"table-responsive\"><table class=\"table table-sm\"><thead><tr><th>Publisher</th><th>Partition</th><th>Broker</th><th>Status</th><th>Published</th><th>Acknowledged</th><th>Last Seen</th></tr></thead> <tbody>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, publisher := range data.Publishers { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "<tr><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<tr><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var28 string - templ_7745c5c3_Var28, templ_7745c5c3_Err = templ.JoinStringErrs(publisher.PublisherName) + var templ_7745c5c3_Var30 string + templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(publisher.PublisherName) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 287, Col: 84} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 303, Col: 84} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) + _, 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, 56, "</td><td><span class=\"badge bg-primary\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "</td><td><span class=\"badge bg-primary\">") 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", publisher.PartitionID)) + var templ_7745c5c3_Var31 string + templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", publisher.PartitionID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 288, Col: 132} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 304, Col: 132} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 57, "</span></td><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "</span></td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var30 string - templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(publisher.Broker) + var templ_7745c5c3_Var32 string + templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(publisher.Broker) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 289, Col: 77} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 305, Col: 77} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) + _, 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, 58, "</td><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "</td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if publisher.IsActive { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "<span class=\"badge bg-success\">Active</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "<span class=\"badge bg-success\">Active</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "<span class=\"badge bg-secondary\">Inactive</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "<span class=\"badge bg-secondary\">Inactive</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "</td><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if publisher.LastPublishedOffset > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "<span class=\"text-muted\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<span class=\"text-muted\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var31 string - templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", publisher.LastPublishedOffset)) + var templ_7745c5c3_Var33 string + templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", publisher.LastPublishedOffset)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 299, Col: 138} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 315, Col: 138} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "<span class=\"text-muted\">-</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "<span class=\"text-muted\">-</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "</td><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if publisher.LastAckedOffset > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "<span class=\"text-muted\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "<span class=\"text-muted\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var32 string - templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", publisher.LastAckedOffset)) + var templ_7745c5c3_Var34 string + templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", publisher.LastAckedOffset)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 306, Col: 134} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 322, Col: 134} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 68, "<span class=\"text-muted\">-</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "<span class=\"text-muted\">-</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 69, "</td><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "</td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if !publisher.LastSeenTime.IsZero() { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 70, "<span class=\"text-muted\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "<span class=\"text-muted\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var33 string - templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(publisher.LastSeenTime.Format("15:04:05")) + var templ_7745c5c3_Var35 string + templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(publisher.LastSeenTime.Format("15:04:05")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 313, Col: 131} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 329, Col: 131} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 71, "</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 72, "<span class=\"text-muted\">-</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "<span class=\"text-muted\">-</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 73, "</td></tr>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "</td></tr>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 74, "</tbody></table></div>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "</tbody></table></div>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 75, "</div></div></div></div><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"card\"><div class=\"card-header\"><h5 class=\"mb-0\">Active Subscribers <span class=\"badge bg-info\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "</div></div></div></div><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"card\"><div class=\"card-header\"><h5 class=\"mb-0\">Active Subscribers <span class=\"badge bg-info\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var34 string - templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(data.Subscribers))) + var templ_7745c5c3_Var36 string + templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(data.Subscribers))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 333, Col: 137} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 349, Col: 137} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "</span></h5></div><div class=\"card-body\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "</span></h5></div><div class=\"card-body\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if len(data.Subscribers) == 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "<div class=\"alert alert-info mb-0\"><i class=\"fas fa-info-circle\"></i> No active subscribers found for this topic.</div>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "<div class=\"alert alert-info mb-0\"><i class=\"fas fa-info-circle\"></i> No active subscribers found for this topic.</div>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "<div class=\"table-responsive\"><table class=\"table table-sm\"><thead><tr><th>Consumer Group</th><th>Consumer ID</th><th>Partition</th><th>Broker</th><th>Status</th><th>Received</th><th>Acknowledged</th><th>Last Seen</th></tr></thead> <tbody>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "<div class=\"table-responsive\"><table class=\"table table-sm\"><thead><tr><th>Consumer Group</th><th>Consumer ID</th><th>Partition</th><th>Broker</th><th>Status</th><th>Received</th><th>Acknowledged</th><th>Last Seen</th></tr></thead> <tbody>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, subscriber := range data.Subscribers { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, "<tr><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "<tr><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var35 string - templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(subscriber.ConsumerGroup) + var templ_7745c5c3_Var37 string + templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(subscriber.ConsumerGroup) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 358, Col: 85} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 374, Col: 85} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, "</td><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "</td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var36 string - templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(subscriber.ConsumerID) + var templ_7745c5c3_Var38 string + templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(subscriber.ConsumerID) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 359, Col: 82} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 375, Col: 82} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "</td><td><span class=\"badge bg-primary\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "</td><td><span class=\"badge bg-primary\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var37 string - templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", subscriber.PartitionID)) + var templ_7745c5c3_Var39 string + templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", subscriber.PartitionID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 360, Col: 133} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 376, Col: 133} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "</span></td><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "</span></td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var38 string - templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(subscriber.Broker) + var templ_7745c5c3_Var40 string + templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(subscriber.Broker) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 361, Col: 78} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 377, Col: 78} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, "</td><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "</td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if subscriber.IsActive { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "<span class=\"badge bg-success\">Active</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "<span class=\"badge bg-success\">Active</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "<span class=\"badge bg-secondary\">Inactive</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "<span class=\"badge bg-secondary\">Inactive</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "</td><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "</td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if subscriber.LastReceivedOffset > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "<span class=\"text-muted\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "<span class=\"text-muted\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var39 string - templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", subscriber.LastReceivedOffset)) + var templ_7745c5c3_Var41 string + templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", subscriber.LastReceivedOffset)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 371, Col: 138} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 387, Col: 138} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "<span class=\"text-muted\">-</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "<span class=\"text-muted\">-</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "</td><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "</td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if subscriber.CurrentOffset > 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "<span class=\"text-muted\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "<span class=\"text-muted\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var40 string - templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", subscriber.CurrentOffset)) + var templ_7745c5c3_Var42 string + templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", subscriber.CurrentOffset)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 378, Col: 133} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 394, Col: 133} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "<span class=\"text-muted\">-</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "<span class=\"text-muted\">-</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "</td><td>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "</td><td>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if !subscriber.LastSeenTime.IsZero() { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "<span class=\"text-muted\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "<span class=\"text-muted\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var41 string - templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(subscriber.LastSeenTime.Format("15:04:05")) + var templ_7745c5c3_Var43 string + templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(subscriber.LastSeenTime.Format("15:04:05")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 385, Col: 132} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 401, Col: 132} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "<span class=\"text-muted\">-</span>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "<span class=\"text-muted\">-</span>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "</td></tr>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "</td></tr>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "</tbody></table></div>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "</tbody></table></div>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "</div></div></div></div><!-- Consumer Group Offsets --><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"card\"><div class=\"card-header\"><h5 class=\"mb-0\">Consumer Group Offsets <span class=\"badge bg-warning\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "</div></div></div></div><!-- Consumer Group Offsets --><div class=\"row mb-4\"><div class=\"col-12\"><div class=\"card\"><div class=\"card-header\"><h5 class=\"mb-0\">Consumer Group Offsets <span class=\"badge bg-warning\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var42 string - templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(data.ConsumerGroupOffsets))) + var templ_7745c5c3_Var44 string + templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", len(data.ConsumerGroupOffsets))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 406, Col: 153} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 422, Col: 153} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "</span></h5></div><div class=\"card-body\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "</span></h5></div><div class=\"card-body\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if len(data.ConsumerGroupOffsets) == 0 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "<div class=\"alert alert-info mb-0\"><i class=\"fas fa-info-circle\"></i> No consumer group offsets found for this topic.</div>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "<div class=\"alert alert-info mb-0\"><i class=\"fas fa-info-circle\"></i> No consumer group offsets found for this topic.</div>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "<div class=\"table-responsive\"><table class=\"table table-sm\"><thead><tr><th>Consumer Group</th><th>Partition</th><th>Offset</th><th>Last Updated</th></tr></thead> <tbody>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "<div class=\"table-responsive\"><table class=\"table table-sm\"><thead><tr><th>Consumer Group</th><th>Partition</th><th>Offset</th><th>Last Updated</th></tr></thead> <tbody>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } for _, offset := range data.ConsumerGroupOffsets { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "<tr><td><span class=\"badge bg-secondary\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "<tr><td><span class=\"badge bg-secondary\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var43 string - templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(offset.ConsumerGroup) + var templ_7745c5c3_Var45 string + templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(offset.ConsumerGroup) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 428, Col: 114} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 444, Col: 114} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "</span></td><td><span class=\"badge bg-primary\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 111, "</span></td><td><span class=\"badge bg-primary\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var44 string - templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", offset.PartitionID)) + var templ_7745c5c3_Var46 string + templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", offset.PartitionID)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 431, Col: 129} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 447, Col: 129} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "</span></td><td><strong>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 112, "</span></td><td><strong>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var45 string - templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", offset.Offset)) + var templ_7745c5c3_Var47 string + templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", offset.Offset)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 434, Col: 101} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 450, Col: 101} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 107, "</strong></td><td><span class=\"text-muted\">") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 113, "</strong></td><td><span class=\"text-muted\">") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var46 string - templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(offset.LastUpdated.Format("2006-01-02 15:04:05")) + var templ_7745c5c3_Var48 string + templ_7745c5c3_Var48, templ_7745c5c3_Err = templ.JoinStringErrs(offset.LastUpdated.Format("2006-01-02 15:04:05")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 437, Col: 134} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/topic_details.templ`, Line: 453, Col: 134} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46)) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48)) if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 108, "</span></td></tr>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 114, "</span></td></tr>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 109, "</tbody></table></div>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 115, "</tbody></table></div>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 110, "</div></div></div></div></div></div></div><script>\n function exportPartitionsCSV() {\n const table = document.getElementById('partitionsTable');\n if (!table) return;\n \n let csv = 'Partition ID,Leader Broker,Follower Broker,Messages,Size,Last Data Time,Created\\n';\n \n const rows = table.querySelectorAll('tbody tr');\n rows.forEach(row => {\n const cells = row.querySelectorAll('td');\n if (cells.length >= 7) {\n const rowData = [\n cells[0].querySelector('.badge')?.textContent || '',\n cells[1].querySelector('strong')?.textContent || '',\n cells[2].textContent || '',\n cells[3].textContent || '',\n cells[4].textContent || '',\n cells[5].querySelector('span')?.textContent || '',\n cells[6].querySelector('span')?.textContent || ''\n ];\n csv += rowData.map(field => `\"${field.replace(/\"/g, '\"\"')}\"`).join(',') + '\\n';\n }\n });\n \n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const link = document.createElement('a');\n const url = URL.createObjectURL(blob);\n link.setAttribute('href', url);\n link.setAttribute('download', 'topic_partitions.csv');\n link.style.visibility = 'hidden';\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n }\n\n // Edit retention functions\n function showEditRetentionModal() {\n const modal = new bootstrap.Modal(document.getElementById('editRetentionModal'));\n \n // Get current retention values from the page\n const currentEnabled = document.querySelector('dd .badge.bg-success') !== null;\n const currentDurationElement = document.querySelector('dd .text-success');\n \n let currentValue = 7;\n let currentUnit = 'days';\n \n if (currentEnabled && currentDurationElement) {\n const durationText = currentDurationElement.textContent.trim();\n const parts = durationText.split(' ');\n if (parts.length >= 2) {\n currentValue = parseInt(parts[0]) || 7;\n currentUnit = parts[1].toLowerCase();\n // Handle plural forms\n if (currentUnit.endsWith('s')) {\n currentUnit = currentUnit.slice(0, -1);\n }\n // Map to our dropdown values\n if (currentUnit === 'hour') {\n currentUnit = 'hours';\n } else if (currentUnit === 'day') {\n currentUnit = 'days';\n }\n }\n }\n \n // Set current values in the modal\n document.getElementById('editEnableRetention').checked = currentEnabled;\n document.getElementById('editRetentionValue').value = currentValue;\n document.getElementById('editRetentionUnit').value = currentUnit;\n \n // Show/hide retention fields based on current state\n toggleEditRetentionFields();\n \n modal.show();\n }\n\n function toggleEditRetentionFields() {\n const enableRetention = document.getElementById('editEnableRetention');\n const retentionFields = document.getElementById('editRetentionFields');\n \n if (enableRetention.checked) {\n retentionFields.style.display = 'block';\n } else {\n retentionFields.style.display = 'none';\n }\n }\n\n function updateRetention() {\n const form = document.getElementById('editRetentionForm');\n const formData = new FormData(form);\n \n // Get topic details from the page\n const topicName = document.querySelector('h1').textContent.replace('Topic Details: ', '');\n const parts = topicName.split('.');\n \n if (parts.length < 2) {\n alert('Invalid topic name format');\n return;\n }\n \n const namespace = parts[0];\n const name = parts.slice(1).join('.');\n \n // Convert form data to JSON\n const data = {\n namespace: namespace,\n name: name,\n retention: {\n enabled: formData.get('editEnableRetention') === 'on',\n retention_seconds: 0\n }\n };\n\n // Calculate retention seconds if enabled\n if (data.retention.enabled) {\n const retentionValue = parseInt(formData.get('editRetentionValue'));\n const retentionUnit = formData.get('editRetentionUnit');\n \n if (retentionUnit === 'hours') {\n data.retention.retention_seconds = retentionValue * 3600;\n } else if (retentionUnit === 'days') {\n data.retention.retention_seconds = retentionValue * 86400;\n }\n }\n\n // Show loading state\n const updateButton = document.querySelector('#editRetentionModal .btn-primary');\n updateButton.disabled = true;\n updateButton.innerHTML = '<i class=\"fas fa-spinner fa-spin me-1\"></i>Updating...';\n\n // Send API request\n fetch('/api/mq/topics/retention/update', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(data)\n })\n .then(response => response.json())\n .then(result => {\n if (result.error) {\n alert('Failed to update retention: ' + result.error);\n } else {\n alert('Retention policy updated successfully!');\n // Close modal and refresh page\n const modal = bootstrap.Modal.getInstance(document.getElementById('editRetentionModal'));\n modal.hide();\n window.location.reload();\n }\n })\n .catch(error => {\n alert('Failed to update retention: ' + error.message);\n })\n .finally(() => {\n // Reset button state\n updateButton.disabled = false;\n updateButton.innerHTML = '<i class=\"fas fa-save me-1\"></i>Update Retention';\n });\n }\n </script><!-- Edit Retention Modal --><div class=\"modal fade\" id=\"editRetentionModal\" tabindex=\"-1\" role=\"dialog\"><div class=\"modal-dialog modal-lg\" role=\"document\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\"><i class=\"fas fa-edit me-2\"></i>Edit Retention Policy</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\"></button></div><div class=\"modal-body\"><form id=\"editRetentionForm\"><div class=\"card\"><div class=\"card-header\"><h6 class=\"mb-0\"><i class=\"fas fa-clock me-2\"></i>Retention Configuration</h6></div><div class=\"card-body\"><div class=\"form-check mb-3\"><input class=\"form-check-input\" type=\"checkbox\" id=\"editEnableRetention\" name=\"editEnableRetention\" onchange=\"toggleEditRetentionFields()\"> <label class=\"form-check-label\" for=\"editEnableRetention\">Enable data retention</label></div><div id=\"editRetentionFields\" style=\"display: none;\"><div class=\"row\"><div class=\"col-md-6\"><div class=\"mb-3\"><label for=\"editRetentionValue\" class=\"form-label\">Retention Duration</label> <input type=\"number\" class=\"form-control\" id=\"editRetentionValue\" name=\"editRetentionValue\" min=\"1\" value=\"7\"></div></div><div class=\"col-md-6\"><div class=\"mb-3\"><label for=\"editRetentionUnit\" class=\"form-label\">Unit</label> <select class=\"form-control\" id=\"editRetentionUnit\" name=\"editRetentionUnit\"><option value=\"hours\">Hours</option> <option value=\"days\" selected>Days</option></select></div></div></div><div class=\"alert alert-info\"><i class=\"fas fa-info-circle me-2\"></i> Data older than this duration will be automatically purged to save storage space.</div></div></div></div></form></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"button\" class=\"btn btn-primary\" onclick=\"updateRetention()\"><i class=\"fas fa-save me-1\"></i>Update Retention</button></div></div></div></div>") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 116, "</div></div></div></div></div></div></div><script>\n function exportPartitionsCSV() {\n const table = document.getElementById('partitionsTable');\n if (!table) return;\n \n let csv = 'Partition ID,Leader Broker,Follower Broker,Messages,Size,Last Data Time,Created\\n';\n \n const rows = table.querySelectorAll('tbody tr');\n rows.forEach(row => {\n const cells = row.querySelectorAll('td');\n if (cells.length >= 7) {\n const rowData = [\n cells[0].querySelector('.badge')?.textContent || '',\n cells[1].querySelector('strong')?.textContent || '',\n cells[2].textContent || '',\n cells[3].textContent || '',\n cells[4].textContent || '',\n cells[5].querySelector('span')?.textContent || '',\n cells[6].querySelector('span')?.textContent || ''\n ];\n csv += rowData.map(field => `\"${field.replace(/\"/g, '\"\"')}\"`).join(',') + '\\n';\n }\n });\n \n const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });\n const link = document.createElement('a');\n const url = URL.createObjectURL(blob);\n link.setAttribute('href', url);\n link.setAttribute('download', 'topic_partitions.csv');\n link.style.visibility = 'hidden';\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n }\n\n // Edit retention functions\n function showEditRetentionModal() {\n const modal = new bootstrap.Modal(document.getElementById('editRetentionModal'));\n \n // Get current retention values from the page\n const currentEnabled = document.querySelector('dd .badge.bg-success') !== null;\n const currentDurationElement = document.querySelector('dd .text-success');\n \n let currentValue = 7;\n let currentUnit = 'days';\n \n if (currentEnabled && currentDurationElement) {\n const durationText = currentDurationElement.textContent.trim();\n const parts = durationText.split(' ');\n if (parts.length >= 2) {\n currentValue = parseInt(parts[0]) || 7;\n currentUnit = parts[1].toLowerCase();\n // Handle plural forms\n if (currentUnit.endsWith('s')) {\n currentUnit = currentUnit.slice(0, -1);\n }\n // Map to our dropdown values\n if (currentUnit === 'hour') {\n currentUnit = 'hours';\n } else if (currentUnit === 'day') {\n currentUnit = 'days';\n }\n }\n }\n \n // Set current values in the modal\n document.getElementById('editEnableRetention').checked = currentEnabled;\n document.getElementById('editRetentionValue').value = currentValue;\n document.getElementById('editRetentionUnit').value = currentUnit;\n \n // Show/hide retention fields based on current state\n toggleEditRetentionFields();\n \n modal.show();\n }\n\n function toggleEditRetentionFields() {\n const enableRetention = document.getElementById('editEnableRetention');\n const retentionFields = document.getElementById('editRetentionFields');\n \n if (enableRetention.checked) {\n retentionFields.style.display = 'block';\n } else {\n retentionFields.style.display = 'none';\n }\n }\n\n function updateRetention() {\n const form = document.getElementById('editRetentionForm');\n const formData = new FormData(form);\n \n // Get topic details from the page\n const topicName = document.querySelector('h1').textContent.replace('Topic Details: ', '');\n const parts = topicName.split('.');\n \n if (parts.length < 2) {\n alert('Invalid topic name format');\n return;\n }\n \n const namespace = parts[0];\n const name = parts.slice(1).join('.');\n \n // Convert form data to JSON\n const data = {\n namespace: namespace,\n name: name,\n retention: {\n enabled: formData.get('editEnableRetention') === 'on',\n retention_seconds: 0\n }\n };\n\n // Calculate retention seconds if enabled\n if (data.retention.enabled) {\n const retentionValue = parseInt(formData.get('editRetentionValue'));\n const retentionUnit = formData.get('editRetentionUnit');\n \n if (retentionUnit === 'hours') {\n data.retention.retention_seconds = retentionValue * 3600;\n } else if (retentionUnit === 'days') {\n data.retention.retention_seconds = retentionValue * 86400;\n }\n }\n\n // Show loading state\n const updateButton = document.querySelector('#editRetentionModal .btn-primary');\n updateButton.disabled = true;\n updateButton.innerHTML = '<i class=\"fas fa-spinner fa-spin me-1\"></i>Updating...';\n\n // Send API request\n fetch('/api/mq/topics/retention/update', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(data)\n })\n .then(response => response.json())\n .then(result => {\n if (result.error) {\n alert('Failed to update retention: ' + result.error);\n } else {\n alert('Retention policy updated successfully!');\n // Close modal and refresh page\n const modal = bootstrap.Modal.getInstance(document.getElementById('editRetentionModal'));\n modal.hide();\n window.location.reload();\n }\n })\n .catch(error => {\n alert('Failed to update retention: ' + error.message);\n })\n .finally(() => {\n // Reset button state\n updateButton.disabled = false;\n updateButton.innerHTML = '<i class=\"fas fa-save me-1\"></i>Update Retention';\n });\n }\n </script><!-- Edit Retention Modal --><div class=\"modal fade\" id=\"editRetentionModal\" tabindex=\"-1\" role=\"dialog\"><div class=\"modal-dialog modal-lg\" role=\"document\"><div class=\"modal-content\"><div class=\"modal-header\"><h5 class=\"modal-title\"><i class=\"fas fa-edit me-2\"></i>Edit Retention Policy</h5><button type=\"button\" class=\"btn-close\" data-bs-dismiss=\"modal\"></button></div><div class=\"modal-body\"><form id=\"editRetentionForm\"><div class=\"card\"><div class=\"card-header\"><h6 class=\"mb-0\"><i class=\"fas fa-clock me-2\"></i>Retention Configuration</h6></div><div class=\"card-body\"><div class=\"form-check mb-3\"><input class=\"form-check-input\" type=\"checkbox\" id=\"editEnableRetention\" name=\"editEnableRetention\" onchange=\"toggleEditRetentionFields()\"> <label class=\"form-check-label\" for=\"editEnableRetention\">Enable data retention</label></div><div id=\"editRetentionFields\" style=\"display: none;\"><div class=\"row\"><div class=\"col-md-6\"><div class=\"mb-3\"><label for=\"editRetentionValue\" class=\"form-label\">Retention Duration</label> <input type=\"number\" class=\"form-control\" id=\"editRetentionValue\" name=\"editRetentionValue\" min=\"1\" value=\"7\"></div></div><div class=\"col-md-6\"><div class=\"mb-3\"><label for=\"editRetentionUnit\" class=\"form-label\">Unit</label> <select class=\"form-control\" id=\"editRetentionUnit\" name=\"editRetentionUnit\"><option value=\"hours\">Hours</option> <option value=\"days\" selected>Days</option></select></div></div></div><div class=\"alert alert-info\"><i class=\"fas fa-info-circle me-2\"></i> Data older than this duration will be automatically purged to save storage space.</div></div></div></div></form></div><div class=\"modal-footer\"><button type=\"button\" class=\"btn btn-secondary\" data-bs-dismiss=\"modal\">Cancel</button> <button type=\"button\" class=\"btn btn-primary\" onclick=\"updateRetention()\"><i class=\"fas fa-save me-1\"></i>Update Retention</button></div></div></div></div>") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } |
