From 84d4ea0995a9cc01808f3fedbf1b671a6538be32 Mon Sep 17 00:00:00 2001 From: chrislu Date: Fri, 4 Jul 2025 12:49:34 -0700 Subject: [PATCH] show volume details --- weed/admin/dash/admin_server.go | 162 +++-- weed/admin/handlers/admin_handlers.go | 2 + weed/admin/handlers/cluster_handlers.go | 39 ++ weed/admin/view/app/cluster_volumes.templ | 43 +- weed/admin/view/app/cluster_volumes_templ.go | 165 +++-- weed/admin/view/app/volume_details.templ | 425 ++++++++++++ weed/admin/view/app/volume_details_templ.go | 690 +++++++++++++++++++ 7 files changed, 1391 insertions(+), 135 deletions(-) create mode 100644 weed/admin/view/app/volume_details.templ create mode 100644 weed/admin/view/app/volume_details_templ.go diff --git a/weed/admin/dash/admin_server.go b/weed/admin/dash/admin_server.go index 61d15c755..89bae8ea2 100644 --- a/weed/admin/dash/admin_server.go +++ b/weed/admin/dash/admin_server.go @@ -115,25 +115,19 @@ type ClusterVolumeServersData struct { LastUpdated time.Time `json:"last_updated"` } -type VolumeInfo struct { - ID int `json:"id"` - Server string `json:"server"` - DataCenter string `json:"datacenter"` - Rack string `json:"rack"` - Collection string `json:"collection"` - Size int64 `json:"size"` - FileCount int64 `json:"file_count"` - Replication string `json:"replication"` - DiskType string `json:"disk_type"` - Version uint32 `json:"version"` +type VolumeWithTopology struct { + *master_pb.VolumeInformationMessage + Server string `json:"server"` + DataCenter string `json:"datacenter"` + Rack string `json:"rack"` } type ClusterVolumesData struct { - Username string `json:"username"` - Volumes []VolumeInfo `json:"volumes"` - TotalVolumes int `json:"total_volumes"` - TotalSize int64 `json:"total_size"` - LastUpdated time.Time `json:"last_updated"` + Username string `json:"username"` + Volumes []VolumeWithTopology `json:"volumes"` + TotalVolumes int `json:"total_volumes"` + TotalSize int64 `json:"total_size"` + LastUpdated time.Time `json:"last_updated"` // Pagination CurrentPage int `json:"current_page"` @@ -175,6 +169,14 @@ type ClusterVolumesData struct { FilterCollection string `json:"filter_collection"` } +type VolumeDetailsData struct { + Volume VolumeWithTopology `json:"volume"` + Replicas []VolumeWithTopology `json:"replicas"` + VolumeSizeLimit uint64 `json:"volume_size_limit"` + ReplicationCount int `json:"replication_count"` + LastUpdated time.Time `json:"last_updated"` +} + type CollectionInfo struct { Name string `json:"name"` DataCenter string `json:"datacenter"` @@ -816,7 +818,7 @@ func (s *AdminServer) GetClusterVolumes(page int, pageSize int, sortBy string, s if sortOrder == "" { sortOrder = "asc" } - var volumes []VolumeInfo + var volumes []VolumeWithTopology var totalSize int64 // Get detailed volume information via gRPC @@ -832,31 +834,14 @@ func (s *AdminServer) GetClusterVolumes(page int, pageSize int, sortBy string, s for _, node := range rack.DataNodeInfos { for _, diskInfo := range node.DiskInfos { for _, volInfo := range diskInfo.VolumeInfos { - // Extract collection name from volume info - collectionName := volInfo.Collection - // Keep original collection name, don't default to "default" - // This way filtering works correctly - - // Get disk type from volume info, default to hdd if empty - diskType := volInfo.DiskType - if diskType == "" { - diskType = "hdd" - } - - volume := VolumeInfo{ - ID: int(volInfo.Id), // Use actual SeaweedFS volume ID - Server: node.Id, - DataCenter: dc.Id, - Rack: rack.Id, - Collection: collectionName, // Keep original, even if empty - Size: int64(volInfo.Size), - FileCount: int64(volInfo.FileCount), - Replication: fmt.Sprintf("%03d", volInfo.ReplicaPlacement), - DiskType: diskType, - Version: volInfo.Version, + volume := VolumeWithTopology{ + VolumeInformationMessage: volInfo, + Server: node.Id, + DataCenter: dc.Id, + Rack: rack.Id, } volumes = append(volumes, volume) - totalSize += volume.Size + totalSize += int64(volInfo.Size) } } } @@ -873,7 +858,7 @@ func (s *AdminServer) GetClusterVolumes(page int, pageSize int, sortBy string, s // Filter by collection if specified if collection != "" { - var filteredVolumes []VolumeInfo + var filteredVolumes []VolumeWithTopology var filteredTotalSize int64 for _, volume := range volumes { // Handle "default" collection filtering for empty collections @@ -884,7 +869,7 @@ func (s *AdminServer) GetClusterVolumes(page int, pageSize int, sortBy string, s if volumeCollection == collection { filteredVolumes = append(filteredVolumes, volume) - filteredTotalSize += volume.Size + filteredTotalSize += int64(volume.Size) } } volumes = filteredVolumes @@ -939,7 +924,7 @@ func (s *AdminServer) GetClusterVolumes(page int, pageSize int, sortBy string, s startIndex := (page - 1) * pageSize endIndex := startIndex + pageSize if startIndex >= totalVolumes { - volumes = []VolumeInfo{} + volumes = []VolumeWithTopology{} } else { if endIndex > totalVolumes { endIndex = totalVolumes @@ -1032,13 +1017,13 @@ func (s *AdminServer) GetClusterVolumes(page int, pageSize int, sortBy string, s } // sortVolumes sorts the volumes slice based on the specified field and order -func (s *AdminServer) sortVolumes(volumes []VolumeInfo, sortBy string, sortOrder string) { +func (s *AdminServer) sortVolumes(volumes []VolumeWithTopology, sortBy string, sortOrder string) { sort.Slice(volumes, func(i, j int) bool { var less bool switch sortBy { case "id": - less = volumes[i].ID < volumes[j].ID + less = volumes[i].Id < volumes[j].Id case "server": less = volumes[i].Server < volumes[j].Server case "datacenter": @@ -1052,13 +1037,13 @@ func (s *AdminServer) sortVolumes(volumes []VolumeInfo, sortBy string, sortOrder case "filecount": less = volumes[i].FileCount < volumes[j].FileCount case "replication": - less = volumes[i].Replication < volumes[j].Replication + less = volumes[i].ReplicaPlacement < volumes[j].ReplicaPlacement case "disktype": less = volumes[i].DiskType < volumes[j].DiskType case "version": less = volumes[i].Version < volumes[j].Version default: - less = volumes[i].ID < volumes[j].ID + less = volumes[i].Id < volumes[j].Id } if sortOrder == "desc" { @@ -1321,3 +1306,86 @@ func (s *AdminServer) GetClusterFilers() (*ClusterFilersData, error) { func (s *AdminServer) GetAllFilers() []string { return s.getDiscoveredFilers() } + +// GetVolumeDetails retrieves detailed information about a specific volume +func (s *AdminServer) GetVolumeDetails(volumeID int, server string) (*VolumeDetailsData, error) { + var primaryVolume VolumeWithTopology + var replicas []VolumeWithTopology + var volumeSizeLimit uint64 + var found bool + + // Find the volume and all its replicas in the cluster + err := s.WithMasterClient(func(client master_pb.SeaweedClient) error { + resp, err := client.VolumeList(context.Background(), &master_pb.VolumeListRequest{}) + if err != nil { + return err + } + + if resp.TopologyInfo != nil { + for _, dc := range resp.TopologyInfo.DataCenterInfos { + for _, rack := range dc.RackInfos { + for _, node := range rack.DataNodeInfos { + for _, diskInfo := range node.DiskInfos { + for _, volInfo := range diskInfo.VolumeInfos { + if int(volInfo.Id) == volumeID { + diskType := volInfo.DiskType + if diskType == "" { + diskType = "hdd" + } + + volume := VolumeWithTopology{ + VolumeInformationMessage: volInfo, + Server: node.Id, + DataCenter: dc.Id, + Rack: rack.Id, + } + + // If this is the requested server, it's the primary volume + if node.Id == server { + primaryVolume = volume + found = true + } else { + // This is a replica on another server + replicas = append(replicas, volume) + } + } + } + } + } + } + } + } + return nil + }) + + if err != nil { + return nil, err + } + + if !found { + return nil, fmt.Errorf("volume %d not found on server %s", volumeID, server) + } + + // Get volume size limit from master + err = s.WithMasterClient(func(client master_pb.SeaweedClient) error { + resp, err := client.GetMasterConfiguration(context.Background(), &master_pb.GetMasterConfigurationRequest{}) + if err != nil { + return err + } + volumeSizeLimit = uint64(resp.VolumeSizeLimitMB) * 1024 * 1024 // Convert MB to bytes + return nil + }) + + if err != nil { + // If we can't get the limit, set a default + volumeSizeLimit = 30 * 1024 * 1024 * 1024 // 30GB default + } + + return &VolumeDetailsData{ + Volume: primaryVolume, + Replicas: replicas, + VolumeSizeLimit: volumeSizeLimit, + ReplicationCount: len(replicas) + 1, // Include the primary volume + LastUpdated: time.Now(), + }, nil +} diff --git a/weed/admin/handlers/admin_handlers.go b/weed/admin/handlers/admin_handlers.go index 7617a0539..012694ffb 100644 --- a/weed/admin/handlers/admin_handlers.go +++ b/weed/admin/handlers/admin_handlers.go @@ -66,6 +66,7 @@ func (h *AdminHandlers) SetupRoutes(r *gin.Engine, authRequired bool, username, protected.GET("/cluster/filers", h.clusterHandlers.ShowClusterFilers) protected.GET("/cluster/volume-servers", h.clusterHandlers.ShowClusterVolumeServers) protected.GET("/cluster/volumes", h.clusterHandlers.ShowClusterVolumes) + protected.GET("/cluster/volumes/:id/:server", h.clusterHandlers.ShowVolumeDetails) protected.GET("/cluster/collections", h.clusterHandlers.ShowClusterCollections) // API routes for AJAX calls @@ -130,6 +131,7 @@ func (h *AdminHandlers) SetupRoutes(r *gin.Engine, authRequired bool, username, r.GET("/cluster/filers", h.clusterHandlers.ShowClusterFilers) r.GET("/cluster/volume-servers", h.clusterHandlers.ShowClusterVolumeServers) r.GET("/cluster/volumes", h.clusterHandlers.ShowClusterVolumes) + r.GET("/cluster/volumes/:id/:server", h.clusterHandlers.ShowVolumeDetails) r.GET("/cluster/collections", h.clusterHandlers.ShowClusterCollections) // API routes for AJAX calls diff --git a/weed/admin/handlers/cluster_handlers.go b/weed/admin/handlers/cluster_handlers.go index b89d6aa29..769cc2894 100644 --- a/weed/admin/handlers/cluster_handlers.go +++ b/weed/admin/handlers/cluster_handlers.go @@ -95,6 +95,45 @@ func (h *ClusterHandlers) ShowClusterVolumes(c *gin.Context) { } } +// ShowVolumeDetails renders the volume details page +func (h *ClusterHandlers) ShowVolumeDetails(c *gin.Context) { + volumeIDStr := c.Param("id") + server := c.Param("server") + + if volumeIDStr == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Volume ID is required"}) + return + } + + if server == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Server is required"}) + return + } + + volumeID, err := strconv.Atoi(volumeIDStr) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid volume ID"}) + return + } + + // Get volume details + volumeDetails, err := h.adminServer.GetVolumeDetails(volumeID, server) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get volume details: " + err.Error()}) + return + } + + // Render HTML template + c.Header("Content-Type", "text/html") + volumeDetailsComponent := app.VolumeDetails(*volumeDetails) + layoutComponent := layout.Layout(c, volumeDetailsComponent) + err = layoutComponent.Render(c.Request.Context(), c.Writer) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to render template: " + err.Error()}) + return + } +} + // ShowClusterCollections renders the cluster collections page func (h *ClusterHandlers) ShowClusterCollections(c *gin.Context) { // Get cluster collections data diff --git a/weed/admin/view/app/cluster_volumes.templ b/weed/admin/view/app/cluster_volumes.templ index 1973fdcdb..b21918066 100644 --- a/weed/admin/view/app/cluster_volumes.templ +++ b/weed/admin/view/app/cluster_volumes.templ @@ -312,7 +312,7 @@ templ ClusterVolumes(data dash.ClusterVolumesData) { for _, volume := range data.Volumes { - {fmt.Sprintf("%d", volume.ID)} + {fmt.Sprintf("%d", volume.Id)} @@ -343,11 +343,11 @@ templ ClusterVolumes(data dash.ClusterVolumesData) { } } - {formatBytes(volume.Size)} - {fmt.Sprintf("%d", volume.FileCount)} - - {volume.Replication} - + {formatBytes(int64(volume.Size))} + {fmt.Sprintf("%d", volume.FileCount)} + + {fmt.Sprintf("%03d", volume.ReplicaPlacement)} + if data.ShowDiskTypeColumn { {volume.DiskType} @@ -360,8 +360,8 @@ templ ClusterVolumes(data dash.ClusterVolumesData) { }
-
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "
Showing ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "
Showing ") 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", (data.CurrentPage-1)*data.PageSize+1)) + var templ_7745c5c3_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", (data.CurrentPage-1)*data.PageSize+1)) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 387, Col: 98} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) + _, 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, 78, " to ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 79, " to ") 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", minInt(data.CurrentPage*data.PageSize, data.TotalVolumes))) + var templ_7745c5c3_Var30 string + templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", minInt(data.CurrentPage*data.PageSize, data.TotalVolumes))) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 387, Col: 180} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) + _, 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, 79, " of ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 80, " of ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var30 string - templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.TotalVolumes)) + var templ_7745c5c3_Var31 string + templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.TotalVolumes)) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 387, Col: 222} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) + _, 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, 80, " volumes
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, " volumes
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if data.TotalPages > 1 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "
Page ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "
Page ") 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", data.CurrentPage)) + var templ_7745c5c3_Var32 string + templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.CurrentPage)) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 393, Col: 77} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) + _, 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, 82, " of ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 83, " of ") 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", data.TotalPages)) + var templ_7745c5c3_Var33 string + templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.TotalPages)) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 393, Col: 117} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) + _, 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, 83, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, " ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } if data.TotalPages > 1 { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "
No Volumes Found

