diff --git a/weed/admin/dash/volume_export.go b/weed/admin/dash/volume_export.go new file mode 100644 index 000000000..58a63d9ef --- /dev/null +++ b/weed/admin/dash/volume_export.go @@ -0,0 +1,340 @@ +package dash + +import ( + "context" + "sort" + "time" + + "github.com/seaweedfs/seaweedfs/weed/pb/master_pb" + "github.com/seaweedfs/seaweedfs/weed/storage" + "github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding" +) + +// VolumeListExport is a full-cluster volume report, downloadable from the admin +// UI as JSON. It mirrors the topology the `volume.list` shell command walks +// (data center -> rack -> node -> disk -> volumes / EC shards) and adds derived +// fields the shell command does not print: garbage and fullness ratios, a +// human-readable modified time, remote-tiering keys, per-disk capacity counts, +// and the cluster-wide duplicate-volume-id scan. +type VolumeListExport struct { + GeneratedAt time.Time `json:"generated_at"` + VolumeSizeLimitMB uint64 `json:"volume_size_limit_mb"` + FilterCollection string `json:"filter_collection,omitempty"` + Totals VolumeExportTotals `json:"totals"` + DuplicateVolumeIds []DuplicateVolumeId `json:"duplicate_volume_ids"` + DataCenters []*ExportDataCenter `json:"data_centers"` +} + +// VolumeExportTotals aggregates the included records. TotalSize sums normal +// volume sizes and EC shard sizes (matching volume.list's statistics); the +// file/delete totals cover normal volumes only. +type VolumeExportTotals struct { + VolumeCount int `json:"volume_count"` + EcShardCount int `json:"ec_shard_count"` + TotalSize uint64 `json:"total_size"` + FileCount uint64 `json:"file_count"` + DeletedFileCount uint64 `json:"deleted_file_count"` + DeletedBytes uint64 `json:"deleted_bytes"` +} + +// DuplicateVolumeId flags a volume id that appears under more than one +// collection, where collection.delete or a bare-id operation is dangerous. +type DuplicateVolumeId struct { + VolumeId uint32 `json:"volume_id"` + Collections []string `json:"collections"` +} + +type ExportDataCenter struct { + Id string `json:"id"` + Racks []*ExportRack `json:"racks"` +} + +type ExportRack struct { + Id string `json:"id"` + Nodes []*ExportNode `json:"nodes"` +} + +type ExportNode struct { + Id string `json:"id"` + GrpcPort uint32 `json:"grpc_port,omitempty"` + Disks []*ExportDisk `json:"disks"` +} + +// ExportDisk capacity counts describe the whole physical disk; the Volumes / +// EcShards lists are filtered when a collection filter is active. +type ExportDisk struct { + DiskType string `json:"disk_type"` + DiskId uint32 `json:"disk_id"` + VolumeCount int64 `json:"volume_count"` + MaxVolumeCount int64 `json:"max_volume_count"` + ActiveVolumeCount int64 `json:"active_volume_count"` + FreeVolumeCount int64 `json:"free_volume_count"` + RemoteVolumeCount int64 `json:"remote_volume_count"` + Volumes []*ExportVolume `json:"volumes,omitempty"` + EcShards []*ExportEcShard `json:"ec_shards,omitempty"` +} + +type ExportVolume struct { + Id uint32 `json:"id"` + Collection string `json:"collection"` + Size uint64 `json:"size"` + FileCount uint64 `json:"file_count"` + DeleteCount uint64 `json:"delete_count"` + DeletedByteCount uint64 `json:"deleted_byte_count"` + GarbageRatio float64 `json:"garbage_ratio"` + FullnessRatio float64 `json:"fullness_ratio"` + ReplicaPlacement string `json:"replica_placement"` + Version uint32 `json:"version"` + Ttl string `json:"ttl"` + ReadOnly bool `json:"read_only"` + CompactRevision uint32 `json:"compact_revision"` + ModifiedAtSecond int64 `json:"modified_at_second"` + ModifiedAt string `json:"modified_at,omitempty"` + DiskType string `json:"disk_type"` + DiskId uint32 `json:"disk_id"` + RemoteStorageName string `json:"remote_storage_name,omitempty"` + RemoteStorageKey string `json:"remote_storage_key,omitempty"` +} + +type ExportEcShard struct { + Id uint32 `json:"id"` + Collection string `json:"collection"` + ShardIds []uint32 `json:"shard_ids"` + ShardSizes []int64 `json:"shard_sizes"` + TotalSize int64 `json:"total_size"` + FileCount uint64 `json:"file_count"` + DeleteCount uint64 `json:"delete_count"` + DiskType string `json:"disk_type"` + DiskId uint32 `json:"disk_id"` + ExpireAtSec uint64 `json:"expire_at_sec,omitempty"` +} + +// ExportClusterVolumeList builds a full-cluster volume report from the master +// topology. A non-empty collection limits the listed volumes and EC shards to +// that collection; the duplicate-id scan always covers the whole cluster. +// generatedAt is passed in so the report is deterministic and testable. +func (s *AdminServer) ExportClusterVolumeList(ctx context.Context, collection string, generatedAt time.Time) (*VolumeListExport, error) { + export := &VolumeListExport{ + GeneratedAt: generatedAt, + FilterCollection: collection, + DuplicateVolumeIds: []DuplicateVolumeId{}, + DataCenters: []*ExportDataCenter{}, + } + + err := s.WithMasterClient(func(client master_pb.SeaweedClient) error { + resp, err := client.VolumeList(ctx, &master_pb.VolumeListRequest{}) + if err != nil { + return err + } + export.VolumeSizeLimitMB = resp.VolumeSizeLimitMb + topo := resp.TopologyInfo + if topo == nil { + return nil + } + + volumeSizeLimit := resp.VolumeSizeLimitMb * 1024 * 1024 + export.DuplicateVolumeIds = findDuplicateVolumeIdsForExport(topo) + + dcs := append([]*master_pb.DataCenterInfo(nil), topo.DataCenterInfos...) + sort.Slice(dcs, func(i, j int) bool { return dcs[i].Id < dcs[j].Id }) + for _, dc := range dcs { + exportDc := &ExportDataCenter{Id: dc.Id, Racks: []*ExportRack{}} + racks := append([]*master_pb.RackInfo(nil), dc.RackInfos...) + sort.Slice(racks, func(i, j int) bool { return racks[i].Id < racks[j].Id }) + for _, rack := range racks { + exportRack := &ExportRack{Id: rack.Id, Nodes: []*ExportNode{}} + nodes := append([]*master_pb.DataNodeInfo(nil), rack.DataNodeInfos...) + sort.Slice(nodes, func(i, j int) bool { return nodes[i].Id < nodes[j].Id }) + for _, node := range nodes { + exportNode := &ExportNode{Id: node.Id, GrpcPort: node.GrpcPort, Disks: []*ExportDisk{}} + for _, diskType := range sortedDiskTypes(node.DiskInfos) { + // Split per physical disk like volume.list so DiskId is meaningful. + for _, diskInfo := range node.DiskInfos[diskType].SplitByPhysicalDisk() { + if disk := buildExportDisk(diskInfo, collection, volumeSizeLimit, &export.Totals); disk != nil { + exportNode.Disks = append(exportNode.Disks, disk) + } + } + } + if len(exportNode.Disks) > 0 { + exportRack.Nodes = append(exportRack.Nodes, exportNode) + } + } + if len(exportRack.Nodes) > 0 { + exportDc.Racks = append(exportDc.Racks, exportRack) + } + } + if len(exportDc.Racks) > 0 { + export.DataCenters = append(export.DataCenters, exportDc) + } + } + return nil + }) + if err != nil { + return nil, err + } + return export, nil +} + +func sortedDiskTypes(diskInfos map[string]*master_pb.DiskInfo) []string { + types := make([]string, 0, len(diskInfos)) + for t := range diskInfos { + types = append(types, t) + } + sort.Strings(types) + return types +} + +// buildExportDisk returns nil when the disk holds nothing matching the filter. +func buildExportDisk(diskInfo *master_pb.DiskInfo, collection string, volumeSizeLimit uint64, totals *VolumeExportTotals) *ExportDisk { + diskType := diskInfo.Type + if diskType == "" { + diskType = "hdd" + } + + vols := append([]*master_pb.VolumeInformationMessage(nil), diskInfo.VolumeInfos...) + sort.Slice(vols, func(i, j int) bool { return vols[i].Id < vols[j].Id }) + var volumes []*ExportVolume + for _, vi := range vols { + if collection != "" && !matchesCollection(vi.Collection, collection) { + continue + } + volumes = append(volumes, buildExportVolume(vi, volumeSizeLimit)) + totals.VolumeCount++ + totals.TotalSize += vi.Size + totals.FileCount += vi.FileCount + totals.DeletedFileCount += vi.DeleteCount + totals.DeletedBytes += vi.DeletedByteCount + } + + shards := append([]*master_pb.VolumeEcShardInformationMessage(nil), diskInfo.EcShardInfos...) + sort.Slice(shards, func(i, j int) bool { return shards[i].Id < shards[j].Id }) + var ecShards []*ExportEcShard + for _, eci := range shards { + if collection != "" && !matchesCollection(eci.Collection, collection) { + continue + } + shard := buildExportEcShard(eci) + ecShards = append(ecShards, shard) + totals.EcShardCount++ + totals.TotalSize += uint64(shard.TotalSize) + } + + if len(volumes) == 0 && len(ecShards) == 0 { + return nil + } + + return &ExportDisk{ + DiskType: diskType, + DiskId: diskInfo.DiskId, + VolumeCount: diskInfo.VolumeCount, + MaxVolumeCount: diskInfo.MaxVolumeCount, + ActiveVolumeCount: diskInfo.ActiveVolumeCount, + FreeVolumeCount: diskInfo.FreeVolumeCount, + RemoteVolumeCount: diskInfo.RemoteVolumeCount, + Volumes: volumes, + EcShards: ecShards, + } +} + +func buildExportVolume(m *master_pb.VolumeInformationMessage, volumeSizeLimit uint64) *ExportVolume { + v := &ExportVolume{ + Id: m.Id, + Collection: m.Collection, + Size: m.Size, + FileCount: m.FileCount, + DeleteCount: m.DeleteCount, + DeletedByteCount: m.DeletedByteCount, + Version: m.Version, + ReadOnly: m.ReadOnly, + CompactRevision: m.CompactRevision, + ModifiedAtSecond: m.ModifiedAtSecond, + DiskType: m.DiskType, + DiskId: m.DiskId, + RemoteStorageName: m.RemoteStorageName, + RemoteStorageKey: m.RemoteStorageKey, + } + // Decode replica placement and TTL the way volume.list does. + if vi, err := storage.NewVolumeInfo(m); err == nil { + v.ReplicaPlacement = vi.ReplicaPlacement.String() + v.Ttl = vi.Ttl.String() + } + if m.Size > 0 { + v.GarbageRatio = float64(m.DeletedByteCount) / float64(m.Size) + } + if volumeSizeLimit > 0 { + v.FullnessRatio = float64(m.Size) / float64(volumeSizeLimit) + } + if m.ModifiedAtSecond > 0 { + v.ModifiedAt = time.Unix(m.ModifiedAtSecond, 0).UTC().Format(time.RFC3339) + } + return v +} + +func buildExportEcShard(m *master_pb.VolumeEcShardInformationMessage) *ExportEcShard { + si := erasure_coding.ShardsInfoFromVolumeEcShardInformationMessage(m) + return &ExportEcShard{ + Id: m.Id, + Collection: m.Collection, + ShardIds: si.IdsUint32(), + ShardSizes: si.SizesInt64(), + TotalSize: int64(si.TotalSize()), + FileCount: m.FileCount, + DeleteCount: m.DeleteCount, + DiskType: m.DiskType, + DiskId: m.DiskId, + ExpireAtSec: m.ExpireAtSec, + } +} + +// findDuplicateVolumeIdsForExport reports volume ids living under more than one +// collection. Volume ids are meant to be globally unique; a duplicate is a +// cluster-health hazard (collection.delete destroys one collection's copy, +// bare-id lookups are ambiguous). This mirrors volume.list's findDuplicateVolumeIds +// rather than reaching into the shell package, per the admin-renderer design. +// First collection per id is tracked alone, and a set is allocated only on the +// first clash, keeping allocations O(duplicates) on million-volume clusters. +func findDuplicateVolumeIdsForExport(topo *master_pb.TopologyInfo) []DuplicateVolumeId { + firstCollectionByVid := make(map[uint32]string) + collectionsByVid := make(map[uint32]map[string]struct{}) + note := func(vid uint32, collection string) { + if collections := collectionsByVid[vid]; collections != nil { + collections[collection] = struct{}{} + return + } + first, seen := firstCollectionByVid[vid] + if !seen { + firstCollectionByVid[vid] = collection + return + } + if first == collection { + return + } + collectionsByVid[vid] = map[string]struct{}{first: {}, collection: {}} + } + for _, dc := range topo.DataCenterInfos { + for _, rack := range dc.RackInfos { + for _, node := range rack.DataNodeInfos { + for _, disk := range node.DiskInfos { + for _, vi := range disk.VolumeInfos { + note(vi.Id, vi.Collection) + } + for _, ec := range disk.EcShardInfos { + note(ec.Id, ec.Collection) + } + } + } + } + } + + duplicates := make([]DuplicateVolumeId, 0, len(collectionsByVid)) + for vid, set := range collectionsByVid { + names := make([]string, 0, len(set)) + for name := range set { + names = append(names, name) + } + sort.Strings(names) + duplicates = append(duplicates, DuplicateVolumeId{VolumeId: vid, Collections: names}) + } + sort.Slice(duplicates, func(i, j int) bool { return duplicates[i].VolumeId < duplicates[j].VolumeId }) + return duplicates +} diff --git a/weed/admin/dash/volume_export_test.go b/weed/admin/dash/volume_export_test.go new file mode 100644 index 000000000..e3ed0cd30 --- /dev/null +++ b/weed/admin/dash/volume_export_test.go @@ -0,0 +1,129 @@ +package dash + +import ( + "testing" + + "github.com/seaweedfs/seaweedfs/weed/pb/master_pb" +) + +func TestBuildExportVolumeDerivedFields(t *testing.T) { + v := buildExportVolume(&master_pb.VolumeInformationMessage{ + Id: 7, + Collection: "photos", + Size: 100, + DeletedByteCount: 25, + ReplicaPlacement: 0, + ModifiedAtSecond: 1700000000, + }, 1000) + + if v.GarbageRatio != 0.25 { + t.Errorf("garbage ratio = %v, want 0.25", v.GarbageRatio) + } + if v.FullnessRatio != 0.1 { + t.Errorf("fullness ratio = %v, want 0.1", v.FullnessRatio) + } + if v.ReplicaPlacement != "000" { + t.Errorf("replica placement = %q, want 000", v.ReplicaPlacement) + } + if v.ModifiedAt == "" { + t.Error("modified_at should be set when modified_at_second > 0") + } +} + +func TestBuildExportDiskFilterAndTotals(t *testing.T) { + disk := &master_pb.DiskInfo{ + Type: "hdd", + VolumeCount: 2, + VolumeInfos: []*master_pb.VolumeInformationMessage{ + {Id: 1, Collection: "keep", Size: 10, FileCount: 3, DeleteCount: 1, DeletedByteCount: 2}, + {Id: 2, Collection: "drop", Size: 99, FileCount: 9}, + }, + EcShardInfos: []*master_pb.VolumeEcShardInformationMessage{ + {Id: 3, Collection: "keep", EcIndexBits: 0b111, ShardSizes: []int64{4, 4, 4}, FileCount: 5}, + {Id: 4, Collection: "drop", EcIndexBits: 0b1, ShardSizes: []int64{7}}, + }, + } + + var totals VolumeExportTotals + got := buildExportDisk(disk, "keep", 1000, &totals) + if got == nil { + t.Fatal("expected disk with matching records, got nil") + } + if len(got.Volumes) != 1 || got.Volumes[0].Id != 1 { + t.Errorf("expected only volume 1 after filter, got %+v", got.Volumes) + } + if len(got.EcShards) != 1 || got.EcShards[0].Id != 3 { + t.Errorf("expected only ec shard 3 after filter, got %+v", got.EcShards) + } + if got.VolumeCount != 2 { + t.Errorf("disk VolumeCount should stay physical (2), got %d", got.VolumeCount) + } + if got.EcShards[0].TotalSize != 12 { + t.Errorf("ec shard total size = %d, want 12", got.EcShards[0].TotalSize) + } + + if totals.VolumeCount != 1 || totals.EcShardCount != 1 { + t.Errorf("totals counts = vol %d ec %d, want 1/1", totals.VolumeCount, totals.EcShardCount) + } + // 10 (volume size) + 12 (kept ec shard sizes). + if totals.TotalSize != 22 { + t.Errorf("totals TotalSize = %d, want 22", totals.TotalSize) + } + if totals.FileCount != 3 || totals.DeletedFileCount != 1 || totals.DeletedBytes != 2 { + t.Errorf("totals files = %d del %d delBytes %d, want 3/1/2", totals.FileCount, totals.DeletedFileCount, totals.DeletedBytes) + } +} + +func TestBuildExportDiskPrunesWhenNoMatch(t *testing.T) { + disk := &master_pb.DiskInfo{ + Type: "hdd", + VolumeInfos: []*master_pb.VolumeInformationMessage{{Id: 1, Collection: "other"}}, + } + var totals VolumeExportTotals + if got := buildExportDisk(disk, "keep", 0, &totals); got != nil { + t.Errorf("expected nil for disk with no matching records, got %+v", got) + } +} + +func TestFindDuplicateVolumeIdsForExport(t *testing.T) { + topo := &master_pb.TopologyInfo{ + DataCenterInfos: []*master_pb.DataCenterInfo{{ + RackInfos: []*master_pb.RackInfo{{ + DataNodeInfos: []*master_pb.DataNodeInfo{ + {DiskInfos: map[string]*master_pb.DiskInfo{"hdd": { + VolumeInfos: []*master_pb.VolumeInformationMessage{ + {Id: 5, Collection: "a"}, + {Id: 7, Collection: "shared"}, // replica below, same collection + }, + }}}, + {DiskInfos: map[string]*master_pb.DiskInfo{"hdd": { + VolumeInfos: []*master_pb.VolumeInformationMessage{ + {Id: 5, Collection: "b"}, // 5 now spans a + b -> duplicate + {Id: 7, Collection: "shared"}, // replica, not a duplicate + }, + EcShardInfos: []*master_pb.VolumeEcShardInformationMessage{ + {Id: 5, Collection: "c"}, // 5 also as EC in c + }, + }}}, + }, + }}, + }}, + } + + dups := findDuplicateVolumeIdsForExport(topo) + if len(dups) != 1 { + t.Fatalf("expected exactly 1 duplicate, got %+v", dups) + } + if dups[0].VolumeId != 5 { + t.Errorf("duplicate volume id = %d, want 5", dups[0].VolumeId) + } + want := []string{"a", "b", "c"} + if len(dups[0].Collections) != len(want) { + t.Fatalf("collections = %v, want %v", dups[0].Collections, want) + } + for i := range want { + if dups[0].Collections[i] != want[i] { + t.Errorf("collections[%d] = %q, want %q", i, dups[0].Collections[i], want[i]) + } + } +} diff --git a/weed/admin/handlers/admin_handlers.go b/weed/admin/handlers/admin_handlers.go index 4ac8f717f..fee4b198e 100644 --- a/weed/admin/handlers/admin_handlers.go +++ b/weed/admin/handlers/admin_handlers.go @@ -250,6 +250,7 @@ func (h *AdminHandlers) registerAPIRoutes(api *mux.Router, enforceWrite bool) { filesApi.HandleFunc("/metadata", h.fileBrowserHandlers.ExportMetadata).Methods(http.MethodGet) volumeApi := api.PathPrefix("/volumes").Subrouter() + volumeApi.HandleFunc("/export", h.clusterHandlers.ExportClusterVolumes).Methods(http.MethodGet) volumeApi.Handle("/{id}/{server}/vacuum", wrapWrite(h.clusterHandlers.VacuumVolume)).Methods(http.MethodPost) pluginApi := api.PathPrefix("/plugin").Subrouter() diff --git a/weed/admin/handlers/cluster_handlers.go b/weed/admin/handlers/cluster_handlers.go index ae454b1a0..fe6734b60 100644 --- a/weed/admin/handlers/cluster_handlers.go +++ b/weed/admin/handlers/cluster_handlers.go @@ -1,14 +1,19 @@ package handlers import ( + "encoding/json" + "fmt" "math" + "mime" "net/http" "strconv" + "time" "github.com/gorilla/mux" "github.com/seaweedfs/seaweedfs/weed/admin/dash" "github.com/seaweedfs/seaweedfs/weed/admin/view/app" "github.com/seaweedfs/seaweedfs/weed/admin/view/layout" + "github.com/seaweedfs/seaweedfs/weed/glog" ) // ClusterHandlers contains all the HTTP handlers for cluster management @@ -88,6 +93,30 @@ func (h *ClusterHandlers) ShowClusterVolumes(w http.ResponseWriter, r *http.Requ } } +// ExportClusterVolumes streams the full-cluster volume list as a downloadable +// JSON report: every volume and EC shard across the topology, with more fields +// than the paginated table (a superset of the volume.list shell command). +func (h *ClusterHandlers) ExportClusterVolumes(w http.ResponseWriter, r *http.Request) { + collection := r.URL.Query().Get("collection") // Optional collection filter + + export, err := h.adminServer.ExportClusterVolumeList(r.Context(), collection, time.Now().UTC()) + if err != nil { + writeJSONError(w, http.StatusInternalServerError, "Failed to export volume list: "+err.Error()) + return + } + + filename := fmt.Sprintf("seaweedfs-volumes-%s.json", export.GeneratedAt.Format("20060102-150405")) + w.Header().Set("Content-Type", "application/json") + w.Header().Set("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": filename})) + + enc := json.NewEncoder(w) + enc.SetIndent("", " ") + if err := enc.Encode(export); err != nil { + // The response is already streaming, so we can only log a late failure. + glog.Errorf("export volume list: encode failed: %v", err) + } +} + // ShowVolumeDetails renders the volume details page func (h *ClusterHandlers) ShowVolumeDetails(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) diff --git a/weed/admin/static/js/admin.js b/weed/admin/static/js/admin.js index f93d8315e..ad9db59c0 100644 --- a/weed/admin/static/js/admin.js +++ b/weed/admin/static/js/admin.js @@ -736,6 +736,18 @@ function exportVolumes() { downloadCSV(csv, 'seaweedfs-volumes.csv'); } +// Export the full cluster volume list (all volumes + EC shards, more fields than +// the table) as a JSON report served by the admin API. Carries the active +// collection filter from the page URL. +function exportVolumeList() { + let url = basePath('/api/volumes/export'); + const collection = new URLSearchParams(window.location.search).get('collection'); + if (collection) { + url += '?collection=' + encodeURIComponent(collection); + } + window.open(url, '_blank'); +} + // Export collections data as CSV function exportCollections() { const table = document.getElementById('collectionsTable'); diff --git a/weed/admin/view/app/cluster_volumes.templ b/weed/admin/view/app/cluster_volumes.templ index 7cee0474e..a1b4bcb44 100644 --- a/weed/admin/view/app/cluster_volumes.templ +++ b/weed/admin/view/app/cluster_volumes.templ @@ -32,7 +32,10 @@ templ ClusterVolumes(data dash.ClusterVolumesData) { + diff --git a/weed/admin/view/app/cluster_volumes_templ.go b/weed/admin/view/app/cluster_volumes_templ.go index 9fca3108f..50421b01a 100644 --- a/weed/admin/view/app/cluster_volumes_templ.go +++ b/weed/admin/view/app/cluster_volumes_templ.go @@ -111,14 +111,14 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { return templ_7745c5c3_Err } } - templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, ">500 per page
Total Volumes
") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, ">500 per page
Total Volumes
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, 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: 53, Col: 73} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 56, Col: 73} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -147,7 +147,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(data.SingleCollection) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 78, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 81, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -157,7 +157,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.CollectionCount)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 80, Col: 80} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 83, Col: 80} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -187,7 +187,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(data.SingleDataCenter) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 106, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 109, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -197,7 +197,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var8 string templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.DataCenterCount)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 108, Col: 80} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 111, Col: 80} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) if templ_7745c5c3_Err != nil { @@ -227,7 +227,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var9 string templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(data.SingleRack) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 134, Col: 56} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 137, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) if templ_7745c5c3_Err != nil { @@ -237,7 +237,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var10 string templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.RackCount)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 136, Col: 74} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 139, Col: 74} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) if templ_7745c5c3_Err != nil { @@ -267,7 +267,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var11 string templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(data.SingleDiskType) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 162, Col: 60} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 165, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) if templ_7745c5c3_Err != nil { @@ -277,7 +277,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var12 string templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(data.AllDiskTypes, ", ")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 164, Col: 78} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 167, Col: 78} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) if templ_7745c5c3_Err != nil { @@ -307,7 +307,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var13 string templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(data.SingleVersion) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 190, Col: 59} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 193, Col: 59} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) if templ_7745c5c3_Err != nil { @@ -317,7 +317,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var14 string templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(strings.Join(data.AllVersions, ", ")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 192, Col: 77} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 195, Col: 77} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) if templ_7745c5c3_Err != nil { @@ -331,7 +331,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var15 string templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(data.TotalSize)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 213, Col: 64} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 216, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) if templ_7745c5c3_Err != nil { @@ -472,7 +472,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var16 string templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", volume.Id)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 317, Col: 94} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 320, Col: 94} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) if templ_7745c5c3_Err != nil { @@ -485,7 +485,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var17 string templ_7745c5c3_Var17, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", volume.Id)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 319, Col: 77} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 322, Col: 77} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var17)) if templ_7745c5c3_Err != nil { @@ -498,7 +498,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var18 templ.SafeURL templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinURLErrs(templ.SafeURL(fmt.Sprintf("//%s/ui/index.html", volume.Server))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 323, Col: 116} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 326, Col: 116} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) if templ_7745c5c3_Err != nil { @@ -511,7 +511,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var19 string templ_7745c5c3_Var19, templ_7745c5c3_Err = templ.JoinStringErrs(volume.Server) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 324, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 327, Col: 62} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19)) if templ_7745c5c3_Err != nil { @@ -529,7 +529,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var20 string templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(volume.DataCenter) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 330, Col: 105} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 333, Col: 105} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20)) if templ_7745c5c3_Err != nil { @@ -548,7 +548,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var21 string templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(volume.Rack) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 335, Col: 99} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 338, Col: 99} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) if templ_7745c5c3_Err != nil { @@ -572,7 +572,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var22 templ.SafeURL templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, "/storage/volumes?collection=default")) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 341, Col: 115} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 344, Col: 115} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) if templ_7745c5c3_Err != nil { @@ -590,7 +590,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var23 templ.SafeURL templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinURLErrs(dash.PUrl(ctx, fmt.Sprintf("/storage/volumes?collection=%s", volume.Collection))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 345, Col: 142} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 348, Col: 142} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) if templ_7745c5c3_Err != nil { @@ -603,7 +603,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var24 string templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(volume.Collection) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 346, Col: 107} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 349, Col: 107} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) if templ_7745c5c3_Err != nil { @@ -626,7 +626,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var25 string templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(int64(volume.Size))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 351, Col: 100} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 354, Col: 100} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25)) if templ_7745c5c3_Err != nil { @@ -648,7 +648,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { return 0 }())) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 366, Col: 49} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 369, Col: 49} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26)) if templ_7745c5c3_Err != nil { @@ -661,7 +661,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var27 string templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("Active: %s", formatBytes(int64(volume.Size-volume.DeletedByteCount)))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 367, Col: 132} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 370, Col: 132} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) if templ_7745c5c3_Err != nil { @@ -683,7 +683,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { return 0 }())) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 380, Col: 49} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 383, Col: 49} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28)) if templ_7745c5c3_Err != nil { @@ -696,7 +696,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var29 string templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("Garbage: %s", formatBytes(int64(volume.DeletedByteCount)))) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 381, Col: 119} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 384, Col: 119} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29)) if templ_7745c5c3_Err != nil { @@ -714,7 +714,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { return "N/A" }()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 390, Col: 39} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 393, Col: 39} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) if templ_7745c5c3_Err != nil { @@ -727,7 +727,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var31 string templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", volume.FileCount)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 394, Col: 64} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 397, Col: 64} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var31)) if templ_7745c5c3_Err != nil { @@ -740,7 +740,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var32 string templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%03d", volume.ReplicaPlacement)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 396, Col: 101} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 399, Col: 101} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32)) if templ_7745c5c3_Err != nil { @@ -758,7 +758,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var33 string templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(volume.DiskType) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 400, Col: 95} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 403, Col: 95} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33)) if templ_7745c5c3_Err != nil { @@ -777,7 +777,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var34 string templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("v%d", volume.Version)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 405, Col: 111} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 408, Col: 111} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34)) if templ_7745c5c3_Err != nil { @@ -795,7 +795,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var35 string templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", volume.Id)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 411, Col: 121} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 414, Col: 121} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) if templ_7745c5c3_Err != nil { @@ -808,7 +808,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var36 string templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", volume.Id)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 416, Col: 100} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 419, Col: 100} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36)) if templ_7745c5c3_Err != nil { @@ -821,7 +821,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var37 string templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(volume.Server) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 417, Col: 82} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 420, Col: 82} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37)) if templ_7745c5c3_Err != nil { @@ -839,7 +839,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var38 string templ_7745c5c3_Var38, 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: 432, Col: 98} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 435, Col: 98} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var38)) if templ_7745c5c3_Err != nil { @@ -852,7 +852,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var39 string templ_7745c5c3_Var39, 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: 432, Col: 180} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 435, Col: 180} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) if templ_7745c5c3_Err != nil { @@ -865,7 +865,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var40 string templ_7745c5c3_Var40, 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: 432, Col: 222} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 435, Col: 222} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) if templ_7745c5c3_Err != nil { @@ -883,7 +883,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var41 string templ_7745c5c3_Var41, 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: 438, Col: 77} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 441, Col: 77} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) if templ_7745c5c3_Err != nil { @@ -896,7 +896,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var42 string templ_7745c5c3_Var42, 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: 438, Col: 117} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 441, Col: 117} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42)) if templ_7745c5c3_Err != nil { @@ -924,7 +924,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var43 string templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.CurrentPage-1)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 452, Col: 138} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 455, Col: 138} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var43)) if templ_7745c5c3_Err != nil { @@ -953,7 +953,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var44 string templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 468, Col: 93} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 471, Col: 93} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44)) if templ_7745c5c3_Err != nil { @@ -971,7 +971,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var45 string templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 472, Col: 125} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 475, Col: 125} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45)) if templ_7745c5c3_Err != nil { @@ -984,7 +984,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var46 string templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 472, Col: 148} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 475, Col: 148} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46)) if templ_7745c5c3_Err != nil { @@ -1008,7 +1008,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var47 string templ_7745c5c3_Var47, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.CurrentPage+1)) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 480, Col: 138} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 483, Col: 138} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var47)) if templ_7745c5c3_Err != nil { @@ -1042,7 +1042,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component { var templ_7745c5c3_Var48 string templ_7745c5c3_Var48, 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: 510, Col: 81} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 513, Col: 81} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var48)) if templ_7745c5c3_Err != nil {