aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorchrislu <chris.lu@gmail.com>2025-07-04 12:49:34 -0700
committerchrislu <chris.lu@gmail.com>2025-07-04 12:49:34 -0700
commit84d4ea0995a9cc01808f3fedbf1b671a6538be32 (patch)
tree3971a806257c01b8f5b8173242ed6126af635ab2
parent2cfe079a1fac21d5d654d0e6ed2f1996d5b89209 (diff)
downloadseaweedfs-84d4ea0995a9cc01808f3fedbf1b671a6538be32.tar.xz
seaweedfs-84d4ea0995a9cc01808f3fedbf1b671a6538be32.zip
show volume details
-rw-r--r--weed/admin/dash/admin_server.go162
-rw-r--r--weed/admin/handlers/admin_handlers.go2
-rw-r--r--weed/admin/handlers/cluster_handlers.go39
-rw-r--r--weed/admin/view/app/cluster_volumes.templ43
-rw-r--r--weed/admin/view/app/cluster_volumes_templ.go165
-rw-r--r--weed/admin/view/app/volume_details.templ425
-rw-r--r--weed/admin/view/app/volume_details_templ.go690
7 files changed, 1391 insertions, 135 deletions
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 {
<tr>
<td>
- <code>{fmt.Sprintf("%d", volume.ID)}</code>
+ <code>{fmt.Sprintf("%d", volume.Id)}</code>
</td>
<td>
<a href={templ.SafeURL(fmt.Sprintf("http://%s", volume.Server))} target="_blank" class="text-decoration-none">
@@ -343,11 +343,11 @@ templ ClusterVolumes(data dash.ClusterVolumesData) {
}
</td>
}
- <td>{formatBytes(volume.Size)}</td>
- <td>{fmt.Sprintf("%d", volume.FileCount)}</td>
- <td>
- <span class="badge bg-info">{volume.Replication}</span>
- </td>
+ <td>{formatBytes(int64(volume.Size))}</td>
+ <td>{fmt.Sprintf("%d", volume.FileCount)}</td>
+ <td>
+ <span class="badge bg-info">{fmt.Sprintf("%03d", volume.ReplicaPlacement)}</span>
+ </td>
if data.ShowDiskTypeColumn {
<td>
<span class="badge bg-primary">{volume.DiskType}</span>
@@ -360,8 +360,8 @@ templ ClusterVolumes(data dash.ClusterVolumesData) {
}
<td>
<div class="btn-group btn-group-sm">
- <button type="button" class="btn btn-outline-primary btn-sm"
- title="View Details">
+ <button type="button" class="btn btn-outline-primary btn-sm view-details-btn"
+ title="View Details" data-volume-id={fmt.Sprintf("%d", volume.Id)}>
<i class="fas fa-eye"></i>
</button>
<button type="button" class="btn btn-outline-secondary btn-sm"
@@ -480,6 +480,15 @@ templ ClusterVolumes(data dash.ClusterVolumesData) {
goToPage(page);
});
});
+
+ // Add click handlers to view details buttons
+ document.querySelectorAll('.view-details-btn').forEach(button => {
+ button.addEventListener('click', function(e) {
+ e.preventDefault();
+ const volumeId = this.getAttribute('data-volume-id');
+ viewVolumeDetails(volumeId);
+ });
+ });
});
function goToPage(page) {
@@ -516,15 +525,25 @@ templ ClusterVolumes(data dash.ClusterVolumesData) {
// TODO: Implement volume export functionality
alert('Export functionality to be implemented');
}
+
+ function viewVolumeDetails(volumeId) {
+ // Get the server from the current row
+ const button = event.target.closest('button');
+ const row = button.closest('tr');
+ const serverCell = row.querySelector('td:nth-child(2) a');
+ const server = serverCell ? serverCell.textContent.trim() : 'unknown';
+
+ window.location.href = `/cluster/volumes/${volumeId}/${encodeURIComponent(server)}`;
+ }
</script>
}
-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
@@ -532,7 +551,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 != "" {
@@ -542,7 +561,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
diff --git a/weed/admin/view/app/cluster_volumes_templ.go b/weed/admin/view/app/cluster_volumes_templ.go
index 8d9f1cbe4..3133ab8bd 100644
--- a/weed/admin/view/app/cluster_volumes_templ.go
+++ b/weed/admin/view/app/cluster_volumes_templ.go
@@ -457,7 +457,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var15 string
- templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", volume.ID))
+ templ_7745c5c3_Var15, 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: 315, Col: 79}
}
@@ -586,9 +586,9 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var23 string
- templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(formatBytes(volume.Size))
+ templ_7745c5c3_Var23, 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: 346, Col: 69}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 346, Col: 100}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
if templ_7745c5c3_Err != nil {
@@ -601,7 +601,7 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component {
var templ_7745c5c3_Var24 string
templ_7745c5c3_Var24, 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: 347, Col: 80}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 347, Col: 64}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
if templ_7745c5c3_Err != nil {
@@ -612,9 +612,9 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var25 string
- templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(volume.Replication)
+ templ_7745c5c3_Var25, 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: 349, Col: 91}
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 349, Col: 101}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
if templ_7745c5c3_Err != nil {
@@ -662,228 +662,241 @@ func ClusterVolumes(data dash.ClusterVolumesData) templ.Component {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "<td><div class=\"btn-group btn-group-sm\"><button type=\"button\" class=\"btn btn-outline-primary btn-sm\" title=\"View Details\"><i class=\"fas fa-eye\"></i></button> <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" title=\"Compact\"><i class=\"fas fa-compress-alt\"></i></button> <button type=\"button\" class=\"btn btn-outline-warning btn-sm\" title=\"Fix\"><i class=\"fas fa-wrench\"></i></button></div></td></tr>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 76, "<td><div class=\"btn-group btn-group-sm\"><button type=\"button\" class=\"btn btn-outline-primary btn-sm view-details-btn\" title=\"View Details\" data-volume-id=\"")
+ 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", volume.Id))
+ if templ_7745c5c3_Err != nil {
+ return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/app/cluster_volumes.templ`, Line: 364, Col: 121}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var28))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "\"><i class=\"fas fa-eye\"></i></button> <button type=\"button\" class=\"btn btn-outline-secondary btn-sm\" title=\"Compact\"><i class=\"fas fa-compress-alt\"></i></button> <button type=\"button\" class=\"btn btn-outline-warning btn-sm\" title=\"Fix\"><i class=\"fas fa-wrench\"></i></button></div></td></tr>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 77, "</tbody></table></div><!-- Volume Summary --> <div class=\"d-flex justify-content-between align-items-center mt-3\"><div><small class=\"text-muted\">Showing ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 78, "</tbody></table></div><!-- Volume Summary --> <div class=\"d-flex justify-content-between align-items-center mt-3\"><div><small class=\"text-muted\">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</small></div>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, " volumes</small></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.TotalPages > 1 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 81, "<div><small class=\"text-muted\">Page ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 82, "<div><small class=\"text-muted\">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, "</small></div>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "</small></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 84, "</div><!-- Pagination Controls --> ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "</div><!-- Pagination Controls --> ")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.TotalPages > 1 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 85, "<div class=\"d-flex justify-content-center mt-3\"><nav aria-label=\"Volumes pagination\"><ul class=\"pagination pagination-sm mb-0\"><!-- Previous Button -->")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<div class=\"d-flex justify-content-center mt-3\"><nav aria-label=\"Volumes pagination\"><ul class=\"pagination pagination-sm mb-0\"><!-- Previous Button -->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.CurrentPage > 1 {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 86, "<li class=\"page-item\"><a class=\"page-link pagination-link\" href=\"#\" data-page=\"")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "<li class=\"page-item\"><a class=\"page-link pagination-link\" href=\"#\" data-page=\"")
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.CurrentPage-1))
+ var templ_7745c5c3_Var34 string
+ templ_7745c5c3_Var34, 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: 407, Col: 138}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 87, "\"><i class=\"fas fa-chevron-left\"></i></a></li>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "\"><i class=\"fas fa-chevron-left\"></i></a></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 88, "<li class=\"page-item disabled\"><span class=\"page-link\"><i class=\"fas fa-chevron-left\"></i></span></li>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "<li class=\"page-item disabled\"><span class=\"page-link\"><i class=\"fas fa-chevron-left\"></i></span></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 89, "<!-- Page Numbers -->")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "<!-- Page Numbers -->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
for i := maxInt(1, data.CurrentPage-2); i <= minInt(data.TotalPages, data.CurrentPage+2); i++ {
if i == data.CurrentPage {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 90, "<li class=\"page-item active\"><span class=\"page-link\">")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 91, "<li class=\"page-item active\"><span class=\"page-link\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var34 string
- templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i))
+ var templ_7745c5c3_Var35 string
+ templ_7745c5c3_Var35, 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: 423, Col: 93}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var34))
+ _, 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, 91, "</span></li>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "</span></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 92, "<li class=\"page-item\"><a class=\"page-link pagination-link\" href=\"#\" data-page=\"")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 93, "<li class=\"page-item\"><a class=\"page-link pagination-link\" href=\"#\" data-page=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var35 string
- templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i))
+ var templ_7745c5c3_Var36 string
+ templ_7745c5c3_Var36, 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: 427, Col: 125}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35))
+ _, 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, 93, "\">")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 94, "\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var36 string
- templ_7745c5c3_Var36, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", i))
+ var templ_7745c5c3_Var37 string
+ templ_7745c5c3_Var37, 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: 427, Col: 148}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var36))
+ _, 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, 94, "</a></li>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "</a></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 95, "<!-- Next Button -->")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "<!-- Next Button -->")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if data.CurrentPage < data.TotalPages {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 96, "<li class=\"page-item\"><a class=\"page-link pagination-link\" href=\"#\" data-page=\"")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 97, "<li class=\"page-item\"><a class=\"page-link pagination-link\" href=\"#\" data-page=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
- var templ_7745c5c3_Var37 string
- templ_7745c5c3_Var37, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", data.CurrentPage+1))
+ var templ_7745c5c3_Var38 string
+ templ_7745c5c3_Var38, 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: 435, Col: 138}
}
- _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var37))
+ _, 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, 97, "\"><i class=\"fas fa-chevron-right\"></i></a></li>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "\"><i class=\"fas fa-chevron-right\"></i></a></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 98, "<li class=\"page-item disabled\"><span class=\"page-link\"><i class=\"fas fa-chevron-right\"></i></span></li>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "<li class=\"page-item disabled\"><span class=\"page-link\"><i class=\"fas fa-chevron-right\"></i></span></li>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 99, "</ul></nav></div>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "</ul></nav></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
} else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 100, "<div class=\"text-center py-5\"><i class=\"fas fa-database fa-3x text-muted mb-3\"></i><h5 class=\"text-muted\">No Volumes Found</h5><p class=\"text-muted\">No volumes are currently available in the cluster.</p></div>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "<div class=\"text-center py-5\"><i class=\"fas fa-database fa-3x text-muted mb-3\"></i><h5 class=\"text-muted\">No Volumes Found</h5><p class=\"text-muted\">No volumes are currently available in the cluster.</p></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 101, "</div></div><!-- Last Updated --><div class=\"row\"><div class=\"col-12\"><small class=\"text-muted\"><i class=\"fas fa-clock me-1\"></i> Last updated: ")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 102, "</div></div><!-- Last Updated --><div class=\"row\"><div class=\"col-12\"><small class=\"text-muted\"><i class=\"fas fa-clock me-1\"></i> 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, "</small></div></div></div><!-- JavaScript for pagination and sorting --><script>\n // Initialize pagination links when page loads\n document.addEventListener('DOMContentLoaded', function() {\n // Add click handlers to pagination links\n document.querySelectorAll('.pagination-link').forEach(link => {\n link.addEventListener('click', function(e) {\n e.preventDefault();\n const page = this.getAttribute('data-page');\n goToPage(page);\n });\n });\n });\n \n function goToPage(page) {\n const url = new URL(window.location);\n url.searchParams.set('page', page);\n window.location.href = url.toString();\n }\n \n function changePageSize() {\n const pageSize = document.getElementById('pageSizeSelect').value;\n const url = new URL(window.location);\n url.searchParams.set('pageSize', pageSize);\n url.searchParams.set('page', '1'); // Reset to first page\n window.location.href = url.toString();\n }\n \n function sortTable(column) {\n const url = new URL(window.location);\n const currentSort = url.searchParams.get('sortBy');\n const currentOrder = url.searchParams.get('sortOrder') || 'asc';\n \n let newOrder = 'asc';\n if (currentSort === column && currentOrder === 'asc') {\n newOrder = 'desc';\n }\n \n url.searchParams.set('sortBy', column);\n url.searchParams.set('sortOrder', newOrder);\n url.searchParams.set('page', '1'); // Reset to first page\n window.location.href = url.toString();\n }\n \n function exportVolumes() {\n // TODO: Implement volume export functionality\n alert('Export functionality to be implemented');\n }\n </script>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 103, "</small></div></div></div><!-- JavaScript for pagination and sorting --><script>\n // Initialize pagination links when page loads\n document.addEventListener('DOMContentLoaded', function() {\n // Add click handlers to pagination links\n document.querySelectorAll('.pagination-link').forEach(link => {\n link.addEventListener('click', function(e) {\n e.preventDefault();\n const page = this.getAttribute('data-page');\n goToPage(page);\n });\n });\n \n // Add click handlers to view details buttons\n document.querySelectorAll('.view-details-btn').forEach(button => {\n button.addEventListener('click', function(e) {\n e.preventDefault();\n const volumeId = this.getAttribute('data-volume-id');\n viewVolumeDetails(volumeId);\n });\n });\n });\n \n function goToPage(page) {\n const url = new URL(window.location);\n url.searchParams.set('page', page);\n window.location.href = url.toString();\n }\n \n function changePageSize() {\n const pageSize = document.getElementById('pageSizeSelect').value;\n const url = new URL(window.location);\n url.searchParams.set('pageSize', pageSize);\n url.searchParams.set('page', '1'); // Reset to first page\n window.location.href = url.toString();\n }\n \n function sortTable(column) {\n const url = new URL(window.location);\n const currentSort = url.searchParams.get('sortBy');\n const currentOrder = url.searchParams.get('sortOrder') || 'asc';\n \n let newOrder = 'asc';\n if (currentSort === column && currentOrder === 'asc') {\n newOrder = 'desc';\n }\n \n url.searchParams.set('sortBy', column);\n url.searchParams.set('sortOrder', newOrder);\n url.searchParams.set('page', '1'); // Reset to first page\n window.location.href = url.toString();\n }\n \n function exportVolumes() {\n // TODO: Implement volume export functionality\n alert('Export functionality to be implemented');\n }\n \n function viewVolumeDetails(volumeId) {\n // Get the server from the current row\n const button = event.target.closest('button');\n const row = button.closest('tr');\n const serverCell = row.querySelector('td:nth-child(2) a');\n const server = serverCell ? serverCell.textContent.trim() : 'unknown';\n \n window.location.href = `/cluster/volumes/${volumeId}/${encodeURIComponent(server)}`;\n }\n </script>")
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, "<i class=\"fas fa-sort text-muted ms-1\"></i>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "<i class=\"fas fa-sort text-muted ms-1\"></i>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else if currentOrder == "asc" {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 104, "<i class=\"fas fa-sort-up text-primary ms-1\"></i>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "<i class=\"fas fa-sort-up text-primary ms-1\"></i>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
} else {
- templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 105, "<i class=\"fas fa-sort-down text-primary ms-1\"></i>")
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 106, "<i class=\"fas fa-sort-down text-primary ms-1\"></i>")
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) {
+ <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
+ <div>
+ <h1 class="h2">
+ <i class="fas fa-database me-2"></i>Volume Details
+ </h1>
+ <nav aria-label="breadcrumb">
+ <ol class="breadcrumb">
+ <li class="breadcrumb-item"><a href="/admin" class="text-decoration-none">Dashboard</a></li>
+ <li class="breadcrumb-item"><a href="/cluster/volumes" class="text-decoration-none">Volumes</a></li>
+ <li class="breadcrumb-item active" aria-current="page">Volume {fmt.Sprintf("%d", data.Volume.Id)}</li>
+ </ol>
+ </nav>
+ </div>
+ <div class="btn-toolbar mb-2 mb-md-0">
+ <div class="btn-group me-2">
+ <button type="button" class="btn btn-sm btn-outline-secondary" onclick="history.back()">
+ <i class="fas fa-arrow-left me-1"></i>Back
+ </button>
+ <button type="button" class="btn btn-sm btn-outline-primary" onclick="window.location.reload()">
+ <i class="fas fa-refresh me-1"></i>Refresh
+ </button>
+ </div>
+ </div>
+ </div>
+
+ <div class="row">
+ <!-- Volume Information Card -->
+ <div class="col-lg-8">
+ <div class="card shadow mb-4">
+ <div class="card-header py-3">
+ <h6 class="m-0 font-weight-bold text-primary">
+ <i class="fas fa-info-circle me-2"></i>Volume Information
+ </h6>
+ </div>
+ <div class="card-body">
+ <div class="row">
+ <div class="col-md-6">
+ <div class="mb-3">
+ <label class="form-label"><strong>Volume ID:</strong></label>
+ <div><code class="fs-5">{fmt.Sprintf("%d", data.Volume.Id)}</code></div>
+ </div>
+ <div class="mb-3">
+ <label class="form-label"><strong>Server:</strong></label>
+ <div>
+ <a href={templ.SafeURL(fmt.Sprintf("http://%s", data.Volume.Server))} target="_blank" class="text-decoration-none">
+ {data.Volume.Server}
+ <i class="fas fa-external-link-alt ms-1 text-muted"></i>
+ </a>
+ </div>
+ </div>
+ <div class="mb-3">
+ <label class="form-label"><strong>Data Center:</strong></label>
+ <div><span class="badge bg-light text-dark">{data.Volume.DataCenter}</span></div>
+ </div>
+ <div class="mb-3">
+ <label class="form-label"><strong>Rack:</strong></label>
+ <div><span class="badge bg-light text-dark">{data.Volume.Rack}</span></div>
+ </div>
+ </div>
+ <div class="col-md-6">
+ <div class="mb-3">
+ <label class="form-label"><strong>Collection:</strong></label>
+ <div>
+ if data.Volume.Collection == "" {
+ <a href={templ.SafeURL("/cluster/volumes?collection=default")} class="text-decoration-none">
+ <span class="badge bg-secondary">default</span>
+ </a>
+ } else {
+ <a href={templ.SafeURL(fmt.Sprintf("/cluster/volumes?collection=%s", data.Volume.Collection))} class="text-decoration-none">
+ <span class="badge bg-secondary">{data.Volume.Collection}</span>
+ </a>
+ }
+ </div>
+ </div>
+ <div class="mb-3">
+ <label class="form-label"><strong>Replication:</strong></label>
+ <div><span class="badge bg-info">{fmt.Sprintf("%03d", data.Volume.ReplicaPlacement)}</span></div>
+ </div>
+ <div class="mb-3">
+ <label class="form-label"><strong>Disk Type:</strong></label>
+ <div>
+ <span class="badge bg-primary">
+ if data.Volume.DiskType == "" {
+ hdd
+ } else {
+ {data.Volume.DiskType}
+ }
+ </span>
+ </div>
+ </div>
+ <div class="mb-3">
+ <label class="form-label"><strong>Version:</strong></label>
+ <div><span class="badge bg-dark">{fmt.Sprintf("v%d", data.Volume.Version)}</span></div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- Statistics Card -->
+ <div class="col-lg-4">
+ <!-- Volume Statistics & Health Card -->
+ <div class="card shadow mb-4">
+ <div class="card-header py-3">
+ <h6 class="m-0 font-weight-bold text-primary">
+ <i class="fas fa-chart-pie me-2"></i>Volume Statistics & Health
+ </h6>
+ </div>
+ <div class="card-body">
+ <!-- Storage Metrics -->
+ <div class="row mb-3">
+ <div class="col-6">
+ <div class="text-center">
+ <div class="h4 mb-0 font-weight-bold text-success">
+ {formatBytes(int64(data.Volume.Size - data.Volume.DeletedByteCount))}
+ </div>
+ <small class="text-muted">Active Bytes</small>
+ </div>
+ </div>
+ <div class="col-6">
+ <div class="text-center">
+ <div class="h4 mb-0 font-weight-bold text-danger">
+ {formatBytes(int64(data.Volume.DeletedByteCount))}
+ </div>
+ <small class="text-muted">Deleted Bytes</small>
+ </div>
+ </div>
+ </div>
+
+
+
+ <!-- File Metrics -->
+ <div class="row mb-3">
+ <div class="col-6">
+ <div class="text-center">
+ <div class="h4 mb-0 font-weight-bold text-success">
+ {fmt.Sprintf("%d", data.Volume.FileCount)}
+ </div>
+ <small class="text-muted">Active Files</small>
+ </div>
+ </div>
+ <div class="col-6">
+ <div class="text-center">
+ <div class="h4 mb-0 font-weight-bold text-danger">
+ {fmt.Sprintf("%d", data.Volume.DeleteCount)}
+ </div>
+ <small class="text-muted">Deleted Files</small>
+ </div>
+ </div>
+ </div>
+
+
+
+ <!-- Storage Efficiency -->
+ if data.Volume.FileCount > 0 && data.Volume.Size > 0 {
+ <div class="mb-3">
+ <div class="d-flex justify-content-between align-items-center mb-1">
+ <small class="text-muted">Storage Efficiency</small>
+ <small class="text-muted">
+ {fmt.Sprintf("%.1f%%", float64(data.Volume.Size-data.Volume.DeletedByteCount)/float64(data.Volume.Size)*100)}
+ </small>
+ </div>
+ <div class="progress" style="height: 8px;">
+ <div class="progress-bar bg-info" role="progressbar"
+ style={fmt.Sprintf("width: %.1f%%", float64(data.Volume.Size-data.Volume.DeletedByteCount)/float64(data.Volume.Size)*100)}
+ aria-valuenow={fmt.Sprintf("%.1f", float64(data.Volume.Size-data.Volume.DeletedByteCount)/float64(data.Volume.Size)*100)}
+ aria-valuemin="0" aria-valuemax="100">
+ </div>
+ </div>
+ </div>
+ }
+
+ <hr class="my-3">
+
+ <!-- Status & Configuration -->
+ <div class="row mb-3">
+ <div class="col-12">
+ <div class="text-center mb-2">
+ if data.Volume.ReadOnly {
+ <span class="badge bg-warning fs-6 px-3 py-2">
+ <i class="fas fa-lock me-1"></i>Read Only
+ </span>
+ if data.Volume.Size >= data.VolumeSizeLimit {
+ <div class="mt-1">
+ <small class="text-muted">Size limit exceeded</small>
+ </div>
+ }
+ } else if data.VolumeSizeLimit > data.Volume.Size {
+ <span class="badge bg-success fs-6 px-3 py-2">
+ <i class="fas fa-edit me-1"></i>Read/Write
+ </span>
+ } else {
+ <span class="badge bg-warning fs-6 px-3 py-2">
+ <i class="fas fa-exclamation-triangle me-1"></i>Size Limit Reached
+ </span>
+ }
+ </div>
+ </div>
+ </div>
+
+ <!-- Maintenance Info -->
+ <div class="row mb-3">
+ <div class="col-6">
+ <div class="text-center">
+ <div class="h6 mb-0 font-weight-bold text-info">
+ #{fmt.Sprintf("%d", data.Volume.CompactRevision)}
+ </div>
+ <small class="text-muted">Compact Revision</small>
+ </div>
+ </div>
+ <div class="col-6">
+ <div class="text-center">
+ <div class="h6 mb-0 font-weight-bold text-secondary">
+ if data.Volume.ModifiedAtSecond > 0 {
+ {formatTimestamp(data.Volume.ModifiedAtSecond)}
+ } else {
+ <span class="text-muted">Never modified</span>
+ }
+ </div>
+ <small class="text-muted">Last Modified</small>
+ </div>
+ </div>
+ </div>
+
+ <!-- TTL Configuration -->
+ if data.Volume.Ttl > 0 {
+ <div class="mb-3 text-center">
+ <span class="badge bg-info fs-6 px-3 py-2">
+ <i class="fas fa-clock me-1"></i>{formatTTL(data.Volume.Ttl)}
+ </span>
+ <div class="mt-1">
+ <small class="text-muted">Time To Live</small>
+ </div>
+ </div>
+ }
+
+ <!-- Remote Storage Configuration -->
+ if data.Volume.RemoteStorageName != "" {
+ <hr class="my-3">
+ <div class="mb-2">
+ <div class="text-center">
+ <div class="h6 mb-1 font-weight-bold text-info">
+ <i class="fas fa-cloud me-1"></i>{data.Volume.RemoteStorageName}
+ </div>
+ <small class="text-muted">Remote Storage</small>
+ </div>
+ </div>
+ if data.Volume.RemoteStorageKey != "" {
+ <div class="text-center">
+ <div class="text-xs font-monospace bg-light p-2 rounded text-truncate" title={data.Volume.RemoteStorageKey}>
+ {data.Volume.RemoteStorageKey}
+ </div>
+ <small class="text-muted">Storage Key</small>
+ </div>
+ }
+ }
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- Replicas Card -->
+ if len(data.Replicas) > 0 {
+ <div class="row">
+ <div class="col-12">
+ <div class="card shadow mb-4">
+ <div class="card-header py-3">
+ <h6 class="m-0 font-weight-bold text-primary">
+ <i class="fas fa-copy me-2"></i>Replicas ({fmt.Sprintf("%d", data.ReplicationCount)})
+ </h6>
+ </div>
+ <div class="card-body">
+ <div class="table-responsive">
+ <table class="table table-hover">
+ <thead>
+ <tr>
+ <th>Server</th>
+ <th>Data Center</th>
+ <th>Rack</th>
+ <th>Size</th>
+ <th>File Count</th>
+ <th>Status</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ <!-- Primary Volume (current one) -->
+ <tr class="table-primary">
+ <td>
+ <strong>
+ <a href={templ.SafeURL(fmt.Sprintf("http://%s", data.Volume.Server))} target="_blank" class="text-decoration-none">
+ {data.Volume.Server}
+ <i class="fas fa-external-link-alt ms-1 text-muted"></i>
+ </a>
+ </strong>
+ <span class="badge bg-success ms-2">Primary</span>
+ </td>
+ <td><span class="badge bg-light text-dark">{data.Volume.DataCenter}</span></td>
+ <td><span class="badge bg-light text-dark">{data.Volume.Rack}</span></td>
+ <td>{formatBytes(int64(data.Volume.Size))}</td>
+ <td>{fmt.Sprintf("%d", data.Volume.FileCount)}</td>
+ <td><span class="badge bg-success">Active</span></td>
+ <td>
+ <span class="text-muted">Current Volume</span>
+ </td>
+ </tr>
+ <!-- Replica Volumes -->
+ for _, replica := range data.Replicas {
+ <tr>
+ <td>
+ <a href={templ.SafeURL(fmt.Sprintf("http://%s", replica.Server))} target="_blank" class="text-decoration-none">
+ {replica.Server}
+ <i class="fas fa-external-link-alt ms-1 text-muted"></i>
+ </a>
+ </td>
+ <td><span class="badge bg-light text-dark">{replica.DataCenter}</span></td>
+ <td><span class="badge bg-light text-dark">{replica.Rack}</span></td>
+ <td>{formatBytes(int64(replica.Size))}</td>
+ <td>{fmt.Sprintf("%d", replica.FileCount)}</td>
+ <td><span class="badge bg-info">Replica</span></td>
+ <td>
+ <a href={templ.SafeURL(fmt.Sprintf("/cluster/volumes/%d/%s", replica.Id, replica.Server))} class="btn btn-sm btn-outline-primary">
+ <i class="fas fa-eye me-1"></i>View
+ </a>
+ </td>
+ </tr>
+ }
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ }
+
+ <!-- Actions Card -->
+ <div class="row">
+ <div class="col-12">
+ <div class="card shadow mb-4">
+ <div class="card-header py-3">
+ <h6 class="m-0 font-weight-bold text-primary">
+ <i class="fas fa-tools me-2"></i>Actions
+ </h6>
+ </div>
+ <div class="card-body">
+ <div class="btn-group" role="group">
+ <button type="button" class="btn btn-outline-secondary" title="Compact Volume">
+ <i class="fas fa-compress-alt me-1"></i>Compact
+ </button>
+ <button type="button" class="btn btn-outline-warning" title="Fix Volume">
+ <i class="fas fa-wrench me-1"></i>Fix
+ </button>
+ <button type="button" class="btn btn-outline-danger" title="Vacuum Volume">
+ <i class="fas fa-trash me-1"></i>Vacuum
+ </button>
+ </div>
+ <div class="mt-3">
+ <small class="text-muted">
+ <i class="fas fa-info-circle me-1"></i>
+ Use these actions to perform maintenance operations on the volume.
+ </small>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <!-- Last Updated -->
+ <div class="row">
+ <div class="col-12">
+ <small class="text-muted">
+ <i class="fas fa-clock me-1"></i>
+ Last updated: {data.LastUpdated.Format("2006-01-02 15:04:05")}
+ </small>
+ </div>
+ </div>
+}
+
+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, "<div class=\"d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom\"><div><h1 class=\"h2\"><i class=\"fas fa-database me-2\"></i>Volume Details</h1><nav aria-label=\"breadcrumb\"><ol class=\"breadcrumb\"><li class=\"breadcrumb-item\"><a href=\"/admin\" class=\"text-decoration-none\">Dashboard</a></li><li class=\"breadcrumb-item\"><a href=\"/cluster/volumes\" class=\"text-decoration-none\">Volumes</a></li><li class=\"breadcrumb-item active\" aria-current=\"page\">Volume ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var2 string
+ templ_7745c5c3_Var2, 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: 19, Col: 116}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "</li></ol></nav></div><div class=\"btn-toolbar mb-2 mb-md-0\"><div class=\"btn-group me-2\"><button type=\"button\" class=\"btn btn-sm btn-outline-secondary\" onclick=\"history.back()\"><i class=\"fas fa-arrow-left me-1\"></i>Back</button> <button type=\"button\" class=\"btn btn-sm btn-outline-primary\" onclick=\"window.location.reload()\"><i class=\"fas fa-refresh me-1\"></i>Refresh</button></div></div></div><div class=\"row\"><!-- Volume Information Card --><div class=\"col-lg-8\"><div class=\"card shadow mb-4\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\"><i class=\"fas fa-info-circle me-2\"></i>Volume Information</h6></div><div class=\"card-body\"><div class=\"row\"><div class=\"col-md-6\"><div class=\"mb-3\"><label class=\"form-label\"><strong>Volume ID:</strong></label><div><code class=\"fs-5\">")
+ 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, "</code></div></div><div class=\"mb-3\"><label class=\"form-label\"><strong>Server:</strong></label><div><a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var4 templ.SafeURL = templ.SafeURL(fmt.Sprintf("http://%s", data.Volume.Server))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "\" target=\"_blank\" class=\"text-decoration-none\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var5 string
+ templ_7745c5c3_Var5, 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: 55, Col: 59}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, " <i class=\"fas fa-external-link-alt ms-1 text-muted\"></i></a></div></div><div class=\"mb-3\"><label class=\"form-label\"><strong>Data Center:</strong></label><div><span class=\"badge bg-light text-dark\">")
+ 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, "</span></div></div><div class=\"mb-3\"><label class=\"form-label\"><strong>Rack:</strong></label><div><span class=\"badge bg-light text-dark\">")
+ 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, "</span></div></div></div><div class=\"col-md-6\"><div class=\"mb-3\"><label class=\"form-label\"><strong>Collection:</strong></label><div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if data.Volume.Collection == "" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "<a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var8 templ.SafeURL = templ.SafeURL("/cluster/volumes?collection=default")
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var8)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "\" class=\"text-decoration-none\"><span class=\"badge bg-secondary\">default</span></a>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "<a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var9 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/cluster/volumes?collection=%s", data.Volume.Collection))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var9)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "\" class=\"text-decoration-none\"><span class=\"badge bg-secondary\">")
+ 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, "</span></a>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "</div></div><div class=\"mb-3\"><label class=\"form-label\"><strong>Replication:</strong></label><div><span class=\"badge bg-info\">")
+ 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, "</span></div></div><div class=\"mb-3\"><label class=\"form-label\"><strong>Disk Type:</strong></label><div><span class=\"badge bg-primary\">")
+ 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, "</span></div></div><div class=\"mb-3\"><label class=\"form-label\"><strong>Version:</strong></label><div><span class=\"badge bg-dark\">")
+ 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, "</span></div></div></div></div></div></div></div><!-- Statistics Card --><div class=\"col-lg-4\"><!-- Volume Statistics & Health Card --><div class=\"card shadow mb-4\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\"><i class=\"fas fa-chart-pie me-2\"></i>Volume Statistics & Health</h6></div><div class=\"card-body\"><!-- Storage Metrics --><div class=\"row mb-3\"><div class=\"col-6\"><div class=\"text-center\"><div class=\"h4 mb-0 font-weight-bold text-success\">")
+ 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, "</div><small class=\"text-muted\">Active Bytes</small></div></div><div class=\"col-6\"><div class=\"text-center\"><div class=\"h4 mb-0 font-weight-bold text-danger\">")
+ 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, "</div><small class=\"text-muted\">Deleted Bytes</small></div></div></div><!-- File Metrics --><div class=\"row mb-3\"><div class=\"col-6\"><div class=\"text-center\"><div class=\"h4 mb-0 font-weight-bold text-success\">")
+ 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, "</div><small class=\"text-muted\">Active Files</small></div></div><div class=\"col-6\"><div class=\"text-center\"><div class=\"h4 mb-0 font-weight-bold text-danger\">")
+ 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, "</div><small class=\"text-muted\">Deleted Files</small></div></div></div><!-- Storage Efficiency -->")
+ 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, "<div class=\"mb-3\"><div class=\"d-flex justify-content-between align-items-center mb-1\"><small class=\"text-muted\">Storage Efficiency</small> <small class=\"text-muted\">")
+ 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, "</small></div><div class=\"progress\" style=\"height: 8px;\"><div class=\"progress-bar bg-info\" role=\"progressbar\" style=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var19 string
+ templ_7745c5c3_Var19, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues(fmt.Sprintf("width: %.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: 175, Col: 158}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var19))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "\" aria-valuenow=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var20 string
+ templ_7745c5c3_Var20, 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: 176, Col: 157}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "\" aria-valuemin=\"0\" aria-valuemax=\"100\"></div></div></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<hr class=\"my-3\"><!-- Status & Configuration --><div class=\"row mb-3\"><div class=\"col-12\"><div class=\"text-center mb-2\">")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if data.Volume.ReadOnly {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "<span class=\"badge bg-warning fs-6 px-3 py-2\"><i class=\"fas fa-lock me-1\"></i>Read Only</span> ")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if data.Volume.Size >= data.VolumeSizeLimit {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "<div class=\"mt-1\"><small class=\"text-muted\">Size limit exceeded</small></div>")
+ 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, "<span class=\"badge bg-success fs-6 px-3 py-2\"><i class=\"fas fa-edit me-1\"></i>Read/Write</span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ } else {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "<span class=\"badge bg-warning fs-6 px-3 py-2\"><i class=\"fas fa-exclamation-triangle me-1\"></i>Size Limit Reached</span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "</div></div></div><!-- Maintenance Info --><div class=\"row mb-3\"><div class=\"col-6\"><div class=\"text-center\"><div class=\"h6 mb-0 font-weight-bold text-info\">#")
+ 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, "</div><small class=\"text-muted\">Compact Revision</small></div></div><div class=\"col-6\"><div class=\"text-center\"><div class=\"h6 mb-0 font-weight-bold text-secondary\">")
+ 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, "<span class=\"text-muted\">Never modified</span>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "</div><small class=\"text-muted\">Last Modified</small></div></div></div><!-- TTL Configuration -->")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if data.Volume.Ttl > 0 {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "<div class=\"mb-3 text-center\"><span class=\"badge bg-info fs-6 px-3 py-2\"><i class=\"fas fa-clock me-1\"></i>")
+ 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, "</span><div class=\"mt-1\"><small class=\"text-muted\">Time To Live</small></div></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<!-- Remote Storage Configuration -->")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if data.Volume.RemoteStorageName != "" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "<hr class=\"my-3\"><div class=\"mb-2\"><div class=\"text-center\"><div class=\"h6 mb-1 font-weight-bold text-info\"><i class=\"fas fa-cloud me-1\"></i>")
+ 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, "</div><small class=\"text-muted\">Remote Storage</small></div></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if data.Volume.RemoteStorageKey != "" {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "<div class=\"text-center\"><div class=\"text-xs font-monospace bg-light p-2 rounded text-truncate\" title=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var25 string
+ templ_7745c5c3_Var25, 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: 260, Col: 138}
+ }
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "\">")
+ 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, "</div><small class=\"text-muted\">Storage Key</small></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "</div></div></div></div><!-- Replicas Card -->")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ if len(data.Replicas) > 0 {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "<div class=\"row\"><div class=\"col-12\"><div class=\"card shadow mb-4\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\"><i class=\"fas fa-copy me-2\"></i>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, ")</h6></div><div class=\"card-body\"><div class=\"table-responsive\"><table class=\"table table-hover\"><thead><tr><th>Server</th><th>Data Center</th><th>Rack</th><th>Size</th><th>File Count</th><th>Status</th><th>Actions</th></tr></thead> <tbody><!-- Primary Volume (current one) --><tr class=\"table-primary\"><td><strong><a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var28 templ.SafeURL = templ.SafeURL(fmt.Sprintf("http://%s", data.Volume.Server))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var28)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "\" target=\"_blank\" class=\"text-decoration-none\">")
+ 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, " <i class=\"fas fa-external-link-alt ms-1 text-muted\"></i></a></strong> <span class=\"badge bg-success ms-2\">Primary</span></td><td><span class=\"badge bg-light text-dark\">")
+ 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, "</span></td><td><span class=\"badge bg-light text-dark\">")
+ 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, "</span></td><td>")
+ 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, "</td><td>")
+ 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, "</td><td><span class=\"badge bg-success\">Active</span></td><td><span class=\"text-muted\">Current Volume</span></td></tr><!-- Replica Volumes -->")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ for _, replica := range data.Replicas {
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "<tr><td><a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var34 templ.SafeURL = templ.SafeURL(fmt.Sprintf("http://%s", replica.Server))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var34)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "\" target=\"_blank\" class=\"text-decoration-none\">")
+ 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, " <i class=\"fas fa-external-link-alt ms-1 text-muted\"></i></a></td><td><span class=\"badge bg-light text-dark\">")
+ 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, "</span></td><td><span class=\"badge bg-light text-dark\">")
+ 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, "</span></td><td>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var38 string
+ templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.JoinStringErrs(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, "</td><td>")
+ 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, "</td><td><span class=\"badge bg-info\">Replica</span></td><td><a href=\"")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ var templ_7745c5c3_Var40 templ.SafeURL = templ.SafeURL(fmt.Sprintf("/cluster/volumes/%d/%s", replica.Id, replica.Server))
+ _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var40)))
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 59, "\" class=\"btn btn-sm btn-outline-primary\"><i class=\"fas fa-eye me-1\"></i>View</a></td></tr>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 60, "</tbody></table></div></div></div></div></div>")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "<!-- Actions Card --><div class=\"row\"><div class=\"col-12\"><div class=\"card shadow mb-4\"><div class=\"card-header py-3\"><h6 class=\"m-0 font-weight-bold text-primary\"><i class=\"fas fa-tools me-2\"></i>Actions</h6></div><div class=\"card-body\"><div class=\"btn-group\" role=\"group\"><button type=\"button\" class=\"btn btn-outline-secondary\" title=\"Compact Volume\"><i class=\"fas fa-compress-alt me-1\"></i>Compact</button> <button type=\"button\" class=\"btn btn-outline-warning\" title=\"Fix Volume\"><i class=\"fas fa-wrench me-1\"></i>Fix</button> <button type=\"button\" class=\"btn btn-outline-danger\" title=\"Vacuum Volume\"><i class=\"fas fa-trash me-1\"></i>Vacuum</button></div><div class=\"mt-3\"><small class=\"text-muted\"><i class=\"fas fa-info-circle me-1\"></i> Use these actions to perform maintenance operations on the volume.</small></div></div></div></div></div><!-- Last Updated --><div class=\"row\"><div class=\"col-12\"><small class=\"text-muted\"><i class=\"fas fa-clock me-1\"></i> 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, "</small></div></div>")
+ 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