No volumes are currently available in the cluster.

") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "
No Volumes Found

No volumes are currently available in the cluster.

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "
Last updated: ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "
Last updated: ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } - var templ_7745c5c3_Var38 string - templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(data.LastUpdated.Format("2006-01-02 15:04:05")) + var templ_7745c5c3_Var39 string + templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(data.LastUpdated.Format("2006-01-02 15:04:05")) if templ_7745c5c3_Err != nil { return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 465, Col: 81} } - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) + _, 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, 102, "
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } @@ -891,12 +904,12 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { }) } -func countActiveVolumes(volumes []dash.VolumeInfo) int { +func countActiveVolumes(volumes []dash.VolumeWithTopology) int { // Since we removed status tracking, consider all volumes as active return len(volumes) } -func countUniqueDataCenters(volumes []dash.VolumeInfo) int { +func countUniqueDataCenters(volumes []dash.VolumeWithTopology) int { dcMap := make(map[string]bool) for _, volume := range volumes { dcMap[volume.DataCenter] = true @@ -904,7 +917,7 @@ func countUniqueDataCenters(volumes []dash.VolumeInfo) int { return len(dcMap) } -func countUniqueRacks(volumes []dash.VolumeInfo) int { +func countUniqueRacks(volumes []dash.VolumeWithTopology) int { rackMap := make(map[string]bool) for _, volume := range volumes { if volume.Rack != "" { @@ -914,7 +927,7 @@ func countUniqueRacks(volumes []dash.VolumeInfo) int { return len(rackMap) } -func countUniqueDiskTypes(volumes []dash.VolumeInfo) int { +func countUniqueDiskTypes(volumes []dash.VolumeWithTopology) int { diskTypeMap := make(map[string]bool) for _, volume := range volumes { diskType := volume.DiskType @@ -942,23 +955,23 @@ func getSortIcon(column, currentSort, currentOrder string) templ.Component { }() } ctx = templ.InitializeContext(ctx) - templ_7745c5c3_Var39 := templ.GetChildren(ctx) - if templ_7745c5c3_Var39 == nil { - templ_7745c5c3_Var39 = templ.NopComponent + templ_7745c5c3_Var40 := templ.GetChildren(ctx) + if templ_7745c5c3_Var40 == nil { + templ_7745c5c3_Var40 = templ.NopComponent } ctx = templ.ClearChildren(ctx) if column != currentSort { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else if currentOrder == "asc" { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } } else { - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/weed/admin/view/app/volume_details.templ b/weed/admin/view/app/volume_details.templ new file mode 100644 index 000000000..01876b46f --- /dev/null +++ b/weed/admin/view/app/volume_details.templ @@ -0,0 +1,425 @@ +package app + +import ( + "fmt" + "time" + "github.com/seaweedfs/seaweedfs/weed/admin/dash" +) + +templ VolumeDetails(data dash.VolumeDetailsData) { +
+
+

+ Volume Details +

+ +
+
+
+ + +
+
+
+ +
+ +
+
+
+
+ Volume Information +
+
+
+
+
+
+ +
{fmt.Sprintf("%d", data.Volume.Id)}
+
+ +
+ +
{data.Volume.DataCenter}
+
+
+ +
{data.Volume.Rack}
+
+
+
+
+ +
+ if data.Volume.Collection == "" { + + default + + } else { + + {data.Volume.Collection} + + } +
+
+
+ +
{fmt.Sprintf("%03d", data.Volume.ReplicaPlacement)}
+
+
+ +
+ + if data.Volume.DiskType == "" { + hdd + } else { + {data.Volume.DiskType} + } + +
+
+
+ +
{fmt.Sprintf("v%d", data.Volume.Version)}
+
+
+
+
+
+
+ + +
+ +
+
+
+ Volume Statistics & Health +
+
+
+ +
+
+
+
+ {formatBytes(int64(data.Volume.Size - data.Volume.DeletedByteCount))} +
+ Active Bytes +
+
+
+
+
+ {formatBytes(int64(data.Volume.DeletedByteCount))} +
+ Deleted Bytes +
+
+
+ + + + +
+
+
+
+ {fmt.Sprintf("%d", data.Volume.FileCount)} +
+ Active Files +
+
+
+
+
+ {fmt.Sprintf("%d", data.Volume.DeleteCount)} +
+ Deleted Files +
+
+
+ + + + + if data.Volume.FileCount > 0 && data.Volume.Size > 0 { +
+
+ Storage Efficiency + + {fmt.Sprintf("%.1f%%", float64(data.Volume.Size-data.Volume.DeletedByteCount)/float64(data.Volume.Size)*100)} + +
+
+
+
+
+
+ } + +
+ + +
+
+
+ if data.Volume.ReadOnly { + + Read Only + + if data.Volume.Size >= data.VolumeSizeLimit { +
+ Size limit exceeded +
+ } + } else if data.VolumeSizeLimit > data.Volume.Size { + + Read/Write + + } else { + + Size Limit Reached + + } +
+
+
+ + +
+
+
+
+ #{fmt.Sprintf("%d", data.Volume.CompactRevision)} +
+ Compact Revision +
+
+
+
+
+ if data.Volume.ModifiedAtSecond > 0 { + {formatTimestamp(data.Volume.ModifiedAtSecond)} + } else { + Never modified + } +
+ Last Modified +
+
+
+ + + if data.Volume.Ttl > 0 { +
+ + {formatTTL(data.Volume.Ttl)} + +
+ Time To Live +
+
+ } + + + if data.Volume.RemoteStorageName != "" { +
+
+
+
+ {data.Volume.RemoteStorageName} +
+ Remote Storage +
+
+ if data.Volume.RemoteStorageKey != "" { +
+
+ {data.Volume.RemoteStorageKey} +
+ Storage Key +
+ } + } +
+
+
+
+ + + if len(data.Replicas) > 0 { +
+
+
+
+
+ Replicas ({fmt.Sprintf("%d", data.ReplicationCount)}) +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + for _, replica := range data.Replicas { + + + + + + + + + + } + +
ServerData CenterRackSizeFile CountStatusActions
+ + + {data.Volume.Server} + + + + Primary + {data.Volume.DataCenter}{data.Volume.Rack}{formatBytes(int64(data.Volume.Size))}{fmt.Sprintf("%d", data.Volume.FileCount)}Active + Current Volume +
+ + {replica.Server} + + + {replica.DataCenter}{replica.Rack}{formatBytes(int64(replica.Size))}{fmt.Sprintf("%d", replica.FileCount)}Replica + + View + +
+
+
+
+
+
+ } + + +
+
+
+
+
+ Actions +
+
+
+
+ + + +
+
+ + + Use these actions to perform maintenance operations on the volume. + +
+
+
+
+
+ + +
+
+ + + Last updated: {data.LastUpdated.Format("2006-01-02 15:04:05")} + +
+
+} + +func formatTimestamp(unixTimestamp int64) string { + if unixTimestamp <= 0 { + return "Never" + } + t := time.Unix(unixTimestamp, 0) + return t.Format("2006-01-02 15:04:05") +} + +func formatTTL(ttlSeconds uint32) string { + if ttlSeconds == 0 { + return "No TTL" + } + + duration := time.Duration(ttlSeconds) * time.Second + + // Convert to human readable format + days := int(duration.Hours()) / 24 + hours := int(duration.Hours()) % 24 + minutes := int(duration.Minutes()) % 60 + + if days > 0 { + if hours > 0 { + return fmt.Sprintf("%dd %dh", days, hours) + } + return fmt.Sprintf("%d days", days) + } else if hours > 0 { + if minutes > 0 { + return fmt.Sprintf("%dh %dm", hours, minutes) + } + return fmt.Sprintf("%d hours", hours) + } else if minutes > 0 { + return fmt.Sprintf("%d minutes", minutes) + } else { + return fmt.Sprintf("%d seconds", int(duration.Seconds())) + } +} \ No newline at end of file diff --git a/weed/admin/view/app/volume_details_templ.go b/weed/admin/view/app/volume_details_templ.go new file mode 100644 index 000000000..6217fa695 --- /dev/null +++ b/weed/admin/view/app/volume_details_templ.go @@ -0,0 +1,690 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +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" + "time" +) + +func VolumeDetails(data dash.VolumeDetailsData) 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, "

Volume Details

Volume Information
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.Volume.Id)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 49, Col: 90} + } + _, 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.DataCenter) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 62, Col: 99} + } + _, 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.Rack) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 66, Col: 93} + } + _, 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, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Volume.Collection == "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "default") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.Collection) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 79, Col: 100} + } + _, 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, 12, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%03d", data.Volume.ReplicaPlacement)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 86, Col: 115} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Volume.DiskType == "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "hdd") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.DiskType) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 95, Col: 65} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("v%d", data.Volume.Version)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 102, Col: 105} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "
Volume Statistics & Health
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(int64(data.Volume.Size - data.Volume.DeletedByteCount))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 125, Col: 104} + } + _, 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, 18, "
Active Bytes
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(int64(data.Volume.DeletedByteCount))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 133, Col: 85} + } + _, 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, 19, "
Deleted Bytes
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.Volume.FileCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 147, Col: 77} + } + _, 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, 20, "
Active Files
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var17 string + templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.Volume.DeleteCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 155, Col: 79} + } + _, 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, 21, "
Deleted Files
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Volume.FileCount > 0 && data.Volume.Size > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "
Storage Efficiency ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%.1f%%", float64(data.Volume.Size-data.Volume.DeletedByteCount)/float64(data.Volume.Size)*100)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 170, Col: 144} + } + _, 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, 23, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Volume.ReadOnly { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "Read Only ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Volume.Size >= data.VolumeSizeLimit { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "
Size limit exceeded
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } else if data.VolumeSizeLimit > data.Volume.Size { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "Read/Write") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "Size Limit Reached") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "
#") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.Volume.CompactRevision)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 216, Col: 84} + } + _, 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, 32, "
Compact Revision
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Volume.ModifiedAtSecond > 0 { + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(formatTimestamp(data.Volume.ModifiedAtSecond)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 225, Col: 86} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "Never modified") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "
Last Modified
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Volume.Ttl > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var23 string + templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(formatTTL(data.Volume.Ttl)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 239, Col: 92} + } + _, 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, 36, "
Time To Live
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Volume.RemoteStorageName != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var24 string + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.RemoteStorageName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 253, Col: 99} + } + _, 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, "
Remote Storage
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if data.Volume.RemoteStorageKey != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var26 string + templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.RemoteStorageKey) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 261, Col: 65} + } + _, 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, 42, "
Storage Key
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(data.Replicas) > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "
Replicas (") + 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", data.ReplicationCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 279, Col: 111} + } + _, 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, 45, ")
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, replica := range data.Replicas { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "
ServerData CenterRackSizeFile CountStatusActions
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var29 string + templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.Server) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 302, Col: 71} + } + _, 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, 47, " Primary") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var30 string + templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.DataCenter) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 308, Col: 106} + } + _, 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, 48, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var31 string + templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(data.Volume.Rack) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 309, Col: 100} + } + _, 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, 49, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var32 string + templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(int64(data.Volume.Size))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 310, Col: 81} + } + _, 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, 50, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var33 string + templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.Volume.FileCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 311, Col: 85} + } + _, 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, 51, "ActiveCurrent Volume
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var35 string + templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(replica.Server) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 322, Col: 67} + } + _, 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, 54, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var36 string + templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(replica.DataCenter) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 326, Col: 106} + } + _, 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, 55, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var37 string + templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(replica.Rack) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 327, Col: 100} + } + _, 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, 56, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var38 string + templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(int64(replica.Size))) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 328, Col: 81} + } + _, 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, 57, "") + 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", replica.FileCount)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 329, Col: 85} + } + _, 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, 58, "ReplicaView
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "
Actions
Use these actions to perform maintenance operations on the volume.
Last updated: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var41 string + templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(data.LastUpdated.Format("2006-01-02 15:04:05")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/volume_details.templ`, Line: 384, Col: 77} + } + _, 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, 62, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func formatTimestamp(unixTimestamp int64) string { + if unixTimestamp <= 0 { + return "Never" + } + t := time.Unix(unixTimestamp, 0) + return t.Format("2006-01-02 15:04:05") +} + +func formatTTL(ttlSeconds uint32) string { + if ttlSeconds == 0 { + return "No TTL" + } + + duration := time.Duration(ttlSeconds) * time.Second + + // Convert to human readable format + days := int(duration.Hours()) / 24 + hours := int(duration.Hours()) % 24 + minutes := int(duration.Minutes()) % 60 + + if days > 0 { + if hours > 0 { + return fmt.Sprintf("%dd %dh", days, hours) + } + return fmt.Sprintf("%d days", days) + } else if hours > 0 { + if minutes > 0 { + return fmt.Sprintf("%dh %dm", hours, minutes) + } + return fmt.Sprintf("%d hours", hours) + } else if minutes > 0 { + return fmt.Sprintf("%d minutes", minutes) + } else { + return fmt.Sprintf("%d seconds", int(duration.Seconds())) + } +} + +var _ = templruntime.GeneratedTemplate